From 9427474b473d70974784a1517a1be902fb8d18ee Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 2 Oct 2019 18:26:55 -0400
Subject: many fixes to pdfs, linking, annotations, presentations.

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

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9e9fe1324..fe4f75cad 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -299,7 +299,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 let rotation = NumCast(this.dataDoc.rotation) % 180;
                 let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
                 let aspect = realsize.height / realsize.width;
-                if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) {
+                if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(1 - NumCast(layoutdoc.nativeHeight) / NumCast(layoutdoc.nativeWidth) / (realsize.height / realsize.width)) > 0.1)) {
                     setTimeout(action(() => {
                         layoutdoc.height = layoutdoc[WidthSym]() * aspect;
                         layoutdoc.nativeHeight = realsize.height;
-- 
cgit v1.2.3-70-g09d2


From 456e9120857f20fb609ab13bb07cbd8a2d2f850b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 3 Oct 2019 00:25:44 -0400
Subject: cleaned up link following code.  changed opening in place behavior to
 not open if view already exists.  fixed formattedText box scrolling.  fixed
 clicking on image in text box. more...

---
 src/client/util/DocumentManager.ts          |  49 +++++----
 src/client/util/RichTextSchema.tsx          |  26 +----
 src/client/util/SearchUtil.ts               |   1 -
 src/client/views/MetadataEntryMenu.tsx      |   2 +-
 src/client/views/linking/LinkFollowBox.tsx  |  82 ++++-----------
 src/client/views/nodes/DocumentView.scss    |   1 +
 src/client/views/nodes/DocumentView.tsx     |  10 +-
 src/client/views/nodes/FormattedTextBox.tsx | 152 ++++++++++++----------------
 src/client/views/nodes/ImageBox.tsx         |   2 +-
 src/client/views/pdf/Annotation.tsx         |   2 +-
 src/new_fields/Doc.ts                       |  17 +++-
 11 files changed, 148 insertions(+), 196 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4ebcdf83c..305a77b14 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -12,6 +12,7 @@ import { undoBatch, UndoManager } from './UndoManager';
 import { Scripting } from './Scripting';
 import { List } from '../../new_fields/List';
 import { SelectionManager } from './SelectionManager';
+import { notDeepEqual } from 'assert';
 
 
 export class DocumentManager {
@@ -131,12 +132,12 @@ export class DocumentManager {
         return pairs;
     }
 
-    public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, closeContextIfNotFound: boolean = false): Promise<void> => {
+    public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false): Promise<void> => {
         const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
         const annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
         if (docView) {  // we have a docView already and aren't forced to create a new one ... just focus on the document.  TODO move into view if necessary otherwise just highlight?
             annotatedDoc && docView.props.focus(annotatedDoc, false);
-            docView.props.focus(targetDoc, willZoom);
+            docView.props.focus(docView.props.Document, willZoom);
         } else {
             const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
             const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
@@ -160,38 +161,52 @@ export class DocumentManager {
                             if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document);
                             (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target
                         }
+                        const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
+                        finalDocView && (finalDocView.Document.scrollToLinkID = linkId);
+                        finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document);
                     }, 0);
                 } else {  // there's no context view so we need to create one first and try again
                     targetDocContext.scrollY = 0;
                     (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext, undefined);
                     setTimeout(() => {
                         const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext);
-                        if (foundTargetDocContextView) { // we should always find a target context here....
-                            this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, true); // so call jump to doc again and if the doc isn't found, it will be created.
+                        if (foundTargetDocContextView) { // we might be lucky and the context loads right away
+                            this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true); // so call jump to doc again and if the doc isn't found, it will be created.
+                        } else {
+                            setTimeout(() => { // if not, wait a bit to see if the context can be loaded (e.g., a PDF).
+                                const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext);
+                                if (foundTargetDocContextView) { // now we should always find a target context here....
+                                    this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true); // so call jump to doc again and if the doc isn't found, it will be created.
+                                }
+                            }, 2000)
                         }
-                    }, 2000); // the long timeout gives the context view a chance to create its children.  think pdf's which need to be activated to render their annotations.
+                    }, 0);
                 }
             }
         }
+        const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
+        finalDocView && (finalDocView.Document.scrollToLinkID = linkId);
+        finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document);
     }
 
-    public async FollowLink(doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) {
-        let linkDocs = LinkManager.Instance.getAllRelatedLinks(doc);
+    public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) {
+        const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc);
         SelectionManager.DeselectAll();
-        let firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored);
-        let secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored);
+        const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored);
+        const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored);
         const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
         const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
-        let first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs;
-        let second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs;
-        let linkFollowDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor2 as Doc] : undefined;
-        let linkFollowDocContexts = first.length ? [await (first[0].targetContext) as Doc, await (first[0].sourceContext) as Doc] : second.length ? [await (second[0].sourceContext) as Doc, await (second[0].targetContext) as Doc] : [undefined, undefined];
-        if (linkFollowDocs && !linkFollowDocs.some(l => l instanceof Promise)) {
-            let maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab");
-            let targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
+        const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs;
+        const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs;
+        const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined;
+        const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined;
+        const linkFollowDocContexts = first.length ? [await first[0].targetContext as Doc, await first[0].sourceContext as Doc] : second.length ? [await second[0].sourceContext as Doc, await second[0].targetContext as Doc] : [undefined, undefined];
+        if (linkFollowDocs && linkDoc) {
+            const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab");
+            const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
             DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom,
                 // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
-                (doc: Doc) => focus(doc, maxLocation), targetContext);
+                (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]);
         }
     }
 
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 49bd93942..066266873 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -13,6 +13,7 @@ import { Cast, NumCast } from "../../new_fields/Types";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { times } from "async";
+import { LinkManager } from "./LinkManager";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -619,27 +620,10 @@ export class ImageResizeView {
             if (!view.isOverlay || e.ctrlKey) {
                 e.preventDefault();
                 e.stopPropagation();
-                DocServer.GetRefField(node.attrs.docid).then(async linkDoc => {
-                    const location = node.attrs.location;
-                    if (linkDoc instanceof Doc) {
-                        let proto = Doc.GetProto(linkDoc);
-                        let targetContext = await Cast(proto.targetContext, Doc);
-                        let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
-                        if (jumpToDoc) {
-                            if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-                                DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey);
-                                return;
-                            }
-                        }
-                        if (targetContext) {
-                            DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab"));
-                        } else if (jumpToDoc) {
-                            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab"));
-                        } else {
-                            DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab"));
-                        }
-                    }
-                });
+                DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
+                    (linkDoc instanceof Doc) &&
+                    DocumentManager.Instance.FollowLink(linkDoc, (view.state.schema as any).Document,
+                        document => addDocTab(document, undefined, node.attrs.location ? node.attrs.location : "inTab"), false));
             }
         };
         this._handle.onpointerdown = function (e: any) {
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index d8b9dbec6..e37f1f90d 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -3,7 +3,6 @@ import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { Utils } from '../../Utils';
-import { ResultParameters } from '../northstar/model/idea/idea';
 import { DocumentType } from '../documents/DocumentTypes';
 
 export namespace SearchUtil {
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index f1b101b8e..41453f8b2 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -3,7 +3,7 @@ import "./MetadataEntryMenu.scss";
 import { observer } from 'mobx-react';
 import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
 import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCast, DocListCastAsync } from '../../new_fields/Doc';
+import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc';
 import * as Autosuggest from 'react-autosuggest';
 import { undoBatch } from '../util/UndoManager';
 import { emptyFunction } from '../../Utils';
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 53b720a9e..2bff3ded4 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -152,21 +152,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
         this.resetPan();
     }
 
-    unhighlight = () => {
-        Doc.UnhighlightAll();
-        document.removeEventListener("pointerdown", this.unhighlight);
-    }
-
-    @action
-    highlightDoc = () => {
-        if (LinkFollowBox.destinationDoc) {
-            document.removeEventListener("pointerdown", this.unhighlight);
-            Doc.HighlightDoc(LinkFollowBox.destinationDoc);
-            window.setTimeout(() => {
-                document.addEventListener("pointerdown", this.unhighlight);
-            }, 10000);
-        }
-    }
+    highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc);
 
     @undoBatch
     openFullScreen = () => {
@@ -235,44 +221,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
 
     @undoBatch
     jumpToLink = async (options: { shouldZoom: boolean }) => {
-        if (LinkFollowBox.destinationDoc && LinkFollowBox.linkDoc) {
-            let jumpToDoc: Doc = LinkFollowBox.destinationDoc;
-            let pdfDoc = FieldValue(Cast(LinkFollowBox.destinationDoc, Doc));
-            if (pdfDoc) {
-                jumpToDoc = pdfDoc;
-            }
-            let proto = Doc.GetProto(LinkFollowBox.linkDoc);
-            let targetContext = await Cast(proto.targetContext, Doc);
-            let sourceContext = await Cast(proto.sourceContext, Doc);
-            let guid = StrCast(LinkFollowBox.linkDoc[Id]);
-            const shouldZoom = options ? options.shouldZoom : false;
-
-            let dockingFunc = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
-
-            if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, async document => dockingFunc(document), targetContext);
-            }
-            else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, document => dockingFunc(sourceContext!));
-                if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) {
-                    if (guid) {
-                        let views = DocumentManager.Instance.getDocumentViews(jumpToDoc);
-                        views.length && (views[0].props.Document.scrollToLinkID = guid);
-                    } else {
-                        jumpToDoc.linkHref = Utils.prepend("/doc/" + StrCast(LinkFollowBox.linkDoc[Id]));
-                    }
-                }
-            }
-            else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom);
-
-            }
-            else {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, dockingFunc);
-            }
+        if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) {
+            let focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+            //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation));
 
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
+            DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined);
         }
     }
 
@@ -310,20 +263,23 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     openLinkInPlace = (options: { shouldZoom: boolean }) => {
 
         if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) {
-            let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-            let y = NumCast(LinkFollowBox.sourceDoc.y);
-            let x = NumCast(LinkFollowBox.sourceDoc.x);
+            if (this.sourceView && this.sourceView.props.addDocument) {
+                let destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc);
+                if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) {
+                    let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
+                    let y = NumCast(LinkFollowBox.sourceDoc.y);
+                    let x = NumCast(LinkFollowBox.sourceDoc.x);
 
-            let width = NumCast(LinkFollowBox.sourceDoc.width);
-            let height = NumCast(LinkFollowBox.sourceDoc.height);
+                    let width = NumCast(LinkFollowBox.sourceDoc.width);
+                    let height = NumCast(LinkFollowBox.sourceDoc.height);
 
-            alias.x = x + width + 30;
-            alias.y = y;
-            alias.width = width;
-            alias.height = height;
+                    alias.x = x + width + 30;
+                    alias.y = y;
+                    alias.width = width;
+                    alias.height = height;
 
-            if (this.sourceView && this.sourceView.props.addDocument) {
-                this.sourceView.props.addDocument(alias, false);
+                    this.sourceView.props.addDocument(alias, false);
+                }
             }
 
             this.jumpToLink({ shouldZoom: options.shouldZoom });
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 4ea200e6d..b3e7898c1 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -5,6 +5,7 @@
   top: 0;
   left:0;
   border-radius: inherit;
+  transition : outline .3s linear;
                     
  // background: $light-color; //overflow: hidden;
   transform-origin: left top;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3273abc1d..67c3fe6e7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -224,7 +224,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
         }
         else if (linkDocs.length) {
-            DocumentManager.Instance.FollowLink(this.props.Document,
+            DocumentManager.Instance.FollowLink(undefined, this.props.Document,
                 (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)),
                 ctrlKey, altKey, this.props.ContainingCollectionDoc);
         }
@@ -658,6 +658,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let animheight = animDims ? animDims[1] : nativeHeight;
         let animwidth = animDims ? animDims[0] : nativeWidth;
 
+        const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
+        const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -665,10 +667,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
                     pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
                     color: StrCast(this.Document.color),
-                    outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree],
-                    outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree],
-                    outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px",
-                    border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined,
+                    outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
+                    border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
                     background: backgroundColor,
                     width: animwidth,
                     height: animheight,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 749886d9a..9347868b3 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,47 +1,46 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import _ from "lodash";
 import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { baseKeymap } from "prosemirror-commands";
 import { history } from "prosemirror-history";
+import { inputRules } from 'prosemirror-inputrules';
 import { keymap } from "prosemirror-keymap";
-import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
+import { Fragment, Mark, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
+import { ReplaceStep } from 'prosemirror-transform';
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc";
 import { Copy, Id } from '../../../new_fields/FieldSymbols';
-import { List } from '../../../new_fields/List';
 import { RichTextField } from "../../../new_fields/RichTextField";
-import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types";
+import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Utils, numberRange, timenow } from '../../../Utils';
+import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange, timenow, Utils } from '../../../Utils';
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DictationManager } from '../../util/DictationManager';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from "../../util/DragManager";
 import buildKeymap from "../../util/ProsemirrorExampleTransfer";
 import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } from "../../util/RichTextSchema";
+import { FootnoteView, ImageResizeView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema";
 import { SelectionManager } from "../../util/SelectionManager";
 import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
 import { TooltipTextMenu } from "../../util/TooltipTextMenu";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocComponent } from "../DocComponent";
+import { DocumentButtonBar } from '../DocumentButtonBar';
+import { DocumentDecorations } from '../DocumentDecorations';
 import { InkingControl } from "../InkingControl";
 import { FieldView, FieldViewProps } from "./FieldView";
 import "./FormattedTextBox.scss";
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
-import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
-import { DocumentDecorations } from '../DocumentDecorations';
-import { DictationManager } from '../../util/DictationManager';
-import { ReplaceStep } from 'prosemirror-transform';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { RichTextUtils } from '../../../new_fields/RichTextUtils';
-import _ from "lodash";
-import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
-import { inputRules } from 'prosemirror-inputrules';
-import { DocumentButtonBar } from '../DocumentButtonBar';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -142,51 +141,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
         }
         FormattedTextBox.Instance = this;
-        this._scrollToRegionReactionDisposer = reaction(
-            () => StrCast(this.props.Document.scrollToLinkID),
-            async (scrollToLinkID) => {
-                let findLinkFrag = (frag: Fragment, editor: EditorView) => {
-                    const nodes: Node[] = [];
-                    frag.forEach((node, index) => {
-                        let examinedNode = findLinkNode(node, editor);
-                        if (examinedNode && examinedNode.textContent) {
-                            nodes.push(examinedNode);
-                            start += index;
-                        }
-                    });
-                    return { frag: Fragment.fromArray(nodes), start: start };
-                };
-                let findLinkNode = (node: Node, editor: EditorView) => {
-                    if (!node.isText) {
-                        const content = findLinkFrag(node.content, editor);
-                        return node.copy(content.frag);
-                    }
-                    const marks = [...node.marks];
-                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
-                    return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
-                };
-
-                let start = -1;
-                if (this._editorView && scrollToLinkID) {
-                    let editor = this._editorView;
-                    let ret = findLinkFrag(editor.state.doc.content, editor);
-
-                    if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) {
-                        let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
-                        if (ret.frag.firstChild) {
-                            selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
-                        }
-                        editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
-                        const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
-                        setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
-                        setTimeout(() => this.unhighlightSearchTerms(), 2000);
-                    }
-                    this.props.Document.scrollToLinkID = undefined;
-                }
-
-            },
-            { fireImmediately: true }
-        );
     }
 
     public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@@ -341,8 +295,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             let url = de.data.urlField.url.href;
             let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image;
             let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
-            this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] })));
-            DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title);
+            let link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title);
+            link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: link[Id] })));
             this.tryUpdateHeight();
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
@@ -424,6 +378,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     _keymap: any = undefined;
     @computed get config() {
         this._keymap = buildKeymap(schema);
+        (schema as any).Document = this.props.Document;
         return {
             schema,
             plugins: this.props.isOverlay ? [
@@ -563,6 +518,51 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 }, 0);
             }), { fireImmediately: true }
         );
+        this._scrollToRegionReactionDisposer = reaction(
+            () => StrCast(this.props.Document.scrollToLinkID),
+            async (scrollToLinkID) => {
+                let findLinkFrag = (frag: Fragment, editor: EditorView) => {
+                    const nodes: Node[] = [];
+                    frag.forEach((node, index) => {
+                        let examinedNode = findLinkNode(node, editor);
+                        if (examinedNode && examinedNode.textContent) {
+                            nodes.push(examinedNode);
+                            start += index;
+                        }
+                    });
+                    return { frag: Fragment.fromArray(nodes), start: start };
+                };
+                let findLinkNode = (node: Node, editor: EditorView) => {
+                    if (!node.isText) {
+                        const content = findLinkFrag(node.content, editor);
+                        return node.copy(content.frag);
+                    }
+                    const marks = [...node.marks];
+                    const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
+                    return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+                };
+
+                let start = -1;
+                if (this._editorView && scrollToLinkID) {
+                    let editor = this._editorView;
+                    let ret = findLinkFrag(editor.state.doc.content, editor);
+
+                    if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) {
+                        let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
+                        if (ret.frag.firstChild) {
+                            selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+                        }
+                        editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+                        const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+                        setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
+                        setTimeout(() => this.unhighlightSearchTerms(), 2000);
+                    }
+                    this.props.Document.scrollToLinkID = undefined;
+                }
+
+            },
+            { fireImmediately: true }
+        );
 
         setTimeout(() => this.tryUpdateHeight(), 0);
     }
@@ -858,27 +858,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 if (href.indexOf(Utils.prepend("/doc/")) === 0) {
                     this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     if (this._linkClicked) {
-                        DocServer.GetRefField(this._linkClicked).then(async linkDoc => {
-                            if (linkDoc instanceof Doc) {
-                                let proto = Doc.GetProto(linkDoc);
-                                let targetContext = await Cast(proto.targetContext, Doc);
-                                let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
-
-                                if (jumpToDoc) {
-                                    if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-                                        DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey);
-                                        return;
-                                    }
-                                }
-                                if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) {
-                                    DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), targetContext);
-                                } else if (jumpToDoc) {
-                                    DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
-                                } else {
-                                    DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
-                                }
-                            }
-                        });
+                        DocServer.GetRefField(this._linkClicked).then(async linkDoc =>
+                            (linkDoc instanceof Doc) &&
+                            DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false));
                         e.stopPropagation();
                         e.preventDefault();
                     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index fe4f75cad..a198a0764 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -299,7 +299,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
                 let rotation = NumCast(this.dataDoc.rotation) % 180;
                 let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
                 let aspect = realsize.height / realsize.width;
-                if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(1 - NumCast(layoutdoc.nativeHeight) / NumCast(layoutdoc.nativeWidth) / (realsize.height / realsize.width)) > 0.1)) {
+                if (layoutdoc.width && (Math.abs(1 - NumCast(layoutdoc.height) / NumCast(layoutdoc.width) / (realsize.height / realsize.width)) > 0.1)) {
                     setTimeout(action(() => {
                         layoutdoc.height = layoutdoc[WidthSym]() * aspect;
                         layoutdoc.nativeHeight = realsize.height;
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index f7f52b3ef..98e04d93e 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -98,7 +98,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
         else if (e.button === 0) {
             let annoGroup = await Cast(this.props.document.group, Doc);
             if (annoGroup) {
-                DocumentManager.Instance.FollowLink(annoGroup,
+                DocumentManager.Instance.FollowLink(undefined, annoGroup,
                     (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"),
                     false, false, undefined);
             }
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 58304cebb..6acc6e1ca 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -637,7 +637,7 @@ export namespace Doc {
 
     export function isBrushedHighlightedDegree(doc: Doc) {
         if (Doc.IsHighlighted(doc)) {
-            return 3;
+            return 6;
         }
         else {
             return Doc.IsBrushedDegree(doc);
@@ -673,6 +673,21 @@ export namespace Doc {
         return doc;
     }
 
+    export function linkFollowUnhighlight() {
+        Doc.UnhighlightAll();
+        document.removeEventListener("pointerdown", linkFollowUnhighlight);
+    }
+
+    let dt = 0;
+    export function linkFollowHighlight(destDoc: Doc) {
+        linkFollowUnhighlight();
+        Doc.HighlightDoc(destDoc);
+        document.removeEventListener("pointerdown", linkFollowUnhighlight);
+        document.addEventListener("pointerdown", linkFollowUnhighlight);
+        let x = dt = Date.now();
+        window.setTimeout(() => dt == x && linkFollowUnhighlight(), 5000);
+    }
+
     export class HighlightBrush {
         @observable HighlightedDoc: Map<Doc, boolean> = new Map();
     }
-- 
cgit v1.2.3-70-g09d2

