From f869c7a75eb6a2f6e6301aa76c57e383b41d3fea Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 27 Jul 2020 11:52:37 -0400
Subject: added hypothesis icon to toolbar. fixed toolbar to update to any
 document selection with specific items for each type.

---
 src/client/views/nodes/DocumentView.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b59875a7e..7f86ec98e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -44,6 +44,7 @@ import { MobileInterface } from '../../../mobile/MobileInterface';
 import { TaskCompletionBox } from './TaskCompletedBox';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { LinkManager } from '../../util/LinkManager';
+import CollectionMenu from '../collections/CollectionMenu';
 
 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,
-- 
cgit v1.2.3-70-g09d2


From 56832abc50045b5154c97155dd5e7aa25ae5de29 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 27 Jul 2020 20:50:38 -0400
Subject: fixed scrollin to web annotations

---
 .../collectionFreeForm/CollectionFreeFormView.tsx   |  2 +-
 src/client/views/nodes/DocumentView.tsx             | 13 +++++++++++++
 src/client/views/nodes/WebBox.tsx                   | 21 +++++++++++++++------
 src/client/views/pdf/PDFViewer.tsx                  |  4 ++--
 4 files changed, 31 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index daa6e7440..792f3af5f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -892,7 +892,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
                 this.props.focus(doc);
             } else {
                 const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
-                const offset = annotOn && (contextHgt / 2 * 96 / 72);
+                const offset = annotOn && (contextHgt / 2);
                 this.props.Document._scrollY = NumCast(doc.y) - offset;
             }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7f86ec98e..c6c21a507 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -586,6 +586,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
+    @undoBatch
+    togglePushpin = () => {
+        this.Document.ignoreClick = false;
+        if (this.Document.isLinkButton || this.Document.isPushpin || this.onClickHandler || this.Document.ignoreClick) {
+            this.Document.isPushpin = this.Document.isLinkButton = false;
+            this.Document.onClick = this.layoutDoc.onClick = undefined;
+        } else {
+            this.Document.isPushpin = this.Document.isLinkButton = true;
+            this.Document.followLinkZoom = false;
+            this.Document.followLinkLocation = undefined;
+        }
+    }
 
     @undoBatch
     toggleLinkButtonBehavior = (): void => {
@@ -776,6 +788,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
         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: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: this.togglePushpin, icon: "snowflake" });
         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...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" });
 
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 7e3e662a4..d30f1499e 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { WebField } from "../../../fields/URLField";
 import { TraceMobx } from "../../../fields/util";
-import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils } from "../../../Utils";
+import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { ImageUtils } from "../../util/Import & Export/ImageUtils";
@@ -553,7 +553,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         clipDoc._width = this.marqueeWidth();
         clipDoc._height = this.marqueeHeight();
         clipDoc._scrollTop = this.marqueeY();
-        const targetDoc = Docs.Create.TextDocument("", { _width: 200, _height: 200, title: "Note linked to " + this.props.Document.title });
+        const targetDoc = Docs.Create.TextDocument("", { _width: 125, _height: 125, title: "Note linked to " + this.props.Document.title });
         Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
         clipDoc.rootDocument = targetDoc;
         targetDoc.layoutKey = "layout";
@@ -564,8 +564,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                     if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
                         DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
                         annotationDoc.isLinkButton = true;
-                        e.annoDragData.dropDocument.isPushpin = true;
-                        e.annoDragData.dropDocument.isLinkButton = true;
                     }
                 }
             });
@@ -588,8 +586,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             else if (this._mainCont.current) {
                 // set marquee x and y positions to the spatially transformed position
                 const boundingRect = this._mainCont.current.getBoundingClientRect();
+                const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width;
                 this._startX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
-                this._startY = (e.clientY - boundingRect.top) / boundingRect.height * (this.Document._nativeHeight || 1);
+                this._startY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1);
                 this._marqueeHeight = this._marqueeWidth = 0;
                 this._marqueeing = true;
             }
@@ -604,8 +603,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
         if (this._marqueeing && this._mainCont.current) {
             // transform positions and find the width and height to set the marquee to
             const boundingRect = this._mainCont.current.getBoundingClientRect();
+            const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width;
             const curX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
-            const curY = (e.clientY - boundingRect.top) / boundingRect.height * (this.Document._nativeHeight || 1);
+            const curY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1);
             this._marqueeWidth = curX - this._startX;
             this._marqueeHeight = curY - this._startY;
             this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
@@ -662,6 +662,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     marqueeX = () => this._marqueeX;
     marqueeY = () => this._marqueeY;
     marqueeing = () => this._marqueeing;
+    visibleHeiht = () => {
+        if (this._mainCont.current) {
+            const boundingRect = this._mainCont.current.getBoundingClientRect();
+            const scalin = (this.Document._nativeWidth || 0) / boundingRect.width;
+            return Math.min(boundingRect.height * scalin, this.props.PanelHeight() * scalin);
+        }
+        return this.props.PanelHeight();
+    }
     scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
     render() {
         return (<div className="webBox" ref={this._mainCont} >
@@ -706,6 +714,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
                             annotationsKey={this.annotationKey}
                             NativeHeight={returnZero}
                             NativeWidth={returnZero}
+                            VisibleHeight={this.visibleHeiht}
                             focus={this.props.focus}
                             setPreviewCursor={this.setPreviewCursor}
                             isSelected={this.props.isSelected}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5ed842ab7..c792df882 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -346,7 +346,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @action
     scrollToAnnotation = (scrollToAnnotation: Doc) => {
         if (scrollToAnnotation) {
-            const offset = this.visibleHeight() / 2 * 96 / 72;
+            const offset = this.visibleHeight() / 2;
             this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
             Doc.linkFollowHighlight(scrollToAnnotation);
         }
@@ -705,7 +705,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     marqueeX = () => this._marqueeX;
     marqueeY = () => this._marqueeY;
     marqueeing = () => this._marqueeing;
-    visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
+    visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling();
     contentZoom = () => this._zoomed;
     render() {
         TraceMobx();
-- 
cgit v1.2.3-70-g09d2


From 622ba99eff99f9af2e03c3a5f8a47e9ce95cdf7e Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 27 Jul 2020 23:46:22 -0400
Subject: removed pdf menu pin button.  tried to improve performance by
 computin links in document view.

---
 src/client/views/DocumentButtonBar.tsx         |  4 ++--
 src/client/views/DocumentDecorations.tsx       |  2 +-
 src/client/views/nodes/DocumentLinksButton.tsx | 24 +++++++++++++-----------
 src/client/views/nodes/DocumentView.tsx        | 15 +++++++++------
 src/client/views/pdf/PDFMenu.tsx               |  7 -------
 src/fields/documentSchemas.ts                  |  1 +
 6 files changed, 26 insertions(+), 27 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c99034d81..6b85616c2 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -276,10 +276,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const considerPush = isText && this.considerGoogleDocsPush;
         return <div className="documentButtonBar">
             <div className="documentButtonBar-button">
-                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+                <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
             </div>
             {DocumentLinksButton.StartLink ? <div className="documentButtonBar-button">
-                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+                <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
             </div> : null}
             <div className="documentButtonBar-button">
                 {this.templateButton}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9a4df926c..4257ec3c9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -88,7 +88,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
             const transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
             var [sptX, sptY] = transform.transformPoint(0, 0);
             let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
-            if (documentView.props.Document.type === DocumentType.LINK) {
+            if (StrCast(Doc.Layout(documentView.props.Document).layout).includes("LinkAnchorBox")) {
                 const docuBox = documentView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
                 if (docuBox.length) {
                     const rect = docuBox[0].getBoundingClientRect();
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c9d23ff3a..ddfa77967 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,19 +1,19 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../fields/Doc";
-import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { Doc } from "../../../fields/Doc";
+import { TraceMobx } from "../../../fields/util";
+import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
-import { UndoManager, undoBatch } from "../../util/UndoManager";
+import { LinkManager } from "../../util/LinkManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
 import './DocumentLinksButton.scss';
 import { DocumentView } from "./DocumentView";
-import React = require("react");
-import { DocUtils } from "../../documents/Documents";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { LinkDocPreview } from "./LinkDocPreview";
-import { TaskCompletionBox } from "./TaskCompletedBox";
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { LinkManager } from "../../util/LinkManager";
-import { Tooltip } from "@material-ui/core";
+import { TaskCompletionBox } from "./TaskCompletedBox";
+import React = require("react");
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -24,6 +24,7 @@ interface DocumentLinksButtonProps {
     AlwaysOn?: boolean;
     InMenu?: boolean;
     StartLink?: boolean;
+    links: Doc[];
 }
 @observer
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -158,7 +159,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @computed
     get linkButton() {
-        const links = DocListCast(this.props.View.props.Document.links);
+        TraceMobx();
+        const links = this.props.links;
 
         const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
         const buttonTitle = "Tap to view links";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c6c21a507..3b4dc3741 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -332,8 +332,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 const alias = Doc.MakeAlias(this.props.Document);
                 DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
                 this.props.addDocTab(alias, "onRight");
-            } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
-                DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
+            } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+                this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
                 if ((this.layoutDoc.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
@@ -683,7 +683,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @undoBatch
     @action
     makeIntoPortal = async () => {
-        const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document);
+        const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
         if (!portalLink) {
             const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
             DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
@@ -1038,6 +1038,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
     }
     childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+    @computed.struct get linkOffset() { return [-15, 0]; }
     @computed get contents() {
         TraceMobx();
         return (<div style={{ position: "absolute", width: "100%", height: "100%" }}>
@@ -1081,7 +1082,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
             {/* {this.allAnchors} */}
             {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) :
-                <DocumentLinksButton View={this} Offset={[-15, 0]} />}
+                <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
         </div>
         );
     }
@@ -1104,13 +1105,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     anchorPanelWidth = () => this.props.PanelWidth() || 1;
     anchorPanelHeight = () => this.props.PanelHeight() || 1;
 
-    @computed get allAnchors() {
+    @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.Document); }
+    @computed.struct get allLinks() { return DocListCast(this.Document.links); }
+    @computed.struct get allAnchors() {
         TraceMobx();
         if (this.props.LayoutTemplateString?.includes("LinkAnchorBox")) return null;
         return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots
             this.layoutDoc.presBox ||  // presentationbox nodes
             this.props.dontRegisterView ? (null) : // view that are not registered
-            DocUtils.FilterDocs(LinkManager.Instance.getAllDirectLinks(this.Document), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+            DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
                 <DocumentView {...this.props} key={i + 1}
                     Document={d}
                     ContainingCollectionView={this.props.ContainingCollectionView}
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 6592c488b..c3e1ae22f 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -84,11 +84,6 @@ export default class PDFMenu extends AntimodeMenu {
         e.preventDefault();
     }
 
-    togglePin = action((e: React.MouseEvent) => {
-        this.Pinned = !this.Pinned;
-        !this.Pinned && (this.Highlighting = false);
-    });
-
     @action
     highlightClicked = (e: React.MouseEvent) => {
         if (!this.Highlight(this.highlightColor) && this.Pinned) {
@@ -161,8 +156,6 @@ export default class PDFMenu extends AntimodeMenu {
                 this.highlighter,
                 <button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
                     <FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
-                <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
-                    <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /></button>,
             ] : [
                 <button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
                     <FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index ddffb56c3..8cf8f47b7 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -90,6 +90,7 @@ export const documentSchema = createSchema({
     followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) 
     hideLinkButton: "boolean",  // whether the blue link counter button should be hidden
     hideAllLinks: "boolean",    // whether all individual blue anchor dots should be hidden
+    linkDisplay: "boolean",     // whether a link connection should be shown between link anchor endpoints.
     isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
     isLinkButton: "boolean",    // whether document functions as a link follow button to follow the first link on the document when clicked   
     isBackground: "boolean",    // whether document is a background element and ignores input events (can only select with marquee)
-- 
cgit v1.2.3-70-g09d2


From 7693ca298392cb0551032b83b688366b1956bdd6 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 28 Jul 2020 10:52:02 -0400
Subject: cleaned up some font stuff.  cleaned up DocumentView a bit

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/util/SharingManager.tsx                 |   5 +-
 src/client/views/DocComponent.tsx                  |   6 +-
 src/client/views/GestureOverlay.tsx                |   2 +-
 src/client/views/MainView.tsx                      |  38 +-
 src/client/views/Touchable.tsx                     |   2 +-
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   8 +-
 src/client/views/collections/CollectionView.tsx    |   7 +-
 .../CollectionFreeFormLayoutEngines.tsx            |  24 --
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 -
 src/client/views/nodes/ComparisonBox.tsx           |   2 +-
 src/client/views/nodes/DocumentView.tsx            | 409 ++++-----------------
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/ScriptingBox.tsx            |   2 +-
 src/fields/Doc.ts                                  |   5 +
 16 files changed, 110 insertions(+), 410 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a1c66c2fe..2cd781a53 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1053,6 +1053,7 @@ export namespace DocUtils {
             }
         });
         batch.end();
+        return doc;
     }
     export function findTemplate(templateName: string, type: string, signature: string) {
         let docLayoutTemplate: Opt<Doc>;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 452a58d21..0d8b33fbe 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -8,7 +8,6 @@ import * as RequestPromise from "request-promise";
 import { Utils } from "../../Utils";
 import "./SharingManager.scss";
 import { observer } from "mobx-react";
-import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
 import { DocumentView } from "../views/nodes/DocumentView";
 import { SelectionManager } from "./SelectionManager";
@@ -23,8 +22,6 @@ import { List } from "../../fields/List";
 import { distributeAcls, SharingPermissions } from "../../fields/util";
 import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
 
-library.add(fa.faCopy, fa.faTimes);
-
 export interface User {
     email: string;
     userDocumentId: string;
@@ -447,7 +444,7 @@ export default class SharingManager extends React.Component<{}> {
                 <div className="sharing-contents">
                     <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
                     <div className={"close-button"} onClick={this.close}>
-                        <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+                        <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
                     </div>
                     {this.targetDoc?.author !== Doc.CurrentUserEmail ? null
                         :
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 9fd406407..4c82149e2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -26,7 +26,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
         // This is the data part of a document -- ie, the data that is constant across all views of the document
         @computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
 
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
@@ -59,7 +59,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
         lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
 
         active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);//  && !Doc.SelectedTool();  // bcz: inking state shouldn't affect static tools 
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     }
     return Component;
 }
@@ -114,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
             return style;
         }
 
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
         _annotationKey: string = "annotations";
         public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 2e588ceb5..ed77da8ca 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -63,7 +63,7 @@ export default class GestureOverlay extends Touchable {
     private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
     private _holdTimer: NodeJS.Timeout | undefined;
 
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     constructor(props: Readonly<{}>) {
         super(props);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 9fad612ee..33cd43678 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,18 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-
-import {
-    faHireAHelper
-} from '@fortawesome/free-brands-svg-icons';
-import {
-    faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
-    faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
-    faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
-    faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
-    faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
-    faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-    faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-    faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser
-} from '@fortawesome/free-solid-svg-icons';
+import { faHireAHelper } from '@fortawesome/free-brands-svg-icons';
+import * as fa from '@fortawesome/free-solid-svg-icons';
 import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -146,14 +134,20 @@ export class MainView extends React.Component {
             }
         }
 
-        library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
-            faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
-            faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
-            faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
-            faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
-            faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
-            faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
-            faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap, faUser, faHireAHelper);
+        library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt,
+            fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
+            fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
+            fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
+            fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
+            fa.faSearch, fa.faFileDownload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
+            fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faClipboard,
+            fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
+            fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
+            fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote,
+            fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
+            fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
+            fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
+            fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper);
         this.initEventListeners();
         this.initAuthenticationRouters();
     }
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index c4cae7e8d..bb9e108cb 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -12,7 +12,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
 
-    protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _touchDrag: boolean = false;
     protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 407524353..3b31947f7 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -176,7 +176,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                 }}
                     onPointerDown={e => e.stopPropagation()} >
                     <span className="bottomPopup-text" >
-                        Creating link from: {DocumentLinksButton.StartLink.title}
+                        Creating link from: {DocumentLinksButton.StartLink.props.Document.title}
                     </span>
 
                     <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d9d9c0eb8..99acfdcc2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -55,17 +55,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
     class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) {
         private dropDisposer?: DragManager.DragDropDisposer;
         private gestureDisposer?: GestureUtils.GestureEventDisposer;
-        protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+        protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
         protected _mainCont?: HTMLDivElement;
         protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
             this.dropDisposer?.();
             this.gestureDisposer?.();
-            this.multiTouchDisposer?.();
+            this._multiTouchDisposer?.();
             if (ele) {
                 this._mainCont = ele;
                 this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
-                this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
+                this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
         }
         protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -74,7 +74,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
 
         componentWillUnmount() {
             this.gestureDisposer?.();
-            this.multiTouchDisposer?.();
+            this._multiTouchDisposer?.();
         }
 
         @computed get dataDoc() {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 2c17254ed..e7a504e61 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -27,12 +27,10 @@ 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 { 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';
@@ -105,7 +103,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
     @observable private static _safeMode = false;
     public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
 
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     private AclMap = new Map<symbol, string>([
         [AclPrivate, SharingPermissions.None],
@@ -289,9 +287,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
         subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
         subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
         subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
-        if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
-            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" });
 
         const existingVm = ContextMenu.Instance.findByDescription(category);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 183ed3119..b00074cc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -434,27 +434,3 @@ function normalizeResults(
         payload: gname.payload
     })));
 }
-
-export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
-    return () => {
-        const addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
-            let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
-            const scriptField = Cast(doc[key], ScriptField);
-            const scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
-                // tslint:disable-next-line: no-unnecessary-callback-wrapper
-                onCancel={() => overlayDisposer()}  // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
-                onSave={(text, onError) => {
-                    const script = CompileScript(text, { params, requiredType, typecheck: false });
-                    if (!script.compiled) {
-                        onError(script.errors.map(error => error.messageText).join("\n"));
-                    } else {
-                        doc[key] = new ScriptField(script);
-                        overlayDisposer();
-                    }
-                }} />;
-            overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
-        };
-        addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
-        addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
-    };
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 792f3af5f..57336131a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -57,7 +57,6 @@ export const panZoomSchema = createSchema({
     currentTimecode: "number",
     displayTimecode: "number",
     currentFrame: "number",
-    arrangeScript: ScriptField,
     arrangeInit: ScriptField,
     useClusters: "boolean",
     fitToBox: "boolean",
@@ -1005,10 +1004,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         return this.props.addDocTab(doc, where);
     });
     getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
-        const result = this.Document.arrangeScript?.script.run(params, console.log);
-        if (result?.success) {
-            return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
-        }
         const layoutDoc = Doc.Layout(params.pair.layout);
         const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
             CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f140cc6e5..616cddfcf 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -24,7 +24,7 @@ const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
 @observer
 export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
-    protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+    protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
     private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
 
     @observable _animating = "";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3b4dc3741..c47edefd6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,31 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import * as fa from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit, AclAdmin } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { Document } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { listSpec } from "../../../fields/Schema";
 import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
+import { MobileInterface } from '../../../mobile/MobileInterface';
 import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
+import { emptyFunction, emptyPath, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { ClientRecommender } from '../../ClientRecommender';
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from "../../util/DocumentManager";
-import { SnappingManager } from '../../util/SnappingManager';
 import { DragManager, dropActionType } from "../../util/DragManager";
 import { InteractionUtils } from '../../util/InteractionUtils';
+import { LinkManager } from '../../util/LinkManager';
 import { Scripting } from '../../util/Scripting';
 import { SearchUtil } from '../../util/SearchUtil';
 import { SelectionManager } from "../../util/SelectionManager";
 import SharingManager from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -35,20 +35,13 @@ import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
 import { KeyphraseQueryView } from '../KeyphraseQueryView';
 import { DocumentContentsView } from "./DocumentContentsView";
+import { DocumentLinksButton } from './DocumentLinksButton';
 import "./DocumentView.scss";
 import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { RadialMenu } from './RadialMenu';
-import React = require("react");
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { MobileInterface } from '../../../mobile/MobileInterface';
 import { TaskCompletionBox } from './TaskCompletedBox';
-import { LinkDescriptionPopup } from './LinkDescriptionPopup';
-import { LinkManager } from '../../util/LinkManager';
-import CollectionMenu from '../collections/CollectionMenu';
-
-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,
-    fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion);
+import React = require("react");
 
 export type DocFocusFunc = () => boolean;
 
@@ -105,24 +98,25 @@ export interface DocumentViewProps {
 
 @observer
 export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
+    @observable _animateScalingTo = 0;
     private _downX: number = 0;
     private _downY: number = 0;
+    private _firstX: number = -1;
+    private _firstY: number = -1;
     private _lastTap: number = 0;
     private _doubleTap = false;
     private _mainCont = React.createRef<HTMLDivElement>();
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _showKPQuery: boolean = false;
     private _queries: string = "";
-    private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
     private _titleRef = React.createRef<EditableView>();
+    private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
+    private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
-    public get title() { return this.props.Document.title; }
     public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
     public get ContentDiv() { return this._mainCont.current; }
-    get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+    private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
     @computed get topMost() { return this.props.renderDepth === 0; }
     @computed get freezeDimensions() { return this.props.FreezeDimensions; }
     @computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@@ -136,9 +130,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     onClickFunc = () => this.onClickHandler;
     onDoubleClickFunc = () => this.onDoubleClickHandler;
 
-    private _firstX: number = -1;
-    private _firstY: number = -1;
-
     handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
         this.removeMoveListeners();
         this.removeEndListeners();
@@ -152,11 +143,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             this._firstX = pt.pageX;
             this._firstY = pt.pageY;
         }
-
     }
 
     handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
 
         if (this._firstX === -1 || this._firstY === -1) {
@@ -200,7 +189,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     componentDidMount() {
         this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document));
         this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
-        this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
+        this._mainCont.current && (this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
         // this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
 
         if (!this.props.dontRegisterView) {
@@ -212,13 +201,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     componentDidUpdate() {
         this._dropDisposer?.();
         this._gestureEventDisposer?.();
-        this.multiTouchDisposer?.();
-        this.holdDisposer?.();
+        this._multiTouchDisposer?.();
+        this._holdDisposer?.();
         if (this._mainCont.current) {
             this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
             this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
-            this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
-            this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+            this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+            this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
         }
     }
 
@@ -226,8 +215,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     componentWillUnmount() {
         this._dropDisposer?.();
         this._gestureEventDisposer?.();
-        this.multiTouchDisposer?.();
-        this.holdDisposer?.();
+        this._multiTouchDisposer?.();
+        this._holdDisposer?.();
         Doc.UnBrushDoc(this.props.Document);
         if (!this.props.dontRegisterView) {
             const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -242,7 +231,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
             dragData.dropAction = dropAction;
             dragData.removeDocument = this.props.removeDocument;
-            dragData.moveDocument = this.props.moveDocument;//  this.layoutDoc.onDragStart ? undefined : this.props.moveDocument;
+            dragData.moveDocument = this.props.moveDocument;
             dragData.dragDivName = this.props.dragDivName;
             dragData.treeViewDoc = this.props.treeViewDoc;
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart });
@@ -254,7 +243,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const de = new DragManager.DocumentDragData([topDoc]);
         de.dragDivName = topDocView.props.dragDivName;
         de.moveDocument = topDocView.props.moveDocument;
-        undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
         setTimeout(() => {
             const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
             if (newDocView) {
@@ -263,6 +251,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 DragManager.StartDocumentDrag([contentDiv], de, x, y, { offsetX: x - xf.left, offsetY: y - xf.top, hideSource: true });
             }
         }, 0);
+        UndoManager.RunInBatch(action(() => topDoc.z = topDoc.z ? 0 : 1), "float");
     }
 
     onKeyDown = (e: React.KeyboardEvent) => {
@@ -301,41 +290,39 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                         const func = () => this.onDoubleClickHandler.script.run({
                             this: this.layoutDoc,
                             self: this.rootDoc,
-                            thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+                            thisContainer: this.props.ContainingCollectionDoc,
+                            shiftKey: e.shiftKey
                         }, console.log);
                         func();
                     } else {
                         UndoManager.RunInBatch(() => {
+                            let fullScreenDoc = this.props.Document;
                             if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
-                                const fullScreenAlias = Doc.MakeAlias(this.props.Document);
-                                fullScreenAlias.layoutKey = "layout_fullScreen";
-                                this.props.addDocTab(fullScreenAlias, "inTab");
-                            } else {
-                                this.props.addDocTab(this.props.Document, "inTab");
+                                fullScreenDoc = Doc.MakeAlias(this.props.Document);
+                                fullScreenDoc.layoutKey = "layout_fullScreen";
                             }
+                            this.props.addDocTab(fullScreenDoc, "inTab");
                         }, "double tap");
                         SelectionManager.DeselectAll();
                         Doc.UnBrushDoc(this.props.Document);
                     }
                 }
             } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
-                //SelectionManager.DeselectAll();
                 const func = () => this.onClickHandler.script.run({
                     this: this.layoutDoc,
                     self: this.rootDoc,
-                    thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+                    thisContainer: this.props.ContainingCollectionDoc,
+                    shiftKey: e.shiftKey
                 }, console.log);
                 if (this.props.Document !== Doc.UserDoc()["dockedBtn-undo"] && this.props.Document !== Doc.UserDoc()["dockedBtn-redo"]) {
                     UndoManager.RunInBatch(func, "on click");
                 } else func();
             } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
-                const alias = Doc.MakeAlias(this.props.Document);
-                DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
-                this.props.addDocTab(alias, "onRight");
+                this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
             } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
             } else {
-                if ((this.layoutDoc.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+                if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
                     SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
@@ -407,7 +394,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
             e.preventDefault();
-
         }
     }
 
@@ -445,12 +431,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const oldPoint2 = this.prevPoints.get(pt2.identifier);
         const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
         if (pinching !== 0 && oldPoint1 && oldPoint2) {
-            // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
-            // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
-            // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
-            // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
-            // let dW = -dX;
-            // let dH = -dY;
             const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
             const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
             const dX = -1 * Math.sign(dW);
@@ -533,7 +513,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerMove = (e: PointerEvent): void => {
-
         if ((e as any).formattedHandled) { e.stopPropagation(); return; }
         if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return;
         if (e.cancelBubble && this.active) {
@@ -554,17 +533,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     onPointerUp = (e: PointerEvent): void => {
         this.cleanUpInteractions();
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
 
         if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
             this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            return;
+        } else {
+            this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+            this._lastTap = Date.now();
         }
-
-        document.removeEventListener("pointermove", this.onPointerMove);
-        document.removeEventListener("pointerup", this.onPointerUp);
-        this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
-        this._lastTap = Date.now();
     }
 
     onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
@@ -586,54 +563,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @undoBatch
-    togglePushpin = () => {
+    @undoBatch @action
+    toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
         this.Document.ignoreClick = false;
-        if (this.Document.isLinkButton || this.Document.isPushpin || this.onClickHandler || this.Document.ignoreClick) {
-            this.Document.isPushpin = this.Document.isLinkButton = false;
-            this.Document.onClick = this.layoutDoc.onClick = undefined;
+        this.Document.isLinkButton = !this.Document.isLinkButton;
+        setPushpin && (this.Document.isPushpin = this.Document.isLinkButton);
+        if (this.Document.isLinkButton && !this.onClickHandler) {
+            this.Document.followLinkZoom = zoom;
+            this.Document.followLinkLocation = location;
         } else {
-            this.Document.isPushpin = this.Document.isLinkButton = true;
-            this.Document.followLinkZoom = false;
-            this.Document.followLinkLocation = undefined;
-        }
-    }
-
-    @undoBatch
-    toggleLinkButtonBehavior = (): void => {
-        this.Document.ignoreClick = false;
-        if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) {
-            this.Document.isLinkButton = false;
             this.Document.onClick = this.layoutDoc.onClick = undefined;
-        } else {
-            this.Document.isLinkButton = true;
-            this.Document.followLinkZoom = false;
-            this.Document.followLinkLocation = undefined;
-        }
-    }
-
-    @undoBatch
-    toggleFollowInPlace = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document.isLinkButton = !this.Document.isLinkButton;
-        if (this.Document.isLinkButton) {
-            this.Document.followLinkZoom = true;
-            this.Document.followLinkLocation = "inPlace";
         }
     }
 
-    @undoBatch
-    toggleFollowOnRight = (): void => {
-        this.Document.ignoreClick = false;
-        this.Document.isLinkButton = !this.Document.isLinkButton;
-        if (this.Document.isLinkButton) {
-            this.Document.followLinkZoom = false;
-            this.Document.followLinkLocation = "onRight";
-        }
-    }
-
-    @undoBatch
-    @action
+    @undoBatch @action
     drop = async (e: Event, de: DragManager.DropEvent) => {
         if (this.props.Document === Doc.UserDoc().activeWorkspace) {
             alert("linking to document tabs not yet supported.  Drop link on document content.");
@@ -663,11 +606,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
         if (de.complete.linkDragData) {
             e.stopPropagation();
-            if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
-                const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
-                    { doc: this.props.Document }, `link`);
-                de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
-                    (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+            const linkSource = de.complete.linkDragData.linkSourceDocument;
+            if (linkSource !== this.props.Document) {
+                const linkDoc = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, `link`);
+                linkSource !== this.props.Document && (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
                 linkDoc && makeLink(linkDoc);
             }
 
@@ -680,6 +622,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.PanelWidth(), this.props.PanelHeight());
     }
 
+    @undoBatch
+    @action
+    toggleLockPosition = (): void => {
+        this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
+    }
+
     @undoBatch
     @action
     makeIntoPortal = async () => {
@@ -694,9 +642,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @undoBatch
     @action
-    toggleBackground = (temporary: boolean): void => {
-        this.Document._overflow = temporary ? "visible" : "hidden";
-        this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
+    toggleBackground = () => {
+        this.Document.isBackground = (this.Document.isBackground ? undefined : true);
+        this.Document._overflow = this.Document.isBackground ? "visible" : undefined;
         if (this.Document.isBackground) {
             this.props.bringToFront(this.props.Document, true);
             this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym]();
@@ -704,41 +652,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @undoBatch
-    @action
-    toggleLockPosition = (): void => {
-        this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
-    }
-
-    @undoBatch
-    @action
-    setAcl = (acl: SharingPermissions) => {
-        this.dataDoc.ACL = this.props.Document.ACL = acl;
-        DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
-            if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
-            const data = d[DataSym];
-            if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
-        });
-    }
-
-    @undoBatch
-    @action
-    testAcl = (acl: SharingPermissions) => {
-        this.dataDoc.author = this.props.Document.author = "ADMIN";
-        this.dataDoc.ACL = this.props.Document.ACL = acl;
-        DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
-            if (d.author === Doc.CurrentUserEmail) {
-                d.author = "ADMIN";
-                d.ACL = acl;
-            }
-            const data = d[DataSym];
-            if (data && data.author === Doc.CurrentUserEmail) {
-                data.author = "ADMIN";
-                data.ACL = acl;
-            }
-        });
-    }
-
     @action
     onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
         // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
@@ -785,10 +698,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         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" });
         onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
-        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
-        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: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: this.togglePushpin, icon: "snowflake" });
+        onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "concierge-bell" });
+        !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "concierge-bell" });
+        onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "concierge-bell" });
+        onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "snowflake" });
         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...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" });
 
@@ -802,16 +715,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
         const more = cm.findByDescription("More...");
         const moreItems = more && "subitems" in more ? more.subitems : [];
-        moreItems.push({
-            description: "Download document", icon: "download", event: async () => {
-                Doc.Zip(this.props.Document);
-                // const a = document.createElement("a");
-                // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
-                // a.href = url;
-                // a.download = `DocExport-${this.props.Document[Id]}.zip`;
-                // a.click();
-            }
-        });
+        moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) });
         if (!Doc.UserDoc().noviceMode) {
             moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
             moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -836,179 +740,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
         cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
 
-        // const existingAcls = cm.findByDescription("Privacy...");
-        // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
-        // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
-        // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
-        // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
-        // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
-        // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
-        // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
-        // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
-
-        // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
-
-        // const recommender_subitems: ContextMenuProps[] = [];
-
-        // recommender_subitems.push({
-        //     description: "Internal recommendations",
-        //     event: () => this.recommender(),
-        //     icon: "brain"
-        // });
-
-        // const ext_recommender_subitems: ContextMenuProps[] = [];
-
-        // ext_recommender_subitems.push({
-        //     description: "arXiv",
-        //     event: () => this.externalRecommendation("arxiv"),
-        //     icon: "brain"
-        // });
-        // ext_recommender_subitems.push({
-        //     description: "Bing",
-        //     event: () => this.externalRecommendation("bing"),
-        //     icon: "brain"
-        // });
-
-        // recommender_subitems.push({
-        //     description: "External recommendations",
-        //     subitems: ext_recommender_subitems,
-        //     icon: "brain"
-        // });
-
-
-        //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
-        //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
-        //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
-
-        // runInAction(() => {
-        //     const setWriteMode = (mode: DocServer.WriteMode) => {
-        //         DocServer.AclsMode = mode;
-        //         const mode1 = mode;
-        //         const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
-        //         DocServer.setFieldWriteMode("x", mode1);
-        //         DocServer.setFieldWriteMode("y", mode1);
-        //         DocServer.setFieldWriteMode("_width", mode1);
-        //         DocServer.setFieldWriteMode("_height", mode1);
-
-        //         DocServer.setFieldWriteMode("_panX", mode2);
-        //         DocServer.setFieldWriteMode("_panY", mode2);
-        //         DocServer.setFieldWriteMode("scale", mode2);
-        //         DocServer.setFieldWriteMode("_viewType", mode2);
-        //     };
-        //     const aclsMenu: ContextMenuProps[] = [];
-        //     aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
-        //     aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
-        //     aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
-        //     aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
-        //     cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
-        // });
         runInAction(() => {
             if (!this.topMost && !(e instanceof Touch)) {
-                // DocumentViews should stop propagation of this event
-                e.stopPropagation();
+                e.stopPropagation(); // DocumentViews should stop propagation of this event
             }
             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), "");
-        const item = ({
-            description: `path: ${path}`, event: () => {
-                if (this.props.LibraryPath !== emptyPath) {
-                    this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
-                    Doc.linkFollowHighlight(this.props.Document);
-                } else {
-                    Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
-                }
-            }, icon: "check"
-        });
-        //cm.addItem(item);
-    }
-
-    recommender = async () => {
-        if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
-        const documents: Doc[] = [];
-        const allDocs = await SearchUtil.GetAllDocs();
-        // clears internal representation of documents as vectors
-        ClientRecommender.Instance.reset_docs();
-        //ClientRecommender.Instance.arxivrequest("electrons");
-        await Promise.all(allDocs.map((doc: Doc) => {
-            let isMainDoc: boolean = false;
-            const dataDoc = Doc.GetProto(doc);
-            if (doc.type === DocumentType.RTF) {
-                if (dataDoc === Doc.GetProto(this.props.Document)) {
-                    isMainDoc = true;
-                }
-                if (!documents.includes(dataDoc)) {
-                    documents.push(dataDoc);
-                    const extdoc = doc.data_ext as Doc;
-                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc);
-                }
-            }
-            if (doc.type === DocumentType.IMG) {
-                if (dataDoc === Doc.GetProto(this.props.Document)) {
-                    isMainDoc = true;
-                }
-                if (!documents.includes(dataDoc)) {
-                    documents.push(dataDoc);
-                    const extdoc = doc.data_ext as Doc;
-                    return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true);
-                }
-            }
-        }));
-        const doclist = ClientRecommender.Instance.computeSimilarities("cosine");
-        const recDocs: { preview: Doc, score: number }[] = [];
-        // tslint:disable-next-line: prefer-for-of
-        for (let i = 0; i < doclist.length; i++) {
-            recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
-        }
-
-        const data = recDocs.map(unit => {
-            unit.preview.score = unit.score;
-            return unit.preview;
-        });
-
-        console.log(recDocs.map(doc => doc.score));
-
-        const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`;
-        const recommendations = Docs.Create.RecommendationsDocument(data, { title });
-        recommendations.documentIconHeight = 150;
-        recommendations.sourceDoc = this.props.Document;
-        recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
-        this.props.addDocTab(recommendations, "onRight");
-
-        // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
-    }
-
-    @action
-    externalRecommendation = async (api: string) => {
-        if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
-        ClientRecommender.Instance.reset_docs();
-        const doc = Doc.GetDataDoc(this.props.Document);
-        const extdoc = doc.data_ext as Doc;
-        const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api);
-        let recs: any;
-        let kps: any;
-        if (recs_and_kps) {
-            recs = recs_and_kps.recs;
-            kps = recs_and_kps.keyterms;
-        }
-        else {
-            console.log("recommender system failed :(");
-            return;
-        }
-        console.log("ibm keyterms: ", kps.toString());
-        const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")];
-        const bodies: Doc[] = [];
-        const titles = recs.title_vals;
-        const urls = recs.url_vals;
-        for (let i = 0; i < 5; i++) {
-            const body = Docs.Create.FreeformDocument([], { title: titles[i] });
-            body.href = urls[i];
-            bodies.push(body);
-        }
-        this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight");
-        this._showKPQuery = true;
-        this._queries = kps.toString();
     }
 
     // does Document set a layout prop
@@ -1088,7 +826,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     // used to decide whether a link anchor view should be created or not.
-    // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
+    // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
     // would be good to generalize this some way.
     isNonTemporalLink = (linkDoc: Doc) => {
         const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
@@ -1096,7 +834,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
     }
 
-
     @observable _link: Opt<Doc>;  // see DocumentButtonBar for explanation of how this works
     makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor.  This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
 
@@ -1186,7 +923,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
         }
     }
-    @observable _animateScalingTo = 0;
+
     switchViews = action((custom: boolean, view: string) => {
         this._animateScalingTo = 0.1;  // shrink doc
         setTimeout(action(() => {
@@ -1200,7 +937,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
             ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
             this.props.renderDepth > 0 && !this.props.treeViewDoc ?
-            <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+            <div className="documentView-lock" onClick={this.toggleBackground}>
                 <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
             </div>
             : (null);
@@ -1224,7 +961,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
         let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
-        return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+        const topmost = this.topMost ? "-topmost" : "";
+        return <div className={`documentView-node${topmost}`}
             id={this.props.Document[Id]}
             ref={this._mainCont} onKeyDown={this.onKeyDown}
             onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
@@ -1266,7 +1004,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this.innards}
             {this.renderLock()}
         </div>;
-        { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
     }
 }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5f689624c..d668d332b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -66,7 +66,7 @@ const uploadIcons = {
 
 @observer
 export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
-    protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+    protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
     private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
     private _dropDisposer?: DragManager.DragDropDisposer;
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index bc43cd473..1a5edc1d9 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -32,7 +32,7 @@ const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
 export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
 
     private dropDisposer?: DragManager.DragDropDisposer;
-    protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
+    protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
     private _overlayDisposer?: () => void;
     private _caretPos = 0;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 87acb2ea7..267defa4b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -587,6 +587,11 @@ export namespace Doc {
     }
 
     export async function Zip(doc: Doc) {
+        // const a = document.createElement("a");
+        // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+        // a.href = url;
+        // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+        // a.click();
         const { clone, map } = await Doc.MakeClone(doc, true);
         function replacer(key: any, value: any) {
             if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
-- 
cgit v1.2.3-70-g09d2

