From 3dab85351e1fe862a54294cec70e7139abea0d7d Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Mon, 15 Jun 2020 00:10:16 -0700
Subject: added ? to a lot of things

---
 src/client/views/collections/CollectionView.tsx | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a25a864af..00a1d27f0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -213,12 +213,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
 
     setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
-        const existingVm = ContextMenu.Instance.findByDescription(category);
+        const existingVm = ContextMenu.Instance?.findByDescription(category);
         const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
 
         subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
         if (addExtras && CollectionView._safeMode) {
-            ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
+            ContextMenu.Instance?.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
         }
         subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
         subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
@@ -235,7 +235,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
-        !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
+        !existingVm && ContextMenu.Instance?.addItem({ description: category, subitems: subItems, icon: "eye" });
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
@@ -248,7 +248,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance.findByDescription("Options...");
+            const existing = ContextMenu.Instance?.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -259,9 +259,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && ContextMenu.Instance?.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
-            const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+            const existingOnClick = ContextMenu.Instance?.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
             const funcs = [
                 { key: "onChildClick", name: "On Child Clicked" },
@@ -277,12 +277,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                     icon: "edit",
                     event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
                 }));
-            !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+            !existingOnClick && ContextMenu.Instance?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
-            const more = ContextMenu.Instance.findByDescription("More...");
+            const more = ContextMenu.Instance?.findByDescription("More...");
             const moreItems = more && "subitems" in more ? more.subitems : [];
             moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-            !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+            !more && ContextMenu.Instance?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
         }
     }
 
-- 
cgit v1.2.3-70-g09d2


From 738a56b3c18c06d8290346373eb60a125120caaf Mon Sep 17 00:00:00 2001
From: Lionel Han <47760119+IGoByJoe@users.noreply.github.com>
Date: Thu, 18 Jun 2020 15:02:50 -0700
Subject: hopefully fixed new ink

---
 src/client/util/InteractionUtils.tsx               |  4 +-
 src/client/views/InkingStroke.tsx                  |  4 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionView.tsx    |  4 +-
 .../views/collections/CollectionViewChromes.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +--
 src/client/views/nodes/DocumentView.tsx            | 44 +++++++++++-----------
 src/mobile/MobileInterface.tsx                     | 16 +++++---
 src/pen-gestures/ndollar.ts                        |  2 +-
 9 files changed, 44 insertions(+), 40 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index aeb0f670d..3b42fcaee 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -96,9 +96,9 @@ export namespace InteractionUtils {
         }
         else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
             //pointer is up (first and last points are the same)
-            points.pop();
+            //points.pop();
             const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
-
+            newPoints.pop();
             const bezierCurves = fitCurve(newPoints, parseInt(bezier));
             for (var i = 0; i < bezierCurves.length; i++) {
                 for (var t = 0; t < 1; t += 0.01) {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7e3bd1c17..5129ef65d 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -68,8 +68,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
                     mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset"
                 }}
                 onContextMenu={() => {
-                    ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
-                    ContextMenu.Instance.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+                    ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
+                    ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
                 }}
             ><defs>
                     <filter id="dangerShine">
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c2e297f61..657e2a08b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -790,7 +790,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         if (this._mainCont && this._mainCont.children) {
             const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
             const scale = Utils.GetScreenTransform(this._mainCont).scale;
-            return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
+            return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
         }
         return Transform.Identity();
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a700d0dfb..0a84e6f72 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -283,10 +283,10 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             !existingOnClick && ContextMenu.Instance?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode) {
-                const more = ContextMenu.Instance.findByDescription("More...");
+                const more = ContextMenu.Instance?.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
                 moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-                !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+                !more && ContextMenu.Instance?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
             }
         }
     }
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 48810f1e9..ec908f034 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -300,7 +300,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
 
     render() {
         const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
-        const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale);
+        const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
         return (
             <div className="collectionViewChrome-cont" style={{
                 top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 82d6dee73..bed871f8d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1199,13 +1199,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     onContextMenu = (e: React.MouseEvent) => {
         if (this.props.annotationsKey) return;
 
-        ContextMenu.Instance.addItem({
+        ContextMenu.Instance?.addItem({
             description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => {
                 this._timelineVisible = !this._timelineVisible;
             }), icon: this._timelineVisible ? faEyeSlash : faEye
         });
 
-        const options = ContextMenu.Instance.findByDescription("Options...");
+        const options = ContextMenu.Instance?.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
 
         optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
@@ -1246,7 +1246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             }
         });
         optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+        ContextMenu.Instance?.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
     @observable _timelineVisible = false;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e027b6a0f..2f868d558 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -708,40 +708,40 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
         if (!(e instanceof Touch)) {
-            if (e.button === 0 && !e.ctrlKey) {
-                e.preventDefault();
+            if (e?.button === 0 && !e.ctrlKey) {
+                e?.preventDefault();
                 return;
             }
-            e.persist();
+            e?.persist();
             e?.stopPropagation();
 
-            if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
-                e.isDefaultPrevented()) {
-                e.preventDefault();
+            if (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3 ||
+                e?.isDefaultPrevented()) {
+                e?.preventDefault();
                 return;
             }
-            e.preventDefault();
+            e?.preventDefault();
         }
 
         const cm = ContextMenu.Instance;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
-            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm?.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
-            cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm?.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
-        let options = cm.findByDescription("Options...");
+        let options = cm?.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         optionItems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         if (!options) {
             options = { description: "Options...", subitems: optionItems, icon: "compass" };
-            cm.addItem(options);
+            cm?.addItem(options);
         }
 
-        const existingOnClick = cm.findByDescription("OnClick...");
+        const existingOnClick = cm?.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.props.Document.layoutKey}")`), icon: "window-restore" });
@@ -750,17 +750,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
-        !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && cm?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
         if (this.Document.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
-            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm?.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
-        const more = cm.findByDescription("More...");
+        const more = cm?.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
@@ -789,26 +789,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
-        !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
+        !more && cm?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+        cm?.moveAfter(cm?.findByDescription("More...")!, cm?.findByDescription("OnClick...")!);
 
-        const help = cm.findByDescription("Help...");
+        const help = cm?.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
         helpItems.push({
             description: "Keyboard Shortcuts       Ctrl+/",
             event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"),
             icon: "keyboard"
         });
-        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+        cm?.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
 
-        const existingAcls = cm.findByDescription("Privacy...");
+        const existingAcls = cm?.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+        !existingAcls && cm?.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
@@ -869,7 +869,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
-            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+            ContextMenu.Instance?.displayMenu(e.pageX - 15, e.pageY - 15);
             !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 4000bee85..3a81e5de1 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -708,6 +708,10 @@ export class MobileInterface extends React.Component {
         e.stopPropagation();
     }
 
+    uploadToCurrentCollection = (doc: Doc) => {
+
+    }
+
     render() {
         return (
             <div className="mobileInterface-container" onDragOver={this.onDragOver}>
@@ -717,13 +721,13 @@ export class MobileInterface extends React.Component {
                 </div>
                 {this.switchMenuView()}
                 {this.inkMenu()}
+                <div className="docButtonContainer">
+                    {this.pinToPresentation()}
+                    {this.downloadDocument()}
+                    {this.drawInk()}
+                    {this.uploadAudioButton()}
+                </div>
                 <GestureOverlay>
-                    <div className="docButtonContainer">
-                        {this.pinToPresentation()}
-                        {this.downloadDocument()}
-                        {this.drawInk()}
-                        {this.uploadAudioButton()}
-                    </div>
                     {this.displayWorkspaces()}
                     {this.renderDefaultContent()}
                 </GestureOverlay>
diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts
index 9d42035d1..ecd8df3e7 100644
--- a/src/pen-gestures/ndollar.ts
+++ b/src/pen-gestures/ndollar.ts
@@ -552,7 +552,7 @@ function Distance(p1: any, p2: any) // distance between two points
 }
 function CalcStartUnitVector(points: any, index: any) // start angle from points[0] to points[index] normalized as a unit vector
 {
-    const v = new Point(points[index].X - points[0].X, points[index].Y - points[0].Y);
+    const v = new Point(points[index]?.X - points[0]?.X, points[index]?.Y - points[0]?.Y);
     const len = Math.sqrt(v.X * v.X + v.Y * v.Y);
     return new Point(v.X / len, v.Y / len);
 }
-- 
cgit v1.2.3-70-g09d2


From 6a75dac53b9fab5b96f2cce4937b59f1e29928f9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 30 Jun 2020 01:08:45 -0400
Subject: added a docFilter for a substring match.  fixed formattedTextBox to
 not keep adding user_mark storedMarks on every load

---
 package-lock.json                                  | 44 ++++++----------------
 src/client/util/SearchUtil.ts                      |  2 +-
 src/client/views/collections/CollectionSubView.tsx | 13 +++++--
 .../views/collections/CollectionTreeView.tsx       |  4 +-
 src/client/views/collections/CollectionView.tsx    | 26 +++++++++----
 .../views/nodes/formattedText/FormattedTextBox.tsx |  9 +++--
 src/client/views/nodes/formattedText/marks_rts.ts  |  1 +
 src/fields/Doc.ts                                  | 33 +++++++++-------
 8 files changed, 69 insertions(+), 63 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/package-lock.json b/package-lock.json
index 53b2fc009..957641213 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2955,8 +2955,7 @@
                         },
                         "ansi-regex": {
                             "version": "2.1.1",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "aproba": {
                             "version": "1.2.0",
@@ -2974,13 +2973,11 @@
                         },
                         "balanced-match": {
                             "version": "1.0.0",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "brace-expansion": {
                             "version": "1.1.11",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "balanced-match": "^1.0.0",
                                 "concat-map": "0.0.1"
@@ -2993,18 +2990,15 @@
                         },
                         "code-point-at": {
                             "version": "1.1.0",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "concat-map": {
                             "version": "0.0.1",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "console-control-strings": {
                             "version": "1.1.0",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "core-util-is": {
                             "version": "1.0.2",
@@ -3107,8 +3101,7 @@
                         },
                         "inherits": {
                             "version": "2.0.4",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "ini": {
                             "version": "1.3.5",
@@ -3118,7 +3111,6 @@
                         "is-fullwidth-code-point": {
                             "version": "1.0.0",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "number-is-nan": "^1.0.0"
                             }
@@ -3131,20 +3123,17 @@
                         "minimatch": {
                             "version": "3.0.4",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "brace-expansion": "^1.1.7"
                             }
                         },
                         "minimist": {
                             "version": "1.2.5",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "minipass": {
                             "version": "2.9.0",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "safe-buffer": "^5.1.2",
                                 "yallist": "^3.0.0"
@@ -3161,7 +3150,6 @@
                         "mkdirp": {
                             "version": "0.5.3",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "minimist": "^1.2.5"
                             }
@@ -3217,8 +3205,7 @@
                         },
                         "npm-normalize-package-bin": {
                             "version": "1.0.1",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "npm-packlist": {
                             "version": "1.4.8",
@@ -3243,8 +3230,7 @@
                         },
                         "number-is-nan": {
                             "version": "1.0.1",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "object-assign": {
                             "version": "4.1.1",
@@ -3254,7 +3240,6 @@
                         "once": {
                             "version": "1.4.0",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "wrappy": "1"
                             }
@@ -3323,8 +3308,7 @@
                         },
                         "safe-buffer": {
                             "version": "5.1.2",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "safer-buffer": {
                             "version": "2.1.2",
@@ -3354,7 +3338,6 @@
                         "string-width": {
                             "version": "1.0.2",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "code-point-at": "^1.0.0",
                                 "is-fullwidth-code-point": "^1.0.0",
@@ -3372,7 +3355,6 @@
                         "strip-ansi": {
                             "version": "3.0.1",
                             "bundled": true,
-                            "optional": true,
                             "requires": {
                                 "ansi-regex": "^2.0.0"
                             }
@@ -3411,13 +3393,11 @@
                         },
                         "wrappy": {
                             "version": "1.0.2",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         },
                         "yallist": {
                             "version": "3.1.1",
-                            "bundled": true,
-                            "optional": true
+                            "bundled": true
                         }
                     }
                 }
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 5679c0a14..1ac68480e 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -74,7 +74,7 @@ export namespace SearchUtil {
         const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc);
         for (let i = 0; i < ids.length; i++) {
             const testDoc = docs[i];
-            if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
+            if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || testDoc.proto === undefined || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
                 theDocs.push(testDoc);
                 theLines.push([]);
             }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 29144abaf..b76859d90 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -7,7 +7,7 @@ import { List } from "../../../fields/List";
 import { listSpec } from "../../../fields/Schema";
 import { ScriptField } from "../../../fields/ScriptField";
 import { WebField } from "../../../fields/URLField";
-import { Cast, ScriptCast, NumCast } from "../../../fields/Types";
+import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types";
 import { GestureUtils } from "../../../pen-gestures/GestureUtils";
 import { Upload } from "../../../server/SharedMediaTypes";
 import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils";
@@ -136,8 +136,12 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             const filteredDocs = docFilters.length && !this.props.dontRegisterView ? childDocs.filter(d => {
                 for (const facetKey of Object.keys(filterFacets)) {
                     const facet = filterFacets[facetKey];
-                    const satisfiesFacet = Object.keys(facet).some(value =>
-                        (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value));
+                    const satisfiesFacet = Object.keys(facet).some(value => {
+                        if (facet[value] === "match") {
+                            return d[facetKey] === undefined || Cast(d[facetKey], RichTextField)?.Text.includes(value);
+                        }
+                        return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
+                    });
                     if (!satisfiesFacet) {
                         return false;
                     }
@@ -471,4 +475,5 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
 import { CollectionView } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager";
+import { SelectionManager } from "../../util/SelectionManager"; import { RichTextField } from "../../../fields/RichTextField";
+
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f6f6fb7cc..c1569a172 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -386,8 +386,8 @@ class TreeView extends React.Component<TreeViewProps> {
         e.stopPropagation();
     }
 
-    @computed
-    get renderBullet() {
+    @computed get renderBullet() {
+        TraceMobx();
         const checked = this.doc.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined;
         return <div className="bullet"
             title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f2ffe7835..c7b1afbd6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -47,6 +47,7 @@ import { CollectionGridView } from './collectionGrid/CollectionGridView';
 import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import { UndoManager } from '../../util/UndoManager';
+import { RichTextField } from '../../../fields/RichTextField';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -357,8 +358,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
     }
     @computed get _allFacets() {
+        TraceMobx();
         const facets = new Set<string>();
-        this.childDocs.filter(child => child).forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        this.childDocs.filter(child => child).forEach(child => child && Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
         Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key)));
         return Array.from(facets).filter(f => !f.startsWith("_") && !["proto", "zIndex", "isPrototype", "context", "text-noTemplate"].includes(f)).sort();
     }
@@ -387,8 +389,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             }
         } else {
             const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]);
-            const facetValues = Array.from(allCollectionDocs.reduce((set, child) =>
-                set.add(Field.toString(child[facetHeader] as Field)), new Set<string>()));
+            var rtfields = 0;
+            const facetValues = Array.from(allCollectionDocs.reduce((set, child) => {
+                const field = child[facetHeader] as Field;
+                if (field instanceof RichTextField) rtfields++;
+                return set.add(Field.toString(field));
+            }, new Set<string>()));
 
             let nonNumbers = 0;
             let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
@@ -402,13 +408,17 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 }
             });
             let newFacet: Opt<Doc>;
-            if (nonNumbers / allCollectionDocs.length < .1) {
-                newFacet = Docs.Create.SliderDocument({ title: facetHeader });
+            if (rtfields / allCollectionDocs.length > 0.1) {
+                newFacet = Docs.Create.TextDocument("", { _width: 200, _height: 30, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
+                Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+                newFacet.target = this.props.Document;
+                const scriptText = `setDocFilter(this.target, "${facetHeader}", text, "match")`;
+                newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
+            } else if (nonNumbers / allCollectionDocs.length < .1) {
+                newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true });
                 const newFacetField = Doc.LayoutFieldKey(newFacet);
                 const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
                 Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
-                newFacet.treeViewExpandedView = "layout";
-                newFacet.treeViewOpen = true;
                 const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05);
                 const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05);
                 newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
@@ -418,7 +428,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 newFacet.target = this.props.Document;
                 const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`;
                 newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
-
                 Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet);
             } else {
                 newFacet = new Doc();
@@ -445,6 +454,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
     }
     @computed get filterView() {
+        TraceMobx();
         const facetCollection = this.props.Document;
         const flyout = (
             <div className="collectionTimeView-flyout" style={{ width: `${this.facetWidth()}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 90f379525..222a359d1 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -22,7 +22,7 @@ import { PrefetchProxy } from '../../../../fields/Proxy';
 import { RichTextField } from "../../../../fields/RichTextField";
 import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../../fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../../fields/Types";
+import { Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../fields/Types";
 import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util';
 import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
@@ -203,13 +203,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             if (!this.dataDoc[AclSym]) {
                 if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
                     this._applyingChange = true;
-                    this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
+                    (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
                     if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
                         if (json !== curLayout?.Data) {
                             !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
                             !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
                             this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
                             this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
+                            ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
                         }
                     } else { // if we've deleted all the text in a note driven by a template, then restore the template data
                         this.dataDoc[this.props.fieldKey] = undefined;
@@ -970,7 +971,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         }
         (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
         // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
-        this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+        if (!this._editorView!.state.storedMarks || !this._editorView!.state.storedMarks.some(mark => mark.type === schema.marks.user_mark)) {
+            this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+        }
     }
     getFont(font: string) {
         switch (font) {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index b09ac0678..54b61aa20 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -270,6 +270,7 @@ export const marks: { [index: string]: MarkSpec } = {
             userid: { default: "" },
             modified: { default: "when?" }, // 1 second intervals since 1970
         },
+        excludes: "user_mark",
         group: "inline",
         toDOM(node: any) {
             const uid = node.attrs.userid.replace(".", "").replace("@", "");
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bef8acb06..e4d11dd4d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -945,20 +945,27 @@ export namespace Doc {
     // filters document in a container collection:
     // all documents with the specified value for the specified key are included/excluded 
     // based on the modifiers :"check", "x", undefined
-    export function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) {
+    export function setDocFilter(container: Doc, key: string, value: any, modifiers?: "match" | "check" | "x" | undefined) {
         const docFilters = Cast(container._docFilters, listSpec("string"), []);
-        for (let i = 0; i < docFilters.length; i += 3) {
-            if (docFilters[i] === key && docFilters[i + 1] === value) {
-                docFilters.splice(i, 3);
-                break;
+        runInAction(() => {
+            for (let i = 0; i < docFilters.length; i += 3) {
+                if (docFilters[i] === key && (docFilters[i + 1] === value || modifiers === "match")) {
+                    if (docFilters[i + 2] === modifiers && modifiers && docFilters[i + 1] === value) return;
+                    docFilters.splice(i, 3);
+                    break;
+                }
             }
-        }
-        if (typeof modifiers === "string") {
-            docFilters.push(key);
-            docFilters.push(value);
-            docFilters.push(modifiers);
-            container._docFilters = new List<string>(docFilters);
-        }
+            if (typeof modifiers === "string") {
+                if (!docFilters.length && modifiers === "match" && value === undefined) {
+                    container._docFilters = undefined;
+                } else {
+                    docFilters.push(key);
+                    docFilters.push(value);
+                    docFilters.push(modifiers);
+                    container._docFilters = new List<string>(docFilters);
+                }
+            }
+        })
     }
     export function readDocRangeFilter(doc: Doc, key: string) {
         const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []);
@@ -1168,5 +1175,5 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo
             (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)));
     return docs.length ? new List(docs) : prevValue;
 });
-Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); });
+Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "match" | "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); });
 Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From eb8399d057089a7b90d756fc69df096becad9a7f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 30 Jun 2020 02:21:31 -0400
Subject: added heuristic to filters to identify text fields based on being RTF
 or having multiple words. fixed text box padding when used as filter input. 
 fixed selection of items in sidebar when RichTextMenu is pinned.

---
 src/client/views/MainView.tsx                             |  5 +++--
 src/client/views/collections/CollectionSubView.tsx        |  6 +++---
 src/client/views/collections/CollectionTreeView.tsx       |  6 +++---
 src/client/views/collections/CollectionView.tsx           | 15 +++++++++------
 src/client/views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 5 files changed, 19 insertions(+), 15 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cea664543..200486279 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -383,7 +383,8 @@ export class MainView extends React.Component {
             doc.dockingConfig ? this.openWorkspace(doc) :
                 CollectionDockingView.AddRightSplit(doc, libraryPath);
     }
-    mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1);
+    sidebarScreenToLocal = () => new Transform(0, RichTextMenu.Instance.Pinned ? -35 : 0, 1);
+    mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
 
     @computed get flyout() {
         const sidebarContent = this.userDoc?.["tabs-panelContainer"];
@@ -402,7 +403,7 @@ export class MainView extends React.Component {
                     pinToPres={emptyFunction}
                     removeDocument={undefined}
                     onClick={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
+                    ScreenToLocalTransform={this.sidebarScreenToLocal}
                     ContentScaling={returnOne}
                     NativeHeight={returnZero}
                     NativeWidth={returnZero}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b76859d90..f39c1ae28 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
 import { action, computed, IReactionDisposer, reaction } from "mobx";
 import { basename } from 'path';
 import CursorField from "../../../fields/CursorField";
-import { Doc, Opt } from "../../../fields/Doc";
+import { Doc, Opt, Field } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
 import { listSpec } from "../../../fields/Schema";
@@ -138,7 +138,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                     const facet = filterFacets[facetKey];
                     const satisfiesFacet = Object.keys(facet).some(value => {
                         if (facet[value] === "match") {
-                            return d[facetKey] === undefined || Cast(d[facetKey], RichTextField)?.Text.includes(value);
+                            return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value);
                         }
                         return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value);
                     });
@@ -475,5 +475,5 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
 import { CollectionView } from "./CollectionView";
-import { SelectionManager } from "../../util/SelectionManager"; import { RichTextField } from "../../../fields/RichTextField";
+import { SelectionManager } from "../../util/SelectionManager";
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index c1569a172..620b977fa 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -304,7 +304,7 @@ class TreeView extends React.Component<TreeViewProps> {
     }
 
     rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.panelWidth() - 20);
-    rtfHeight = () => this.rtfWidth() < this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+    rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
 
     @computed get renderContent() {
         TraceMobx();
@@ -332,8 +332,8 @@ class TreeView extends React.Component<TreeViewProps> {
             </div></ul>;
         } else {
             const layoutDoc = this.layoutDoc;
-            const panelHeight = layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight;
-            const panelWidth = layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth;
+            const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
+            const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth;
             return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.doc[Id]}>
                 <ContentFittingDocumentView
                     Document={layoutDoc}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c7b1afbd6..6acf78af7 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -48,6 +48,7 @@ import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 import { UndoManager } from '../../util/UndoManager';
 import { RichTextField } from '../../../fields/RichTextField';
+import { TextField } from '../../util/ProsemirrorCopy/prompt';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -359,7 +360,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     }
     @computed get _allFacets() {
         TraceMobx();
-        const facets = new Set<string>();
+        const facets = new Set<string>(["type", "text", "data", "author", "ACL"]);
         this.childDocs.filter(child => child).forEach(child => child && Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
         Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key)));
         return Array.from(facets).filter(f => !f.startsWith("_") && !["proto", "zIndex", "isPrototype", "context", "text-noTemplate"].includes(f)).sort();
@@ -392,8 +393,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             var rtfields = 0;
             const facetValues = Array.from(allCollectionDocs.reduce((set, child) => {
                 const field = child[facetHeader] as Field;
-                if (field instanceof RichTextField) rtfields++;
-                return set.add(Field.toString(field));
+                const fieldStr = Field.toString(field);
+                if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++;
+                return set.add(fieldStr);
             }, new Set<string>()));
 
             let nonNumbers = 0;
@@ -408,13 +410,14 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 }
             });
             let newFacet: Opt<Doc>;
-            if (rtfields / allCollectionDocs.length > 0.1) {
-                newFacet = Docs.Create.TextDocument("", { _width: 200, _height: 30, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
+            if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) {
+                newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
                 Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
                 newFacet.target = this.props.Document;
+                newFacet._textBoxPadding = 4;
                 const scriptText = `setDocFilter(this.target, "${facetHeader}", text, "match")`;
                 newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
-            } else if (nonNumbers / allCollectionDocs.length < .1) {
+            } else if (nonNumbers / facetValues.length < .1) {
                 newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true });
                 const newFacetField = Doc.LayoutFieldKey(newFacet);
                 const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 222a359d1..2cb55e0fa 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1308,7 +1308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                         onScroll={this.onscrolled} onDrop={this.ondrop} >
                         <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
                             style={{
-                                padding: `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
+                                padding: this.layoutDoc._textBoxPadding ? this.layoutDoc._textBoxPadding : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px  ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
                                 pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
                             }}
                         />
-- 
cgit v1.2.3-70-g09d2


From 45d4390a70f0a1199718f006cdbaef3029c19443 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 30 Jun 2020 15:37:22 -0400
Subject: added start of push pin annotations

---
 src/client/views/collections/CollectionView.tsx | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 6acf78af7..dab82185a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,7 +18,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
 import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyFilter } from '../../../Utils';
-import { Docs } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
@@ -139,7 +139,21 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             } else if (this.dataDoc[AclSym] === AclAddonly) {
                 added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
             } else {
-                added.map(doc => doc.context = this.props.Document);
+                added.map(doc => {
+                    const context = Cast(doc.context, Doc, null);
+                    if (context && (context?.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+                        const pushpin = Docs.Create.FreeformDocument([], {
+                            title: "pushpin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null),
+                            _width: 10, _height: 10, _backgroundColor: "red", isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
+                        });
+                        Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
+                        DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
+                        const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
+                        first && (first.hidden = true);
+                        doc.displayTimecode = undefined;
+                    }
+                    doc.context = this.props.Document;
+                });
                 added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add));
                 targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]);
                 targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-- 
cgit v1.2.3-70-g09d2


From 973e49dc36f208cc7f9a261cbe03e8d65d001b92 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 1 Jul 2020 01:53:42 -0400
Subject: added isPushpin.  added 'hidden' for DocumentViews to stop displaying
 them.  made pushpin's FontIconBox's.  made Esc close context menu.

---
 src/client/util/DocumentManager.ts                 |  8 +-
 src/client/views/ContextMenu.tsx                   |  2 +
 src/client/views/DocumentDecorations.tsx           | 92 +++++++++++-----------
 src/client/views/GlobalKeyHandler.ts               |  6 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 src/client/views/collections/CollectionView.tsx    | 12 +--
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 src/client/views/nodes/FontIconBox.tsx             |  9 ++-
 8 files changed, 74 insertions(+), 60 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index fb5d1717e..1fa5faeb3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -146,7 +146,7 @@ export class DocumentManager {
         };
         const docView = getFirstDocView(targetDoc, originatingDoc);
         let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
-        if (annotatedDoc) {
+        if (annotatedDoc && !linkDoc?.isPushpin) {
             const first = getFirstDocView(annotatedDoc);
             if (first) {
                 annotatedDoc = first.props.Document;
@@ -156,7 +156,11 @@ export class DocumentManager {
             }
         }
         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(docView.props.Document, willZoom, undefined, focusAndFinish);
+            if (linkDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden;
+            else {
+                docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
+                docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
+            }
             highlight();
         } else {
             const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 941d7b44a..07f7b8e6d 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -155,9 +155,11 @@ export class ContextMenu extends React.Component {
 
     @action
     closeMenu = () => {
+        const wasOpen = this._display;
         this.clearItems();
         this._display = false;
         this._shouldDisplay = false;
+        return wasOpen;
     }
 
     @computed get filteredItems(): (OriginalMenuProps | string[])[] {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4fda10926..a45ef8862 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -600,52 +600,54 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
             }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} >
             </div>
-            <div className="documentDecorations-container" ref={this.setTextBar} style={{
-                width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
-                height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
-                left: bounds.x - this._resizeBorderWidth / 2,
-                top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
-            }}>
-                {maximizeIcon}
-                {titleArea}
-                {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
-                    <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
-                        {"_"}
-                    </div>}
-                <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
-                    {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+            {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
+                <div className="documentDecorations-container" key="container" ref={this.setTextBar} style={{
+                    width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+                    height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
+                    left: bounds.x - this._resizeBorderWidth / 2,
+                    top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
+                }}>
+                    {maximizeIcon}
+                    {titleArea}
+                    {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
+                        <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
+                            {"_"}
+                        </div>}
+                    <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
+                        {SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
+                    </div>
+                    <div id="documentDecorations-rotation" className="documentDecorations-rotation"
+                        onPointerDown={this.onRotateDown}> ⟲ </div>
+                    <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-leftResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-centerCont"></div>
+                    <div id="documentDecorations-rightResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
+                        onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                    {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
+                        <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+                            title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
+                            <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+                        </div>}
+                    <div id="documentDecorations-borderRadius" className="documentDecorations-radius"
+                        onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
+
+                </div >
+                <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
+                    <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
                 </div>
-                <div id="documentDecorations-rotation" className="documentDecorations-rotation"
-                    onPointerDown={this.onRotateDown}> ⟲ </div>
-                <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-topResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-leftResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-centerCont"></div>
-                <div id="documentDecorations-rightResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
-                    onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-                {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
-                    <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
-                        title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
-                        <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
-                    </div>}
-                <div id="documentDecorations-borderRadius" className="documentDecorations-radius"
-                    onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
-
-            </div >
-            <div className="link-button-container" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}>
-                <DocumentButtonBar views={SelectionManager.SelectedDocuments} />
-            </div>
+            </>}
         </div >
         );
     }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e3546dece..c85849adb 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -20,6 +20,7 @@ import { MainView } from "./MainView";
 import { DocumentView } from "./nodes/DocumentView";
 import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
 import PDFMenu from "./pdf/PDFMenu";
+import { ContextMenu } from "./ContextMenu";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -81,16 +82,17 @@ export default class KeyManager {
                 DocumentLinksButton.StartLink = undefined;
                 const main = MainView.Instance;
                 Doc.SetSelectedTool(InkTool.None);
+                var doDeselect = true;
                 if (main.isPointerDown) {
                     DragManager.AbortDrag();
                 } else {
                     if (CollectionDockingView.Instance.HasFullScreen()) {
                         CollectionDockingView.Instance.CloseFullScreen();
                     } else {
-                        SelectionManager.DeselectAll();
+                        doDeselect = !ContextMenu.Instance.closeMenu();
                     }
                 }
-                SelectionManager.DeselectAll();
+                doDeselect && SelectionManager.DeselectAll();
                 DictationManager.Controls.stop();
                 // RecommendationsBox.Instance.closeMenu();
                 GoogleAuthenticationManager.Instance.cancel();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d107db86b..95c4898cc 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -225,7 +225,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                     const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
                     const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
                     const res = addedDocs.length ? this.addDocument(addedDocs) : true;
-                    added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res;
+                    added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document) || de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res;
                 } else {
                     added = this.addDocument(docDragData.droppedDocuments);
                 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dab82185a..705fbdd34 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -141,15 +141,17 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             } else {
                 added.map(doc => {
                     const context = Cast(doc.context, Doc, null);
-                    if (context && (context?.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
-                        const pushpin = Docs.Create.FreeformDocument([], {
-                            title: "pushpin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null),
-                            _width: 10, _height: 10, _backgroundColor: "red", isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
+                    if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG) &&
+                        !DocListCast(doc.links).some(d => d.isPushpin)) {
+                        const pushpin = Docs.Create.FontIconDocument({
+                            icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
+                            _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
                         });
                         Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
-                        DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
+                        const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
                         const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
                         first && (first.hidden = true);
+                        pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
                         doc.displayTimecode = undefined;
                     }
                     doc.context = this.props.Document;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 21b6d8310..09eeaee36 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1169,8 +1169,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
         if (!(this.props.Document instanceof Doc)) return (null);
+        if (this.props.Document[AclSym] && this.props.Document[AclSym] === AclPrivate) return (null);
+        if (this.props.Document.hidden) return (null);
         const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : this.props.forcedBackgroundColor?.(this.Document) || StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
         const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
         const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index cf0b16c7c..5e8dd2497 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -5,7 +5,7 @@ import { createSchema, makeInterface } from '../../../fields/Schema';
 import { DocComponent } from '../DocComponent';
 import './FontIconBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
-import { StrCast, Cast } from '../../../fields/Types';
+import { StrCast, Cast, NumCast } from '../../../fields/Types';
 import { Utils } from "../../../Utils";
 import { runInAction, observable, reaction, IReactionDisposer } from 'mobx';
 import { Doc } from '../../../fields/Doc';
@@ -59,13 +59,14 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
 
     render() {
         const referenceDoc = (this.layoutDoc.dragFactory instanceof Doc ? this.layoutDoc.dragFactory : this.layoutDoc);
-        const referenceLayout = Doc.Layout(referenceDoc);
+        const refLayout = Doc.Layout(referenceDoc);
         return <button className="fontIconBox-outerDiv" title={StrCast(this.layoutDoc.title)} ref={this._ref} onContextMenu={this.specificContextMenu}
             style={{
-                background: StrCast(referenceLayout.backgroundColor),
+                padding: Cast(this.layoutDoc._xPadding, "number", null),
+                background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
                 boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
             }}>
-            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={this._foregroundColor} size="sm" />
+            <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
             {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 5)} </div>}
         </button>;
     }
-- 
cgit v1.2.3-70-g09d2


From 1daf0a496175267d86f495bb24aa5140b6a9eeab Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 2 Jul 2020 14:05:33 -0400
Subject: small clanup in Mobile related code

---
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/collections/CollectionView.tsx    | 21 ++++++++--------
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 ++++----
 src/client/views/nodes/DocumentView.tsx            | 29 +++++++++++-----------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 5 files changed, 33 insertions(+), 32 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 839ab09c7..487467b2b 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -264,7 +264,7 @@ export default class GestureOverlay extends Touchable {
     handleHandDown = async (e: React.TouchEvent) => {
         this._holdTimer && clearTimeout(this._holdTimer);
 
-        // this chunk of code helps us keep track of which touch events ar e associated with a hand event
+        // this chunk of code helps us keep track of which touch events are associated with a hand event
         // so that if a hand is held down, but a second hand is interacting with dash, the second hand's events
         // won't interfere with the first hand's events.
         const fingers = new Array<React.Touch>();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 35196b634..300309882 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -231,12 +231,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
 
 
     setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
-        const existingVm = ContextMenu.Instance?.findByDescription(category);
+        const existingVm = ContextMenu.Instance.findByDescription(category);
         const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
 
         subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
         if (addExtras && CollectionView._safeMode) {
-            ContextMenu.Instance?.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
+            ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
         }
         subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
         subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
@@ -255,11 +255,12 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
         }
         addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
-        !existingVm && ContextMenu.Instance?.addItem({ description: category, subitems: subItems, icon: "eye" });
+        !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
     }
 
     onContextMenu = (e: React.MouseEvent): void => {
-        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        const cm = ContextMenu.Instance;
+        if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             this.setupViewTypes("Add a Perspective...", vtype => {
                 const newRendition = Doc.MakeAlias(this.props.Document);
                 newRendition._viewType = vtype;
@@ -267,7 +268,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                 return newRendition;
             }, false);
 
-            const existing = ContextMenu.Instance?.findByDescription("Options...");
+            const existing = cm.findByDescription("Options...");
             const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
             layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
             if (this.props.Document.childLayout instanceof Doc) {
@@ -278,9 +279,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
             }
             layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
 
-            !existing && ContextMenu.Instance?.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+            !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
 
-            const existingOnClick = ContextMenu.Instance?.findByDescription("OnClick...");
+            const existingOnClick = cm.findByDescription("OnClick...");
             const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
             const funcs = [
                 { key: "onChildClick", name: "On Child Clicked" },
@@ -296,13 +297,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                     icon: "edit",
                     event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
                 }));
-            !existingOnClick && ContextMenu.Instance?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+            !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
             if (!Doc.UserDoc().noviceMode) {
-                const more = ContextMenu.Instance?.findByDescription("More...");
+                const more = cm.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
                 moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
-                !more && ContextMenu.Instance?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+                !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
             }
         }
     }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ae7213d0b..21b0473c9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -602,7 +602,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
         // bcz: theres should be a better way of doing these than referencing these static instances directly
         MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
-        // PDFMenu.Instance.fadeOut(true);
 
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
@@ -1204,17 +1203,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
     private thumbIdentifier?: number;
 
     onContextMenu = (e: React.MouseEvent) => {
-        if (this.props.annotationsKey) return;
+        if (this.props.annotationsKey || !ContextMenu.Instance) return;
 
-        const appearance = ContextMenu.Instance?.findByDescription("Appearance...");
+        const appearance = ContextMenu.Instance.findByDescription("Appearance...");
         const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
         appearanceItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
         appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
         appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
-        !appearance && ContextMenu.Instance?.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+        !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
 
-        const options = ContextMenu.Instance?.findByDescription("Options...");
+        const options = ContextMenu.Instance.findByDescription("Options...");
         const optionItems = options && "subitems" in options ? options.subitems : [];
         !this.props.isAnnotationOverlay &&
             optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
@@ -1254,7 +1253,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 }
             });
         }
-        !options && ContextMenu.Instance?.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+        !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
 
     }
     @observable showTimeline = false;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8d122baeb..732089068 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -736,21 +736,22 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
 
         const cm = ContextMenu.Instance;
+        if (!cm) return;
 
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
-            cm?.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
         this.props.contextMenuItems?.().forEach(item =>
-            cm?.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
+            cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
 
         const options = cm.findByDescription("Options...");
         const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
         const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
         templateDoc && optionItems.push({ description: "Open Template   ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
         optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
-        !options && cm?.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+        !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
 
-        const existingOnClick = cm?.findByDescription("OnClick...");
+        const existingOnClick = cm.findByDescription("OnClick...");
         const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
         onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
         onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "window-restore" });
@@ -759,17 +760,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
         onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
         onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
-        !existingOnClick && cm?.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+        !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
 
         const funcs: ContextMenuProps[] = [];
         if (this.layoutDoc.onDragStart) {
             funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
             funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
             funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
-            cm?.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+            cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
         }
 
-        const more = cm?.findByDescription("More...");
+        const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
@@ -798,16 +799,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
         moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
-        !more && cm?.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
-        cm?.moveAfter(cm?.findByDescription("More...")!, cm?.findByDescription("OnClick...")!);
+        !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
+        cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
 
-        const help = cm?.findByDescription("Help...");
+        const help = cm.findByDescription("Help...");
         const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
         helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
         helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
-        cm?.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
+        cm.addItem({ description: "Help...", subitems: helpItems, icon: "question" });
 
-        const existingAcls = cm?.findByDescription("Privacy...");
+        const existingAcls = cm.findByDescription("Privacy...");
         const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
         aclItems.push({ description: "Make Add Only", event: () => this.setAcl("addOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
@@ -815,7 +816,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
         aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
-        !existingAcls && cm?.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+        !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
 
         // const recommender_subitems: ContextMenuProps[] = [];
 
@@ -876,7 +877,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 // DocumentViews should stop propagation of this event
                 e.stopPropagation();
             }
-            ContextMenu.Instance?.displayMenu(e.pageX - 15, e.pageY - 15);
+            cm.displayMenu(e.pageX - 15, e.pageY - 15);
             !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
         });
         const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 270fcb01c..b127cd024 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -190,7 +190,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 }
             }
             const state = this._editorView.state.apply(tx);
-            this._editorView?.updateState(state);
+            this._editorView.updateState(state);
             (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
 
             const tsel = this._editorView.state.selection.$from;
-- 
cgit v1.2.3-70-g09d2


From d95136bc94270132711ab60f329c20419f52a91d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 3 Jul 2020 17:26:11 -0400
Subject: remove imports

---
 src/client/views/collections/CollectionView.tsx    | 11 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 35 ++++++++++------------
 2 files changed, 21 insertions(+), 25 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 10ebe571b..26abd2529 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -12,26 +12,29 @@ import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
+import { RichTextField } from '../../../fields/RichTextField';
 import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, emptyPath, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnEmptyFilter } from '../../../Utils';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { InteractionUtils } from '../../util/InteractionUtils';
+import { UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { ScriptBox } from '../ScriptBox';
 import { Touchable } from '../Touchable';
-import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
+import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from "./CollectionDockingView";
 import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionGridView } from './collectionGrid/CollectionGridView';
 import { CollectionLinearView } from './CollectionLinearView';
 import CollectionMapView from './CollectionMapView';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
@@ -43,12 +46,8 @@ import { CollectionStaffView } from './CollectionStaffView';
 import { SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
 import { CollectionTreeView } from "./CollectionTreeView";
-import { CollectionGridView } from './collectionGrid/CollectionGridView';
 import './CollectionView.scss';
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
-import { UndoManager } from '../../util/UndoManager';
-import { RichTextField } from '../../../fields/RichTextField';
-import { TextField } from '../../util/ProsemirrorCopy/prompt';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7f6d1d506..b81e400b3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,54 +1,51 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc";
-import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
+import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
 import { Id } from "../../../../fields/FieldSymbols";
-import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField";
+import { InkData, InkField, InkTool } from "../../../../fields/InkField";
 import { List } from "../../../../fields/List";
 import { RichTextField } from "../../../../fields/RichTextField";
-import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
-import { ScriptField, ComputedField } from "../../../../fields/ScriptField";
+import { createSchema, makeInterface } from "../../../../fields/Schema";
+import { ScriptField } from "../../../../fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
 import { TraceMobx } from "../../../../fields/util";
 import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
-import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
+import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { Docs, DocUtils } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
 import { DocumentManager } from "../../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../../util/DragManager";
 import { HistoryUtil } from "../../../util/History";
 import { InteractionUtils } from "../../../util/InteractionUtils";
 import { SelectionManager } from "../../../util/SelectionManager";
+import { SnappingManager } from "../../../util/SnappingManager";
 import { Transform } from "../../../util/Transform";
 import { undoBatch, UndoManager } from "../../../util/UndoManager";
 import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
+import { Timeline } from "../../animationtimeline/Timeline";
 import { ContextMenu } from "../../ContextMenu";
-import { ContextMenuProps } from "../../ContextMenuItem";
+import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke";
 import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
+import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
+import { DocumentViewProps } from "../../nodes/DocumentView";
 import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
 import { pageSchema } from "../../nodes/ImageBox";
-import PDFMenu from "../../pdf/PDFMenu";
 import { CollectionDockingView } from "../CollectionDockingView";
 import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout, computerPassLayout } from "./CollectionFreeFormLayoutEngines";
+import { CollectionViewType } from "../CollectionView";
+import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
 import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
 import "./CollectionFreeFormView.scss";
 import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
-import { CollectionViewType } from "../CollectionView";
-import { Timeline } from "../../animationtimeline/Timeline";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { InkingStroke, ActiveArrowStart, ActiveArrowEnd, ActiveInkColor, ActiveFillColor, ActiveInkWidth, ActiveInkBezierApprox, ActiveDash } from "../../InkingStroke";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { DocumentLinksButton } from "../../nodes/DocumentLinksButton";
-import { MainView } from "../../MainView";
 
 library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
 
-- 
cgit v1.2.3-70-g09d2


From 8264ac5484c0c7b5d47cb78ce60f5d6568e736d9 Mon Sep 17 00:00:00 2001
From: anika-ahluwalia <anika.ahluwalia@gmail.com>
Date: Sat, 4 Jul 2020 17:25:10 -0500
Subject: redesigned editing menu, added link descriptions and popup on created

---
 src/client/documents/Documents.ts                  |  9 +--
 src/client/util/LinkManager.ts                     |  6 +-
 src/client/views/EditableView.tsx                  |  6 +-
 src/client/views/GestureOverlay.tsx                |  2 +-
 src/client/views/MainView.tsx                      |  2 +
 src/client/views/RecommendationsBox.tsx            |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/linking/LinkEditor.scss           | 48 ++++++++++++---
 src/client/views/linking/LinkEditor.tsx            | 59 +++++++++++++++---
 src/client/views/linking/LinkMenu.scss             | 13 +++-
 src/client/views/linking/LinkMenu.tsx              | 15 +++--
 src/client/views/linking/LinkMenuGroup.tsx         |  6 +-
 src/client/views/linking/LinkMenuItem.scss         | 48 ++++++++++++---
 src/client/views/linking/LinkMenuItem.tsx          | 23 ++++---
 src/client/views/nodes/DocumentLinksButton.tsx     | 52 ++++++++++------
 src/client/views/nodes/DocumentView.tsx            | 42 +++++++++----
 src/client/views/nodes/LinkCreatedBox.tsx          |  8 +--
 src/client/views/nodes/LinkDescriptionPopup.scss   | 62 +++++++++++++++++++
 src/client/views/nodes/LinkDescriptionPopup.tsx    | 70 ++++++++++++++++++++++
 22 files changed, 393 insertions(+), 88 deletions(-)
 create mode 100644 src/client/views/nodes/LinkDescriptionPopup.scss
 create mode 100644 src/client/views/nodes/LinkDescriptionPopup.tsx

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f81c25bab..11b80aef9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -94,6 +94,7 @@ export interface DocumentOptions {
     label?: string; // short form of title for use as an icon label
     style?: string;
     page?: number;
+    description?: string; // added for links
     _viewScale?: number;
     isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents
     forceActive?: boolean;
@@ -259,7 +260,7 @@ export namespace Docs {
             }],
             [DocumentType.LINK, {
                 layout: { view: LinkBox, dataField: defaultDataKey },
-                options: { _height: 150 }
+                options: { _height: 150, description: "" }
             }],
             [DocumentType.LINKDB, {
                 data: new List<Doc>(),
@@ -864,15 +865,15 @@ export namespace DocUtils {
     export let ActiveRecordings: Doc[] = [];
 
     export function MakeLinkToActiveAudio(doc: Doc) {
-        DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
+        DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "", "audio timeline"));
     }
 
-    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) {
+    export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
         const sv = DocumentManager.Instance.getDocumentView(source.doc);
         if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
         if (target.doc === Doc.UserDoc()) return undefined;
 
-        const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id);
+        const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
         linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
         Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") "  + self.anchor2?.title');
 
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 749fabfcc..6da581f35 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,4 +1,4 @@
-import { Doc, DocListCast } from "../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../fields/Doc";
 import { List } from "../../fields/List";
 import { listSpec } from "../../fields/Schema";
 import { Cast, StrCast } from "../../fields/Types";
@@ -23,6 +23,10 @@ import { Scripting } from "./Scripting";
 export class LinkManager {
 
     private static _instance: LinkManager;
+
+
+    public static currentLink: Opt<Doc>;
+
     public static get Instance(): LinkManager {
         return this._instance || (this._instance = new this());
     }
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 628db366f..25a87ab56 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -194,7 +194,11 @@ export class EditableView extends React.Component<EditableProps> {
                     ref={this._ref}
                     style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
                     onClick={this.onClick} placeholder={this.props.placeholder}>
-                    <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
+                    <span style={{
+                        fontStyle: this.props.fontStyle, fontSize: this.props.fontSize,
+                        color: this.props.contents ? this.props.color ? this.props.color : "black" : "grey"
+                    }}>
+                        {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
                 </div>
             );
         }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 487467b2b..cdc468066 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -551,7 +551,7 @@ export default class GestureOverlay extends Touchable {
             else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
                 // we don't want to create a link between ink strokes (doing so makes drawing a t very hard)
                 if (this._d1.type !== "ink" && doc.type !== "ink") {
-                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link");
+                    DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", "");
                     actionPerformed = true;
                 }
             }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 15f818d1f..68ea51456 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -61,6 +61,7 @@ import { LinkMenu } from './linking/LinkMenu';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
 import { Fade } from '@material-ui/core';
 import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 
 @observer
 export class MainView extends React.Component {
@@ -608,6 +609,7 @@ export class MainView extends React.Component {
             </GestureOverlay>
             <PreviewCursor />
             <LinkCreatedBox />
+            {LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
             {DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
             {LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
                 linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
index cdde32c21..196151e32 100644
--- a/src/client/views/RecommendationsBox.tsx
+++ b/src/client/views/RecommendationsBox.tsx
@@ -169,7 +169,7 @@ export class RecommendationsBox extends React.Component<FieldViewProps> {
                             <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
                                 <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
                             </div>
-                            <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", undefined)}>
+                            <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", "", undefined)}>
                                 <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 620b977fa..26c41f524 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -207,7 +207,7 @@ class TreeView extends React.Component<TreeViewProps> {
         if (complete.linkDragData) {
             const sourceDoc = complete.linkDragData.linkSourceDocument;
             const destDoc = this.doc;
-            DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link");
+            DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", "");
             e.stopPropagation();
         }
         const docDragData = complete.docDragData;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 26abd2529..df21d6a28 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -147,7 +147,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
                             _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
                         });
                         Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
-                        const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin");
+                        const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
                         const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
                         first && (first.hidden = true);
                         pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b81e400b3..9bf425db2 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -246,7 +246,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         } else {
             const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
             this.props.addDocument(source);
-            linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation"); // TODODO this is where in text links get passed
+            linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed
             e.stopPropagation();
             return true;
         }
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 97ed74c10..b47236bea 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -493,7 +493,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         summary._backgroundColor = "#e2ad32";
         portal.layoutKey = "layout_portal";
         portal.title = "document collection";
-        DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing");
+        DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
 
         this.props.addLiveTextDocument(summary);
         MarqueeOptionsMenu.Instance.fadeOut(true);
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index b47c8976e..14b2106d5 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -7,34 +7,66 @@
     user-select: none;
 }
 
-.linkEditor-back {
+.linkEditor-button-back {
     margin-bottom: 6px;
+    border-radius: 10px;
+    width: 18px;
+    height: 18px;
+    padding: 0;
 }
 
 .linkEditor-info {
-    border-bottom: 0.5px solid $light-color-secondary; 
-    padding-bottom: 6px;
-    margin-bottom: 6px;
+    //border-bottom: 0.5px solid $light-color-secondary;
+    padding-bottom: 4px;
+    padding-top: 5px;
+    padding-left: 5px;
+    //margin-bottom: 6px;
     display: flex;
     justify-content: space-between;
 
     .linkEditor-linkedTo {
         width: calc(100% - 26px);
+        padding-left: 5px;
+        padding-right: 5px
+    }
+}
+
+.linkEditor-description {
+    padding-left: 6.5px;
+    padding-right: 6.5px;
+    padding-bottom: 3.5px;
+
+    .linkEditor-description-text {
+        text-decoration-color: grey;
+    }
+
+    .linkEditor-description-input {
+        border: 1px solid rgb(114, 162, 179);
+        border-radius: 4px;
+        background-color: lightblue;
+        padding-left: 2px;
+        padding-right: 2px;
+        color: grey;
+        text-decoration-color: grey;
     }
 }
 
-.linkEditor-button, .linkEditor-addbutton {
+
+.linkEditor-button,
+.linkEditor-addbutton {
     width: 18px;
     height: 18px;
     padding: 0;
     // font-size: 12px;
     border-radius: 10px;
 
+
     &:disabled {
         background-color: gray;
     }
 }
-.linkEditor-addbutton{
+
+.linkEditor-addbutton {
     margin-left: 0px;
 }
 
@@ -44,7 +76,7 @@
 }
 
 .linkEditor-group {
-    background-color: $light-color-secondary; 
+    background-color: $light-color-secondary;
     padding: 6px;
     margin: 3px 0;
     border-radius: 3px;
@@ -56,7 +88,7 @@
 
     .linkEditor-group-row-label {
         margin-right: 6px;
-        display:inline-block;
+        display: inline-block;
     }
 
     .linkEditor-metadata-row {
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 13b9a2459..4a6d9f2d3 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,14 +1,17 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
 import { observer } from "mobx-react";
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
 import { StrCast } from "../../../fields/Types";
 import { Utils } from "../../../Utils";
 import { LinkManager } from "../../util/LinkManager";
 import './LinkEditor.scss';
 import React = require("react");
+import { DocumentView } from "../nodes/DocumentView";
+import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
+import { EditableView } from "../EditableView";
 
 library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
 
@@ -281,27 +284,69 @@ interface LinkEditorProps {
 @observer
 export class LinkEditor extends React.Component<LinkEditorProps> {
 
+
+    @observable description = StrCast(LinkManager.currentLink?.description);
+
+    //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
+
     @action
     deleteLink = (): void => {
         LinkManager.Instance.deleteLink(this.props.linkDoc);
         this.props.showLinks();
     }
 
+    @action
+    setDescripValue = (value: string) => {
+        if (LinkManager.currentLink) {
+            LinkManager.currentLink.description = value;
+            return true;
+        }
+    }
+
+    @computed
+    get editDescription() {
+        return <div className="linkEditor-description">
+            <div className="linkEditor-description-label">
+                Link Description:</div>
+            <div className="linkEditor-description-input">
+                <EditableView
+                    GetValue={() => StrCast(LinkManager.currentLink?.description)}
+                    SetValue={value => { this.setDescripValue(value); return false; }}
+                    contents={LinkManager.currentLink?.description}
+                    placeholder={"(optional) enter link description"}
+                    color={"rgb(88, 88, 88)"}
+                ></EditableView></div></div>;
+    }
+
+    @computed
+    get followingDropdown() {
+        return "choose follow behavior";
+    }
+
     render() {
         const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
 
         const groups = [this.props.linkDoc].map(groupDoc => {
-            return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc} sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
+            return <LinkGroupEditor key={"gred-" + StrCast(groupDoc.linkRelationship)} linkDoc={this.props.linkDoc}
+                sourceDoc={this.props.sourceDoc} groupDoc={groupDoc} />;
         });
 
         return !destination ? (null) : (
             <div className="linkEditor">
-                {this.props.hideback ? (null) : <button className="linkEditor-back" onPointerDown={() => this.props.showLinks()}><FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
                 <div className="linkEditor-info">
-                    <p className="linkEditor-linkedTo">editing link to: <b>{destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
-                    <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link"><FontAwesomeIcon icon="trash" size="sm" /></button>
+                    {this.props.hideback ? (null) : <button className="linkEditor-button-back"
+                        onPointerDown={this.props.showLinks}>
+                        <FontAwesomeIcon icon="arrow-left" size="sm" /></button>}
+                    <p className="linkEditor-linkedTo">editing link to: <b>{
+                        destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
+                    <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
+                        <FontAwesomeIcon icon="trash" size="sm" /></button>
                 </div>
-                {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>}
+
+                <div>{this.editDescription}</div>
+                <div>{this.followingDropdown}</div>
+
+                {/* {groups.length > 0 ? groups : <div className="linkEditor-group">There are currently no relationships associated with this link.</div>} */}
             </div>
 
         );
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 6468ccd3d..f827f25c2 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -11,12 +11,17 @@
     position: absolute;
     z-index: 10;
     background: $link-color;
-    min-width: 150px
+    min-width: 150px;
+    border-radius: 5px;
+    padding-top: 6.5px;
+    padding-bottom: 6.5px;
+    padding-left: 6.5px;
+    padding-right: 2px;
 }
 
 .linkMenu-group {
-    border-bottom: 0.5px solid lightgray;
-    padding: 5px 0;
+    //border-bottom: 0.5px solid lightgray;
+    //@extend: 5px 0;
 
 
     &:last-child {
@@ -26,9 +31,11 @@
     .linkMenu-group-name {
         display: flex;
 
+
         &:hover {
             p {
                 background-color: lightgray;
+
             }
 
             p.expand-one {
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index c672511ac..8721b9f3d 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -11,6 +11,7 @@ import { faTrash } from '@fortawesome/free-solid-svg-icons';
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
 import { LinkDocPreview } from "../nodes/LinkDocPreview";
+import { isUndefined } from "util";
 
 library.add(faTrash);
 
@@ -26,18 +27,19 @@ export class LinkMenu extends React.Component<Props> {
 
     @observable private _editingLink?: Doc;
     @observable private _linkMenuRef: Opt<HTMLDivElement | null>;
+    private _editorRef = React.createRef<HTMLDivElement>();
 
     @action
     onClick = (e: PointerEvent) => {
 
         LinkDocPreview.LinkInfo = undefined;
 
-        if (this._linkMenuRef?.contains(e.target as any)) {
-            DocumentLinksButton.EditLink = undefined;
-        }
 
-        if (this._linkMenuRef && !this._linkMenuRef.contains(e.target as any)) {
-            DocumentLinksButton.EditLink = undefined;
+        if (this._linkMenuRef && !!!this._linkMenuRef.contains(e.target as any)) {
+            if (this._editorRef && !!!this._editorRef.current?.contains(e.target as any)) {
+                console.log("outside click");
+                DocumentLinksButton.EditLink = undefined;
+            }
         }
     }
     @action
@@ -82,7 +84,8 @@ export class LinkMenu extends React.Component<Props> {
             ref={(r) => this._linkMenuRef = r} style={{ left: this.props.location[0], top: this.props.location[1] }}>
             {!this._editingLink ?
                 this.renderAllGroups(groups) :
-                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)} />
+                <LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
+                    showLinks={action(() => this._editingLink = undefined)} />
             }
         </div>;
     }
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index 7892d381b..ec17776e3 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -26,6 +26,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
 
     private _drag = React.createRef<HTMLDivElement>();
     private _table = React.createRef<HTMLDivElement>();
+    private _menuRef = React.createRef<HTMLDivElement>();
 
     onLinkButtonDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
@@ -74,12 +75,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
                     linkDoc={linkDoc}
                     sourceDoc={this.props.sourceDoc}
                     destinationDoc={destination}
-                    showEditor={this.props.showEditor} />;
+                    showEditor={this.props.showEditor}
+                    menuRef={this._menuRef} />;
             }
         });
 
         return (
-            <div className="linkMenu-group">
+            <div className="linkMenu-group" ref={this._menuRef}>
                 {/* <div className="linkMenu-group-name">
                     <p ref={this._drag} onPointerDown={this.onLinkButtonDown}
                         className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index e3ce69cd7..a71b2dbba 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,19 +4,38 @@
     // border-top: 0.5px solid $main-accent; 
     position: relative;
     display: flex;
-    font-size: 12px;
 
 
     .linkMenu-name {
         position: relative;
 
-        p {
-            padding: 4px 6px;
-            line-height: 12px;
-            border-radius: 5px;
-            overflow-wrap: break-word;
-            user-select: none;
+        .linkMenu-text {
+
+            padding: 4px 2px;
+
+            .linkMenu-destination-title {
+                text-decoration: none;
+                color: rgb(46, 82, 160);
+                font-size: 14px;
+                padding-bottom: 2px;
+            }
+
+            .linkMenu-description {
+                text-decoration: none;
+                font-style: italic;
+                color: rgb(95, 97, 102);
+                font-size: 10px;
+            }
+
+            p {
+                //padding: 4px 2px;
+                line-height: 12px;
+                border-radius: 5px;
+                overflow-wrap: break-word;
+                user-select: none;
+            }
         }
+
     }
 
     .linkMenu-item-content {
@@ -32,19 +51,30 @@
     }
 
     &:hover {
+
         .linkMenu-item-buttons {
             display: flex;
         }
 
         .linkMenu-item-content {
+
+            .linkMenu-destination-title {
+                text-decoration: underline;
+                color: rgb(15, 57, 148);
+            }
+
             &.expand-two p {
                 width: calc(100% - 52px);
-                background-color: lightgray;
+                //text-decoration: underline;
+                //color: rgb(15, 57, 148);
+                //background-color: lightgray;
             }
 
             &.expand-three p {
                 width: calc(100% - 84px);
-                background-color: lightgray;
+                //text-decoration: underline;
+                //color: rgb(15, 57, 148);
+                //background-color: lightgray;
             }
         }
     }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 04cd83ee0..891c6d263 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -26,6 +26,7 @@ interface LinkMenuItemProps {
     destinationDoc: Doc;
     showEditor: (linkDoc: Doc) => void;
     addDocTab: (document: Doc, where: string) => boolean;
+    menuRef: React.Ref<HTMLDivElement>;
 }
 
 // drag links and drop link targets (aliasing them if needed)
@@ -77,6 +78,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
 
     onEdit = (e: React.PointerEvent): void => {
+        LinkManager.currentLink = this.props.linkDoc;
         setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
     }
 
@@ -110,7 +112,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         document.removeEventListener("pointerup", this.onLinkButtonUp);
         document.addEventListener("pointerup", this.onLinkButtonUp);
 
-        if (this._buttonRef && this._buttonRef.current?.contains(e.target as any)) {
+        if (this._buttonRef && !!!this._buttonRef.current?.contains(e.target as any)) {
+            console.log("outside click");
             LinkDocPreview.LinkInfo = undefined;
         }
     }
@@ -174,18 +177,24 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                             Location: [e.clientX, e.clientY + 20]
                         }))}
                         onPointerDown={this.onLinkButtonDown}>
-                        <p >{StrCast(this.props.destinationDoc.title)}</p>
+
+                        <div className="linkMenu-text">
+                            <p className="linkMenu-destination-title"
+                                onPointerDown={this.followDefault}>
+                                {StrCast(this.props.destinationDoc.title)}</p>
+                            {this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
+                                {StrCast(this.props.linkDoc.description)}</p> : null} </div>
+
                         <div className="linkMenu-item-buttons" ref={this._buttonRef} >
                             {canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
                                 <FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
 
-                            {/* <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
-                                <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div> */}
+                            <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+                                <FontAwesomeIcon className="fa-icon" icon="pencil-alt" size="sm" /></div>
                             <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
                                 <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
-                            <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
-                                <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
-                            </div>
+                            {/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
+                                <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
                         </div>
                     </div>
                     {this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index bfd860f65..5d1a68af5 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -11,6 +11,8 @@ import { DocUtils } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
 import { LinkCreatedBox } from "./LinkCreatedBox";
+import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
+import { LinkManager } from "../../util/LinkManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -54,6 +56,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         return false;
     }
 
+
     onLinkButtonDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu) {
@@ -87,15 +90,22 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     //     Doc.UnBrushDoc(this.props.View.Document);
                     // });
                 } else {
-                    DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
-                        DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
-                    runInAction(() => {
-                        LinkCreatedBox.popupX = e.screenX;
-                        LinkCreatedBox.popupY = e.screenY - 120;
-                        LinkCreatedBox.linkCreated = true;
-                        setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-                    });
+
+                    if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+                        const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                        LinkManager.currentLink = linkDoc;
+                        runInAction(() => {
+                            LinkCreatedBox.popupX = e.screenX;
+                            LinkCreatedBox.popupY = e.screenY - 33;
+                            LinkCreatedBox.linkCreated = true;
+
+                            LinkDescriptionPopup.popupX = e.screenX;
+                            LinkDescriptionPopup.popupY = e.screenY;
+                            LinkDescriptionPopup.descriptionPopup = true;
+
+                            setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                        });
+                    }
                 }
             }
         }));
@@ -109,15 +119,21 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             //     Doc.UnBrushDoc(this.props.View.Document);
             // });
         } else {
-            DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
-                DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
-
-            runInAction(() => {
-                LinkCreatedBox.popupX = e.screenX;
-                LinkCreatedBox.popupY = e.screenY - 120;
-                LinkCreatedBox.linkCreated = true;
-                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-            });
+            if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+                const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                LinkManager.currentLink = linkDoc;
+                runInAction(() => {
+                    LinkCreatedBox.popupX = e.screenX;
+                    LinkCreatedBox.popupY = e.screenY - 33;
+                    LinkCreatedBox.linkCreated = true;
+
+                    LinkDescriptionPopup.popupX = e.screenX;
+                    LinkDescriptionPopup.popupY = e.screenY;
+                    LinkDescriptionPopup.descriptionPopup = true;
+
+                    setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                });
+            }
         }
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b38db9a1e..9a4d9ac53 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,6 +43,8 @@ import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
 import { LinkCreatedBox } from './LinkCreatedBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
+import { LinkManager } from '../../util/LinkManager';
 
 library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
     fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -642,30 +644,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             e.stopPropagation();
             de.complete.annoDragData.linkedToDoc = true;
 
+            const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
+            LinkManager.currentLink = linkDoc;
+
             runInAction(() => {
                 LinkCreatedBox.popupX = de.x;
-                LinkCreatedBox.popupY = de.y;
+                LinkCreatedBox.popupY = de.y - 33;
                 LinkCreatedBox.linkCreated = true;
+
+                LinkDescriptionPopup.popupX = de.x;
+                LinkDescriptionPopup.popupY = de.y;
+                LinkDescriptionPopup.descriptionPopup = true;
+
                 setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
             });
-
-            DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
 
-            runInAction(() => {
-                LinkCreatedBox.popupX = de.x;
-                LinkCreatedBox.popupY = de.y;
-                LinkCreatedBox.linkCreated = true;
-                setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
-            });
+            if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
+                const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
+                    { doc: this.props.Document }, `link`);
+                LinkManager.currentLink = linkDoc;
+
+                de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
+                    (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+                runInAction(() => {
+                    LinkCreatedBox.popupX = de.x;
+                    LinkCreatedBox.popupY = de.y - 33;
+                    LinkCreatedBox.linkCreated = true;
+
+                    LinkDescriptionPopup.popupX = de.x;
+                    LinkDescriptionPopup.popupY = de.y;
+                    LinkDescriptionPopup.descriptionPopup = true;
+
+                    setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+                });
+            }
 
-            de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
-                (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
-                    { doc: this.props.Document }, `link`)); // TODODO this is where in text links get passed
         }
     }
 
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
index d157d3fca..648ae23c8 100644
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ b/src/client/views/nodes/LinkCreatedBox.tsx
@@ -11,8 +11,8 @@ import { Fade } from "@material-ui/core";
 export class LinkCreatedBox extends React.Component<{}> {
 
     @observable public static linkCreated: boolean = false;
-    @observable public static popupX: number = 600;
-    @observable public static popupY: number = 250;
+    @observable public static popupX: number = 500;
+    @observable public static popupY: number = 150;
 
     @action
     public static changeLinkCreated = () => {
@@ -23,8 +23,8 @@ export class LinkCreatedBox extends React.Component<{}> {
         return <Fade in={LinkCreatedBox.linkCreated}>
             <div className="linkCreatedBox-fade"
                 style={{
-                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 600,
-                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 250,
+                    left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
+                    top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
                 }}>Link Created</div>
         </Fade>;
     }
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
new file mode 100644
index 000000000..474bd919b
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -0,0 +1,62 @@
+.linkDescriptionPopup {
+
+    display: flex;
+
+    border: 1px solid rgb(100, 100, 100);
+
+    width: auto;
+    position: absolute;
+
+    height: auto;
+    z-index: 10000;
+    border-radius: 10px;
+    font-size: 12px;
+    //white-space: nowrap;
+
+    background-color: rgba(250, 250, 250, 0.95);
+    padding-top: 9px;
+    padding-bottom: 9px;
+    padding-left: 9px;
+    padding-right: 9px;
+
+    .linkDescriptionPopup-input {
+        float: left;
+        color: rgb(100, 100, 100);
+    }
+
+    .linkDescriptionPopup-btn {
+
+        float: right;
+
+
+        .linkDescriptionPopup-btn-dismiss {
+            background-color: white;
+            color: black;
+            display: inline;
+            right: 0;
+            border-radius: 10px;
+            border: 1px solid black;
+            padding: 3px;
+            font-size: 9px;
+            text-align: center;
+            position: relative;
+            transform: translateY(5px);
+        }
+
+        .linkDescriptionPopup-btn-add {
+            background-color: black;
+            color: white;
+            display: inline;
+            right: 0;
+            border-radius: 10px;
+            border: 1px solid black;
+            padding: 3px;
+            font-size: 9px;
+            text-align: center;
+            position: relative;
+            transform: translateY(5px);
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
new file mode 100644
index 000000000..078a738e7
--- /dev/null
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -0,0 +1,70 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import "./LinkDescriptionPopup.scss";
+import { observable, action } from "mobx";
+import { EditableView } from "../EditableView";
+import { LinkManager } from "../../util/LinkManager";
+
+
+@observer
+export class LinkDescriptionPopup extends React.Component<{}> {
+
+    @observable public static descriptionPopup: boolean = false;
+    @observable public static popupX: number = 700;
+    @observable public static popupY: number = 350;
+    @observable description: string = "";
+    @observable popupRef = React.createRef<HTMLDivElement>();
+
+    @action
+    descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this.description = e.currentTarget.value;
+    }
+
+    @action
+    setDescription = () => {
+        if (LinkManager.currentLink) {
+            LinkManager.currentLink.description = this.description;
+        }
+        LinkDescriptionPopup.descriptionPopup = false;
+    }
+
+    @action
+    onDismiss = () => {
+        LinkDescriptionPopup.descriptionPopup = false;
+    }
+
+    @action
+    onClick = (e: PointerEvent) => {
+        if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
+            LinkDescriptionPopup.descriptionPopup = false;
+        }
+    }
+
+    @action
+    componentDidMount() {
+        document.addEventListener("pointerdown", this.onClick);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("pointerdown", this.onClick);
+    }
+
+    render() {
+        return <div className="linkDescriptionPopup" ref={this.popupRef}
+            style={{
+                left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
+                top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+            }}>
+            <input className="linkDescriptionPopup-input"
+                placeholder={"(optional) enter link label..."}
+                onChange={(e) => this.descriptionChanged(e)}>
+            </input>
+            <div className="linkDescriptionPopup-btn">
+                <div className="linkDescriptionPopup-btn-dismiss"
+                    onPointerDown={this.onDismiss}> Dismiss </div>
+                <div className="linkDescriptionPopup-btn-add"
+                    onPointerDown={this.setDescription}> Add </div>
+            </div>
+        </div>;
+    }
+} 
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2

