From 128b8f61a8e3c7557bf7c3424a76f9c61ad6a56b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Tue, 1 Oct 2019 21:13:02 -0400
Subject: fixed issues with isMinimized and with iconBoxs being too big.

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 27b9a06f5..98730fe13 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
-    public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
 
     public getActiveDocuments = () => {
         return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
-- 
cgit v1.2.3-70-g09d2


From a19210e7db3e625c0bfe38b4f13b5312cc0c6e53 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 2 Oct 2019 09:10:58 -0400
Subject: adjusted link following to pdf annotations to work better with scroll
 position and unactivated pdf views.

---
 src/client/util/DocumentManager.ts                        | 15 ++++++++++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx         |  2 +-
 2 files changed, 13 insertions(+), 4 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index c048125c5..4e6968f08 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -11,6 +11,7 @@ import { LinkManager } from './LinkManager';
 import { undoBatch, UndoManager } from './UndoManager';
 import { Scripting } from './Scripting';
 import { List } from '../../new_fields/List';
+import { SelectionManager } from './SelectionManager';
 
 
 export class DocumentManager {
@@ -132,7 +133,7 @@ export class DocumentManager {
         let doc = Doc.GetProto(docDelegate);
         const contextDoc = await Cast(doc.annotationOn, Doc);
         if (contextDoc) {
-            contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2;
+            contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2 * 72 / 96;
         }
 
         let docView: DocumentView | null;
@@ -171,9 +172,17 @@ export class DocumentManager {
             } else {
                 let contextView: DocumentView | null;
                 Doc.BrushDoc(docDelegate);
-                if (!forceDockFunc && (contextView = DocumentManager.Instance.getDocumentView(contextDoc))) {
+                let contextViews = DocumentManager.Instance.getDocumentViews(contextDoc);
+                if (!forceDockFunc && contextViews.length) {
+                    contextView = contextViews[0];
+                    SelectionManager.SelectDoc(contextView, false); // force unrendered annotations to be created
                     contextDoc.panTransformType = "Ease";
-                    contextView.props.focus(docDelegate, willZoom);
+                    setTimeout(() => {
+                        SelectionManager.DeselectDoc(contextView!);
+                        docView = DocumentManager.Instance.getDocumentView(docDelegate);
+                        docView && contextView!.props.focus(contextView!.props.Document, willZoom);
+                        docView && UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
+                    }, 0);
                 } else {
                     (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined);
                     setTimeout(() => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 98730fe13..eb40e0bcb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -403,7 +403,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
             let annotOn = Cast(doc.annotationOn, Doc) as Doc;
-            let offset = annotOn && (NumCast(annotOn.height) / 2);
+            let offset = annotOn && (NumCast(annotOn.height) / 2 * 72 / 96);
             this.props.Document.scrollY = NumCast(doc.y) - offset;
         } else {
             const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
-- 
cgit v1.2.3-70-g09d2


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/documents/Documents.ts                  |  24 ++--
 src/client/util/DocumentManager.ts                 | 125 +++++++++++----------
 src/client/util/RichTextSchema.tsx                 |   9 +-
 src/client/util/SharingManager.tsx                 |   2 +-
 .../views/collections/CollectionBaseView.tsx       |   9 +-
 .../views/collections/CollectionDockingView.tsx    |  10 +-
 src/client/views/collections/CollectionSubView.tsx |   1 +
 .../views/collections/CollectionTreeView.tsx       |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  10 +-
 src/client/views/linking/LinkFollowBox.tsx         |  11 +-
 src/client/views/nodes/DocumentView.tsx            |  44 ++------
 src/client/views/nodes/FormattedTextBox.tsx        |  79 ++++++-------
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |  17 ++-
 src/client/views/nodes/PresBox.tsx                 |  24 ++--
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 src/client/views/pdf/Annotation.tsx                |  17 ++-
 src/client/views/pdf/PDFMenu.tsx                   |   6 +-
 src/client/views/pdf/PDFViewer.tsx                 |  72 +++++-------
 .../views/presentationview/PresElementBox.tsx      |  32 +++---
 src/new_fields/Doc.ts                              |   2 +
 21 files changed, 224 insertions(+), 276 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4d9698532..2d323ea4b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -653,32 +653,30 @@ export namespace DocUtils {
             }
         });
     }
-    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) {
-        let sv = DocumentManager.Instance.getDocumentView(source);
-        if (sv && sv.props.ContainingCollectionDoc === target) return;
-        if (target === CurrentUserUtils.UserDocument) return undefined;
+    export function MakeLink(source: {doc:Doc,ctx?:Doc}, target: {doc:Doc,ctx?:Doc}, title: string = "", description: string = "", id?: string, anchored1?: boolean) {
+        let sv = DocumentManager.Instance.getDocumentView(source.doc);
+        if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
+        if (target.doc === CurrentUserUtils.UserDocument) return undefined;
 
         let linkDocProto = new Doc(id, true);
         UndoManager.RunInBatch(() => {
             linkDocProto.type = DocumentType.LINK;
 
-            linkDocProto.targetContext = targetContext;
-            linkDocProto.sourceContext = sourceContext;
-            linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
+            linkDocProto.targetContext = target.ctx;
+            linkDocProto.sourceContext = source.ctx;
+            linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
             linkDocProto.linkDescription = description;
 
-            linkDocProto.anchor1 = source;
-            linkDocProto.anchor1Page = source.curPage;
+            linkDocProto.anchor1 = source.doc;
             linkDocProto.anchor1Groups = new List<Doc>([]);
             linkDocProto.anchor1anchored = anchored1;
-            linkDocProto.anchor2 = target;
-            linkDocProto.anchor2Page = target.curPage;
+            linkDocProto.anchor2 = target.doc;
             linkDocProto.anchor2Groups = new List<Doc>([]);
 
             LinkManager.Instance.addLink(linkDocProto);
 
-            Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)");
-            Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)");
+            Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
+            Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
         }, "make link");
         return linkDocProto;
     }
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 4e6968f08..4ebcdf83c 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
 import { action, computed, observable } from 'mobx';
 import { Doc, DocListCastAsync } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
-import { Cast, NumCast } from '../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
 import { CollectionPDFView } from '../views/collections/CollectionPDFView';
 import { CollectionVideoView } from '../views/collections/CollectionVideoView';
@@ -56,9 +56,9 @@ export class DocumentManager {
         return this.getDocumentViewsById(doc[Id]);
     }
 
-    public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null {
+    public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | undefined {
 
-        let toReturn: DocumentView | null = null;
+        let toReturn: DocumentView | undefined;
         let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
 
         for (let pass of passes) {
@@ -81,10 +81,14 @@ export class DocumentManager {
         return toReturn;
     }
 
-    public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null {
+    public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | undefined {
         return this.getDocumentViewById(toFind[Id], preferredCollection);
     }
 
+    public getFirstDocumentView(toFind: Doc): DocumentView | undefined {
+        const views = this.getDocumentViews(toFind);
+        return views.length ? views[0] : undefined;
+    }
     public getDocumentViews(toFind: Doc): DocumentView[] {
 
         let toReturn: DocumentView[] = [];
@@ -127,72 +131,70 @@ export class DocumentManager {
         return pairs;
     }
 
-
-    @undoBatch
-    public jumpToDocument = async (docDelegate: Doc, willZoom: boolean, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number, docContext?: Doc): Promise<void> => {
-        let doc = Doc.GetProto(docDelegate);
-        const contextDoc = await Cast(doc.annotationOn, Doc);
-        if (contextDoc) {
-            contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2 * 72 / 96;
-        }
-
-        let docView: DocumentView | null;
-        // using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed
-        if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
-            Doc.BrushDoc(docView.props.Document);
-            if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
-            UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
+    public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, 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);
         } else {
-            if (!contextDoc) {
-                let docs = docContext ? await DocListCastAsync(docContext.data) : undefined;
-                let found = false;
-                // bcz: this just searches within the context for the target -- perhaps it should recursively search through all children?
-                docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate));
-                if (docContext && found) {
-                    let targetContextView: DocumentView | null;
-
-                    if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) {
-                        docContext.panTransformType = "Ease";
-                        targetContextView.props.focus(docDelegate, willZoom);
-                    } else {
-                        (dockFunc || CollectionDockingView.AddRightSplit)(docContext, undefined);
-                        setTimeout(() => {
-                            let dv = DocumentManager.Instance.getDocumentView(docContext);
-                            dv && this.jumpToDocument(docDelegate, willZoom, forceDockFunc,
-                                doc => dv!.props.focus(dv!.props.Document, true, 1, () => dv!.props.addDocTab(doc, undefined, "inPlace")),
-                                linkPage);
-                        }, 1050);
-                    }
-                } else {
-                    const actualDoc = Doc.MakeAlias(docDelegate);
-                    Doc.BrushDoc(actualDoc);
-                    if (linkPage !== undefined) actualDoc.curPage = linkPage;
-                    (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc, undefined);
-                }
+            const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
+            const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
+            const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc);
+
+            if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
+                (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined);
             } else {
-                let contextView: DocumentView | null;
-                Doc.BrushDoc(docDelegate);
-                let contextViews = DocumentManager.Instance.getDocumentViews(contextDoc);
-                if (!forceDockFunc && contextViews.length) {
-                    contextView = contextViews[0];
-                    SelectionManager.SelectDoc(contextView, false); // force unrendered annotations to be created
-                    contextDoc.panTransformType = "Ease";
+                const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext);
+                if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context
+                    targetDocContext.panTransformType = "Ease";
+                    targetDocContext.scrollY = 0;
+                    targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
+
+                    // now find the target document within the context
                     setTimeout(() => {
-                        SelectionManager.DeselectDoc(contextView!);
-                        docView = DocumentManager.Instance.getDocumentView(docDelegate);
-                        docView && contextView!.props.focus(contextView!.props.Document, willZoom);
-                        docView && UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
+                        const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc);
+                        if (retryDocView) {
+                            retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context
+                        } else {
+                            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
+                        }
                     }, 0);
-                } else {
-                    (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined);
+                } 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(() => {
-                        this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
-                    }, 1000);
+                        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.
+                        }
+                    }, 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.
                 }
             }
         }
     }
 
+    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);
+        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 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;
+            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);
+        }
+    }
+
     @action
     zoomIntoScale = (docDelegate: Doc, scale: number) => {
         let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
@@ -202,8 +204,7 @@ export class DocumentManager {
     getScaleOfDocView = (docDelegate: Doc) => {
         let doc = Doc.GetProto(docDelegate);
 
-        let docView: DocumentView | null;
-        docView = DocumentManager.Instance.getDocumentView(doc);
+        const docView = DocumentManager.Instance.getDocumentView(doc);
         if (docView) {
             return docView.props.getScale();
         } else {
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 64821d8db..49bd93942 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -627,17 +627,16 @@ export class ImageResizeView {
                         let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
                         if (jumpToDoc) {
                             if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-
-                                DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
+                                DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey);
                                 return;
                             }
                         }
                         if (targetContext) {
-                            DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
+                            DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab"));
                         } else if (jumpToDoc) {
-                            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
+                            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab"));
                         } else {
-                            DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
+                            DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab"));
                         }
                     }
                 });
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index f427e40b1..1cde2aa8e 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -192,7 +192,7 @@ export default class SharingManager extends React.Component<{}> {
                 onClick={() => {
                     let context: Opt<CollectionVideoView | CollectionPDFView | CollectionView>;
                     if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) {
-                        DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, undefined, undefined, context.props.Document);
+                        DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
                     }
                 }}
                 onPointerEnter={action(() => {
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index e928887e2..38d050e5c 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -5,7 +5,7 @@ import { Doc, DocListCast } from '../../../new_fields/Doc';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
 import { listSpec } from '../../../new_fields/Schema';
-import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from '../../../new_fields/Types';
+import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from '../../../new_fields/Types';
 import { DocumentManager } from '../../util/DocumentManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { ContextMenu } from '../ContextMenu';
@@ -130,8 +130,11 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
         index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
-        PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
-            annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
+        PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => {
+            if (Doc.AreProtosEqual(annotationOn, FieldValue(Cast(this.dataDoc.extendsDoc, Doc)))) {
+                Doc.GetProto(doc).annotationOn = undefined;
+            }
+        });
 
         if (index !== -1) {
             value.splice(index, 1);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 98aff41d3..14e513157 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -574,8 +574,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc;
         if (curPres) {
             let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
-            Doc.GetProto(pinDoc).target = doc;
-            Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.target instanceof Doc) && this.target.title.toString()');
+            Doc.GetProto(pinDoc).presentationTargetDoc = doc;
+            Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
             const data = Cast(curPres.data, listSpec(Doc));
             if (data) {
                 data.push(pinDoc);
@@ -614,8 +614,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
     }
 
-    panelWidth = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth()));
-    panelHeight = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight()));
+    panelWidth = () => this._document && this._document.maxWidth ? Math.min(Math.max(NumCast(this._document.width), NumCast(this._document.nativeWidth)), this._panelWidth) : this._panelWidth;
+    panelHeight = () => this._panelHeight;
 
     nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0;
     nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0;
@@ -644,7 +644,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
         }
         return Transform.Identity();
     }
-    get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; }
+    get previewPanelCenteringOffset() { return this.nativeWidth() && !this._document!.ignoreAspect ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
 
     addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {
         SelectionManager.DeselectAll();
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 155f2718b..28d1eb384 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -31,6 +31,7 @@ export interface CollectionViewProps extends FieldViewProps {
     moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
+    VisibleHeight?: () => number;
     chromeCollapsed: boolean;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ffd1f9170..882a0f144 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -217,7 +217,7 @@ class TreeView extends React.Component<TreeViewProps> {
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc = de.data.linkSourceDocument;
             let destDoc = this.props.document;
-            DocUtils.MakeLink(sourceDoc, destDoc);
+            DocUtils.MakeLink({doc:sourceDoc}, {doc:destDoc});
             e.stopPropagation();
         }
         if (de.data instanceof DragManager.DocumentDragData) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eb40e0bcb..4bdede48a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -403,8 +403,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         if (this.props.Document.scrollHeight) {
             let annotOn = Cast(doc.annotationOn, Doc) as Doc;
-            let offset = annotOn && (NumCast(annotOn.height) / 2 * 72 / 96);
-            this.props.Document.scrollY = NumCast(doc.y) - offset;
+            if (!annotOn) {
+                this.props.focus(doc);
+            } else {
+                let contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height);
+                let offset = annotOn && (contextHgt / 2 * 96 / 72);
+                this.props.Document.scrollY = NumCast(doc.y) - offset;
+            }
         } else {
             const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
             const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
@@ -416,6 +421,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
             this.setPan(newPanX, newPanY);
             this.Document.panTransformType = "Ease";
+            Doc.BrushDoc(this.props.Document);
             this.props.focus(this.props.Document);
             willZoom && this.setScaleToZoom(doc, scale);
 
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 72fff8e53..53b720a9e 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -171,7 +171,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
     @undoBatch
     openFullScreen = () => {
         if (LinkFollowBox.destinationDoc) {
-            let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
+            let view = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
             view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
         }
     }
@@ -250,10 +250,10 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             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, false, async document => dockingFunc(document), undefined, targetContext);
+                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, async document => dockingFunc(document), targetContext);
             }
             else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
+                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, document => dockingFunc(sourceContext!));
                 if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) {
                     if (guid) {
                         let views = DocumentManager.Instance.getDocumentViews(jumpToDoc);
@@ -264,12 +264,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
                 }
             }
             else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined,
-                    NumCast((LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 ? LinkFollowBox.linkDoc.anchor2Page : LinkFollowBox.linkDoc.anchor1Page)));
+                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom);
 
             }
             else {
-                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
+                DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, dockingFunc);
             }
 
             this.highlightDoc();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a903f8fe2..3273abc1d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -206,7 +206,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
         let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
         let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
-        let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
+        let linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
         let expandedDocs: Doc[] = [];
         expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
         expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
@@ -223,28 +223,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation)));
             }
         }
-        else if (linkedDocs.length) {
-            SelectionManager.DeselectAll();
-            let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored);
-            let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored);
-            let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
-            let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
-            if (firstUnshown.length) first = [firstUnshown[0]];
-            if (secondUnshown.length) second = [secondUnshown[0]];
-            let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined;
-
-            // @TODO: shouldn't always follow target context
-            let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined];
-            let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined];
-
-            if (linkedFwdDocs && !linkedFwdDocs.some(l => l instanceof Promise)) {
-                let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
-                let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
-                DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
-                    // 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 => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)),
-                    linkedFwdPage[altKey ? 1 : 0], targetContext);
-            }
+        else if (linkDocs.length) {
+            DocumentManager.Instance.FollowLink(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);
         }
     }
 
@@ -352,15 +334,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (de.data instanceof DragManager.AnnotationDragData) {
             /// this whole section for handling PDF annotations looks weird.  Need to rethink this to make it cleaner
             e.stopPropagation();
-            let sourceDoc = de.data.annotationDocument;
-            let targetDoc = this.props.Document;
-            let annotations = await DocListCastAsync(sourceDoc.annotations);
-            sourceDoc.linkedToDoc = true;
-            de.data.targetContext = this.props.ContainingCollectionDoc;
-            targetDoc.targetContext = de.data.targetContext;
-            annotations && annotations.forEach(anno => anno.target = targetDoc);
-
-            DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionDoc, `Link from ${StrCast(sourceDoc.title)}`);
+            (de.data as any).linkedToDoc = true;
+
+            DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`);
         }
         if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
             Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document);
@@ -371,7 +347,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
             // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
             de.data.linkSourceDocument !== this.props.Document &&
-                (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc, undefined, "in-text link being created")); // TODODO this is where in text links get passed
+                (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
         }
     }
 
@@ -407,7 +383,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
             DocServer.GetRefField(portalID).then(existingPortal => {
                 let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID });
-                DocUtils.MakeLink(this.props.Document, portal, undefined, portalID);
+                DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link");
                 this.Document.isButton = true;
             });
         }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 449f56b16..749886d9a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -9,7 +9,7 @@ import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos }
 import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
 import { EditorView } from "prosemirror-view";
 import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
 import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from '../../../new_fields/List';
 import { RichTextField } from "../../../new_fields/RichTextField";
@@ -230,7 +230,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                     this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
                     DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
                     if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
-                    else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true);
+                    else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id, true);
                 });
             });
         });
@@ -342,7 +342,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             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(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]);
+            DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title);
             this.tryUpdateHeight();
             e.stopPropagation();
         } else if (de.data instanceof DragManager.DocumentDragData) {
@@ -667,55 +667,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
     handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
         let cbe = event as ClipboardEvent;
-        let docId: string;
-        let regionId: string;
-        if (!cbe.clipboardData) {
-            return false;
-        }
-        let linkId: string;
-        docId = cbe.clipboardData.getData("dash/pdfOrigin");
-        regionId = cbe.clipboardData.getData("dash/pdfRegion");
-        if (!docId || !regionId) {
-            return false;
-        }
-
-        DocServer.GetRefField(docId).then(doc => {
-            DocServer.GetRefField(regionId).then(region => {
-                if (!(doc instanceof Doc) || !(region instanceof Doc)) {
-                    return;
-                }
-
-                let annotations = DocListCast(region.annotations);
-                annotations.forEach(anno => anno.target = this.props.Document);
-                let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data");
-                let targetAnnotations = DocListCast(fieldExtDoc.annotations);
-                if (targetAnnotations) {
-                    targetAnnotations.push(region);
-                    fieldExtDoc.annotations = new List<Doc>(targetAnnotations);
-                }
+        const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
+        const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
+        if (pdfDocId && pdfRegionId) {
+            DocServer.GetRefField(pdfDocId).then(pdfDoc => {
+                DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
+                    if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+                        setTimeout(async () => {
+                            let targetAnnotations = await DocListCastAsync(Doc.fieldExtensionDoc(pdfDoc, "data").annotations);// bcz: NO... this assumes the pdf is using its 'data' field.  need to have the PDF's view handle updating its own annotations
+                            targetAnnotations && targetAnnotations.push(pdfRegion);
+                        });
 
-                let link = DocUtils.MakeLink(this.props.Document, region, doc);
-                if (link) {
-                    cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
-                    linkId = link[Id];
-                    let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title)));
-                    slice = new Slice(frag, slice.openStart, slice.openEnd);
-                    var tr = view.state.tr.replaceSelection(slice);
-                    view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
-                }
+                        let link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
+                        if (link) {
+                            cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
+                            let linkId = link[Id];
+                            let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+                            slice = new Slice(frag, slice.openStart, slice.openEnd);
+                            var tr = view.state.tr.replaceSelection(slice);
+                            view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+                        }
+                    }
+                });
             });
-        });
+            return true;
+        }
+        return false;
 
-        return true;
 
         function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
             const nodes: Node[] = [];
             frag.forEach(node => nodes.push(marker(node)));
             return Fragment.fromArray(nodes);
         }
-        function addLinkMark(node: Node, title: string) {
+        function addLinkMark(node: Node, title: string, linkId: string) {
             if (!node.isText) {
-                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title));
+                const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
                 return node.copy(content);
             }
             const marks = [...node.marks];
@@ -879,16 +866,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
 
                                 if (jumpToDoc) {
                                     if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-                                        DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
+                                        DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey);
                                         return;
                                     }
                                 }
                                 if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) {
-                                    DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), undefined, targetContext);
+                                    DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), targetContext);
                                 } else if (jumpToDoc) {
-                                    DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
+                                    DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
                                 } else {
-                                    DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
+                                    DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
                                 }
                             }
                         });
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;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 617b580dd..88cd2cdc4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -57,11 +57,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
     loaded = (nw: number, nh: number, np: number) => {
         this.dataDoc.numPages = np;
-        if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) {
-            let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
-            this.Document.nativeWidth = nw * 96 / 72;
-            this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72;
-        }
+        this.Document.nativeWidth = nw * 96 / 72;
+        this.Document.nativeHeight = nh * 96 / 72;
         !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
     }
 
@@ -177,12 +174,14 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
 
         ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
     }
+    _initialScale: number | undefined;
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
         let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive");
         let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf;
-        if (!noPdf && (this.props.isSelected() || this.props.ScreenToLocalTransform().Scale < 2.5)) this._everActive = true;
-        return (!this._everActive ?
+        if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale;
+        if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
+        return (noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
             <div className="pdfBox-title-outer" >
                 <div className={classname} >
                     <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
@@ -203,11 +202,11 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
                     renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
                     Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
-                    addDocTab={this.props.addDocTab} GoToPage={this.gotoPage}
+                    addDocTab={this.props.addDocTab} GoToPage={this.gotoPage} focus={this.props.focus}
                     pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
                     ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
                     isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
-                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this.props.ScreenToLocalTransform().Scale < 2.5 ? true : false} />
+                    fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
                 {this.settingsPanel()}
             </div>);
     }
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index f44ca99b9..180ed9032 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -43,8 +43,8 @@ export class PresBox extends React.Component<FieldViewProps> {
                 value.forEach((item, i) => {
                     if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) {
                         let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
-                        Doc.GetProto(pinDoc).target = item;
-                        Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.target instanceof Doc) && this.target.title.toString()');
+                        Doc.GetProto(pinDoc).presentationTargetDoc = item;
+                        Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
                         value.splice(i, 1, pinDoc);
                     }
                 });
@@ -124,13 +124,13 @@ export class PresBox extends React.Component<FieldViewProps> {
         this.childDocs.forEach((doc, ind) => {
             //the order of cases is aligned based on priority
             if (doc.hideTillShownButton && ind <= index) {
-                (doc.target as Doc).opacity = 1;
+                (doc.presentationTargetDoc as Doc).opacity = 1;
             }
             if (doc.hideAfterButton && ind < index) {
-                (doc.target as Doc).opacity = 0;
+                (doc.presentationTargetDoc as Doc).opacity = 0;
             }
             if (doc.fadeButton && ind < index) {
-                (doc.target as Doc).opacity = 0.5;
+                (doc.presentationTargetDoc as Doc).opacity = 0.5;
             }
         });
     }
@@ -145,13 +145,13 @@ export class PresBox extends React.Component<FieldViewProps> {
             //the order of cases is aligned based on priority
 
             if (key.hideAfterButton && ind >= index) {
-                (key.target as Doc).opacity = 1;
+                (key.presentationTargetDoc as Doc).opacity = 1;
             }
             if (key.fadeButton && ind >= index) {
-                (key.target as Doc).opacity = 1;
+                (key.presentationTargetDoc as Doc).opacity = 1;
             }
             if (key.hideTillShownButton && ind > index) {
-                (key.target as Doc).opacity = 0;
+                (key.presentationTargetDoc as Doc).opacity = 0;
             }
         });
     }
@@ -162,7 +162,7 @@ export class PresBox extends React.Component<FieldViewProps> {
      * te option open, navigates to that element.
      */
     navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
-        let fromDoc = this.childDocs[fromDocIndex].target as Doc;
+        let fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
         let docToJump = curDoc;
         let willZoom = false;
 
@@ -190,7 +190,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         //docToJump stayed same meaning, it was not in the group or was the last element in the group
         if (docToJump === curDoc) {
             //checking if curDoc has navigation open
-            let target = await curDoc.target as Doc;
+            let target = await curDoc.presentationTargetDoc as Doc;
             if (curDoc.navButton) {
                 DocumentManager.Instance.jumpToDocument(target, false);
             } else if (curDoc.showButton) {
@@ -210,8 +210,8 @@ export class PresBox extends React.Component<FieldViewProps> {
         let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
 
         //awaiting jump so that new scale can be found, since jumping is async
-        await DocumentManager.Instance.jumpToDocument(await docToJump.target as Doc, willZoom);
-        let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.target as Doc);
+        await DocumentManager.Instance.jumpToDocument(await docToJump.presentationTargetDoc as Doc, willZoom);
+        let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc);
         curDoc.viewScale = newScale;
         //saving the scale that user was on
         if (curScale !== 1) {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b7d9a1eab..573197117 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -129,7 +129,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                         width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
                     });
                     this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
-                    DocUtils.MakeLink(imageSummary, this.props.Document);
+                    DocUtils.MakeLink({doc:imageSummary}, {doc: this.props.Document}, "snapshot from " + this.props.Document.title, "video frame snapshot");
                 }
             });
         }
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 3ed85f6a5..f7f52b3ef 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,7 +1,7 @@
 import React = require("react");
 import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, WidthSym, Opt, DocListCastAsync } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
@@ -14,6 +14,7 @@ interface IAnnotationProps {
     fieldExtensionDoc: Doc;
     addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;
     pinToPres: (document: Doc) => void;
+    focus: (doc: Doc) => void;
 }
 
 export default class Annotation extends React.Component<IAnnotationProps> {
@@ -60,6 +61,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
     }
 
     componentWillUnmount() {
+        this._brushDisposer && this._brushDisposer();
         this._reactionDisposer && this._reactionDisposer();
     }
 
@@ -94,14 +96,11 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
             PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true);
         }
         else if (e.button === 0) {
-            let targetDoc = await Cast(this.props.document.target, Doc);
-            if (targetDoc) {
-                let context = await Cast(targetDoc.targetContext, Doc);
-                if (context) {
-                    DocumentManager.Instance.jumpToDocument(targetDoc, false, false,
-                        ((doc) => this.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")),
-                        undefined, undefined);
-                }
+            let annoGroup = await Cast(this.props.document.group, Doc);
+            if (annoGroup) {
+                DocumentManager.Instance.FollowLink(annoGroup,
+                    (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"),
+                    false, false, undefined);
             }
         }
     }
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 2202351ee..e62542014 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -31,7 +31,7 @@ export default class PDFMenu extends React.Component {
     @observable public Pinned: boolean = false;
 
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
-    public Highlight: (d: Doc | undefined, color: string) => void = emptyFunction;
+    public Highlight: (color: string) => void = emptyFunction;
     public Delete: () => void = emptyFunction;
     public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
     public AddTag: (key: string, value: string) => boolean = returnFalse;
@@ -156,11 +156,11 @@ export default class PDFMenu extends React.Component {
     @action
     highlightClicked = (e: React.MouseEvent) => {
         if (!this.Pinned) {
-            this.Highlight(undefined, "#f4f442");
+            this.Highlight("#f4f442");
         }
         else {
             this.Highlighting = !this.Highlighting;
-            this.Highlight(undefined, "#f4f442");
+            this.Highlight("#f4f442");
         }
     }
 
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 33226eac4..20dfc4d8c 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -10,7 +10,6 @@ import { listSpec } from "../../../new_fields/Schema";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
 import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils";
-import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -44,6 +43,7 @@ interface IViewerProps {
     select: (isCtrlPressed: boolean) => void;
     startupLive: boolean;
     renderDepth: number;
+    focus: (doc: Doc) => void;
     isSelected: () => boolean;
     loaded: (nw: number, nh: number, np: number) => void;
     active: () => boolean;
@@ -108,7 +108,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
         // file address of the pdf
         this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`)));
         runInAction(() => this._showWaiting = this._showCover = true);
-        this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1 && this.setupPdfJsViewer(), { fireImmediately: this.props.startupLive });
+        this.props.startupLive && this.setupPdfJsViewer();
+        this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => (this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(), { fireImmediately: true });
         this._reactionDisposer = reaction(
             () => this.props.Document.scrollY,
             (scrollY) => {
@@ -136,19 +137,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
         if (this.props.active() && e.clipboardData) {
             e.clipboardData.setData("text/plain", this._selectionText);
             e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]);
-            e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, "#0390fc")[Id]);
+            e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument("#0390fc")[Id]);
             e.preventDefault();
         }
     }
 
-    paste = (e: ClipboardEvent) => {
-        if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) {
-            let linkDocId = e.clipboardData.getData("dash/linkDoc");
-            linkDocId && DocServer.GetRefField(linkDocId).then(async (link) =>
-                (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), "#0390fc", false)));
-        }
-    }
-
     setSelectionText = (text: string) => this._selectionText = text;
 
     @action
@@ -194,13 +187,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             { fireImmediately: true }
         );
 
-        document.removeEventListener("copy", this.copy);
-        document.addEventListener("copy", this.copy);
-        document.addEventListener("pagesinit", action(() => {
-            this.pdfViewer.currentScaleValue = this._zoomed = 1;
-            this.gotoPage(NumCast(this.props.Document.curPage, 1));
-        }));
-        document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false));
         this.createPdfViewer();
     }
 
@@ -212,6 +198,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
             }
             return;
         }
+        document.removeEventListener("copy", this.copy);
+        document.addEventListener("copy", this.copy);
+        document.addEventListener("pagesinit", action(() => {
+            this.pdfViewer.currentScaleValue = this._zoomed = 1;
+            this.gotoPage(NumCast(this.props.Document.curPage, 1));
+        }));
+        document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false));
         var pdfLinkService = new PDFJSViewer.PDFLinkService();
         let pdfFindController = new PDFJSViewer.PDFFindController({
             linkService: pdfLinkService,
@@ -229,19 +222,18 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     @action
-    makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => {
+    makeAnnotationDocument = (color: string): Doc => {
         let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
         let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
         let annoDocs: Doc[] = [];
         let minY = Number.MAX_VALUE;
-        if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1 && !createLink) {
+        if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1) {
             let anno = this._savedAnnotations.values()[0][0];
             let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) });
             if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
             if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
             if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
             if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
-            annoDoc.target = sourceDoc;
             annoDoc.group = mainAnnoDoc;
             annoDoc.color = color;
             annoDoc.type = AnnotationTypes.Region;
@@ -258,7 +250,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
                 if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
                 if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
-                annoDoc.target = sourceDoc;
                 annoDoc.group = mainAnnoDoc;
                 annoDoc.color = color;
                 annoDoc.type = AnnotationTypes.Region;
@@ -272,9 +263,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title);
         mainAnnoDocProto.annotationOn = this.props.Document;
-        if (sourceDoc && createLink) {
-            DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`);
-        }
         this._savedAnnotations.clear();
         this.Index = -1;
         return mainAnnoDoc;
@@ -529,7 +517,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
 
         if (PDFMenu.Instance.Highlighting) {
-            this.highlight(undefined, "goldenrod");
+            this.highlight("goldenrod");
         }
         else {
             PDFMenu.Instance.StartDrag = this.startDrag;
@@ -540,9 +528,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
     }
 
     @action
-    highlight = (targetDoc: Doc | undefined, color: string) => {
+    highlight = (color: string) => {
         // creates annotation documents for current highlights
-        let annotationDoc = this.makeAnnotationDocument(targetDoc, color, false);
+        let annotationDoc = this.makeAnnotationDocument(color);
         Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
         return annotationDoc;
     }
@@ -555,20 +543,14 @@ export class PDFViewer extends React.Component<IViewerProps> {
     startDrag = (e: PointerEvent, ele: HTMLElement): void => {
         e.preventDefault();
         e.stopPropagation();
-        let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" });
-        targetDoc.targetPage = this.getPageFromScroll(this._marqueeY);
-        let annotationDoc = this.highlight(undefined, "red");
-        annotationDoc.linkedToDoc = false;
+        let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title });
+        let annotationDoc = this.highlight("red");
         let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
         DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
             handlers: {
-                dragComplete: () => {
-                    if (!annotationDoc.linkedToDoc) {
-                        let annotations = DocListCast(annotationDoc.annotations);
-                        annotations && annotations.forEach(anno => anno.target = targetDoc);
-                        DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`);
-                    }
-                }
+                dragComplete: () => !(dragData as any).linkedToDoc &&
+                    DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF")
+
             },
             hideSource: false
         });
@@ -602,6 +584,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     @action.bound
     removeDocument(doc: Doc): boolean {
+        Doc.GetProto(doc).annotationOn = undefined;
         //TODO This won't create the field if it doesn't already exist
         let targetDataDoc = this.props.fieldExtensionDoc;
         let targetField = this.props.fieldExt;
@@ -634,10 +617,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
     getCoverImage = () => {
         if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
-            setTimeout(() => {
+            setTimeout((() => {
                 this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
                 this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width;
-            }, 0);
+            }).bind(this), 0);
         }
         let nativeWidth = NumCast(this.props.Document.nativeWidth);
         let nativeHeight = NumCast(this.props.Document.nativeHeight);
@@ -659,7 +642,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     @computed get annotationLayer() {
         return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
             {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
-                <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
+                <Annotation {...this.props} focus={this.props.focus} anno={anno} key={`${anno[Id]}-annotation`} />)}
         </div>;
     }
     @computed get pdfViewerDiv() {
@@ -686,7 +669,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     setPreviewCursor={this.setPreviewCursor}
                     PanelHeight={() => NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))}
                     PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.props.Document.nativeWidth)}
-                    focus={emptyFunction}
+                    VisibleHeight={() => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96}
+                    focus={this.props.focus}
                     isSelected={this.props.isSelected}
                     select={emptyFunction}
                     active={this.active}
@@ -694,7 +678,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     whenActiveChanged={this.whenActiveChanged}
                     removeDocument={this.removeDocument}
                     moveDocument={this.moveDocument}
-                    addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
+                    addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
                     CollectionView={this.props.ContainingCollectionView}
                     ScreenToLocalTransform={this.scrollXf}
                     ruleProvider={undefined}
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index de3242d32..daf000dc7 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -63,13 +63,11 @@ export class PresElementBox extends React.Component<FieldViewProps> {
         this.hideTillShownButton = !this.hideTillShownButton;
         if (!this.hideTillShownButton) {
             if (this.myIndex >= this.currentIndex) {
-                (this.props.Document.target as Doc).opacity = 1;
+                (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
             }
         } else {
-            if (this.presentationDoc.presStatus) {
-                if (this.myIndex > this.currentIndex) {
-                    (this.props.Document.target as Doc).opacity = 0;
-                }
+            if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) {
+                (this.props.Document.presentationTargetDoc as Doc).opacity = 0;
             }
         }
     }
@@ -85,14 +83,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
         this.hideAfterButton = !this.hideAfterButton;
         if (!this.hideAfterButton) {
             if (this.myIndex <= this.currentIndex) {
-                (this.props.Document.target as Doc).opacity = 1;
+                (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
             }
         } else {
             if (this.fadeButton) this.fadeButton = false;
-            if (this.presentationDoc.presStatus) {
-                if (this.myIndex < this.currentIndex) {
-                    (this.props.Document.target as Doc).opacity = 0;
-                }
+            if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) {
+                (this.props.Document.presentationTargetDoc as Doc).opacity = 0;
             }
         }
     }
@@ -108,14 +104,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
         this.fadeButton = !this.fadeButton;
         if (!this.fadeButton) {
             if (this.myIndex <= this.currentIndex) {
-                (this.props.Document.target as Doc).opacity = 1;
+                (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
             }
         } else {
             this.hideAfterButton = false;
-            if (this.presentationDoc.presStatus) {
-                if (this.myIndex < this.currentIndex) {
-                    (this.props.Document.target as Doc).opacity = 0.5;
-                }
+            if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) {
+                (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5;
             }
         }
     }
@@ -162,7 +156,7 @@ export class PresElementBox extends React.Component<FieldViewProps> {
      * presentation element.
      */
     renderEmbeddedInline = () => {
-        if (!this.embedOpen || !(this.props.Document.target instanceof Doc)) {
+        if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) {
             return (null);
         }
 
@@ -175,8 +169,8 @@ export class PresElementBox extends React.Component<FieldViewProps> {
                 width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
             }}>
                 <CollectionSchemaPreview
-                    fitToBox={StrCast(this.props.Document.target.type).indexOf(DocumentType.COL) !== -1}
-                    Document={this.props.Document.target}
+                    fitToBox={StrCast(this.props.Document.presentationTargetDoc.type).indexOf(DocumentType.COL) !== -1}
+                    Document={this.props.Document.presentationTargetDoc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     ruleProvider={undefined}
@@ -205,7 +199,7 @@ export class PresElementBox extends React.Component<FieldViewProps> {
         let pbi = "presElementBox-interaction";
         return (
             <div className={className} key={p.Document[Id] + this.myIndex}
-                style={{ outlineWidth: Doc.IsBrushed(p.Document.target as Doc) ? `1px` : "0px", }}
+                style={{ outlineWidth: Doc.IsBrushed(p.Document.presentationTargetDoc as Doc) ? `1px` : "0px", }}
                 onClick={e => { p.focus(p.Document); e.stopPropagation(); }}>
                 {treecontainer ? (null) : <>
                     <strong className="presElementBox-name">
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index aeffc81c4..58304cebb 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -665,10 +665,12 @@ export namespace Doc {
     export function BrushDoc(doc: Doc) {
         brushManager.BrushedDoc.set(doc, true);
         brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true);
+        return doc;
     }
     export function UnBrushDoc(doc: Doc) {
         brushManager.BrushedDoc.delete(doc);
         brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc));
+        return doc;
     }
 
     export class HighlightBrush {
-- 
cgit v1.2.3-70-g09d2


From 3ba540a0aecc624f9c81f32102441d6d137ade85 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 5 Oct 2019 10:35:33 -0400
Subject: fixed adding to presentation box when minimized

---
 src/client/views/OverlayView.tsx                              | 11 ++++++-----
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx |  4 ++--
 src/client/views/nodes/DocumentView.tsx                       |  5 +++--
 src/client/views/nodes/PresBox.tsx                            |  9 ++++-----
 4 files changed, 15 insertions(+), 14 deletions(-)

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

diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index bb6dd5076..95c7b2683 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -145,6 +145,7 @@ export class OverlayView extends React.Component {
             return (null);
         }
         return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
+            d.inOverlay = true;
             let offsetx = 0, offsety = 0;
             let onPointerMove = action((e: PointerEvent) => {
                 if (e.buttons === 1) {
@@ -170,14 +171,14 @@ export class OverlayView extends React.Component {
                 document.addEventListener("pointerup", onPointerUp);
             };
             return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
-                <DocumentContentsView
+                <DocumentView
                     Document={d}
                     ChromeHeight={returnZero}
-                    isSelected={returnFalse}
-                    select={emptyFunction}
-                    ruleProvider={undefined}
-                    layoutKey={"layout"}
+                    // isSelected={returnFalse}
+                    // select={emptyFunction}
+                    // layoutKey={"layout"}
                     bringToFront={emptyFunction}
+                    ruleProvider={undefined}
                     addDocument={undefined}
                     removeDocument={undefined}
                     ContentScaling={returnOne}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4bdede48a..7b29f2c2a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -338,7 +338,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.props.Document.lockedPosition || this.isAnnotationOverlay) return;
+        if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return;
         if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
             e.stopPropagation();
         }
@@ -360,7 +360,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     setPan(panX: number, panY: number) {
-        if (!this.props.Document.lockedPosition) {
+        if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) {
             this.props.Document.panTransformType = "None";
             var scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 54fafc20b..e50008fdf 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -111,6 +111,7 @@ export const documentSchema = createSchema({
     type: "string",             // enumerated type of document
     maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) 
     lockedPosition: "boolean",  // whether the document can be spatially manipulated
+    inOverlay: "boolean",       // whether the document is rendered in an OverlayView which handles selection/dragging differently
     borderRounding: "string",   // border radius rounding of document
     searchFields: "string",     // the search fields to display when this document matches a search in its metadata
     heading: "number",          // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
@@ -243,7 +244,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 this._hitTemplateDrag = true;
             }
         }
-        if (this.active && e.button === 0 && !this.Document.lockedPosition) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+        if (this.active && e.button === 0 && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         document.addEventListener("pointermove", this.onPointerMove);
@@ -253,7 +254,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition) {
+        else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition && !this.Document.inOverlay) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 if (!e.altKey && !this.topMost && e.buttons === 1) {
                     document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 180ed9032..15fafb022 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -314,12 +314,11 @@ export class PresBox extends React.Component<FieldViewProps> {
     }
 
     toggleMinimize = undoBatch(action((e: React.PointerEvent) => {
-        if (this.props.Document.minimizedView) {
-            this.props.Document.minimizedView = false;
+        if (this.props.Document.inOverlay) {
             Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document);
             CollectionDockingView.AddRightSplit(this.props.Document, this.props.DataDoc);
+            this.props.Document.inOverlay = false;
         } else {
-            this.props.Document.minimizedView = true;
             this.props.Document.x = e.clientX + 25;
             this.props.Document.y = e.clientY - 25;
             this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close");
@@ -370,9 +369,9 @@ export class PresBox extends React.Component<FieldViewProps> {
                         <FontAwesomeIcon icon={this.props.Document.presStatus ? "stop" : "play"} />
                     </button>
                     <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
-                    <button className="presBox-button" title={this.props.Document.minimizedView ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button>
+                    <button className="presBox-button" title={this.props.Document.inOverlay ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button>
                 </div>
-                {this.props.Document.minimizedView ? (null) :
+                {this.props.Document.inOverlay ? (null) :
                     <div className="presBox-listCont" >
                         <CollectionView {...this.props} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
                     </div>
-- 
cgit v1.2.3-70-g09d2


From f9916faa215297d434aab2357b98d2c4b1fdcb92 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 5 Oct 2019 13:02:43 -0400
Subject: started cleaning up videos.  restored link follwing to timecodes. 
 changed to currentTimecode from curPage

---
 src/client/documents/Documents.ts                  | 11 +++--
 src/client/util/DictationManager.ts                |  1 -
 src/client/util/DocumentManager.ts                 |  7 ++-
 src/client/views/InkingCanvas.tsx                  |  6 +--
 .../views/collections/CollectionBaseView.tsx       |  4 +-
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionVideoView.tsx      |  6 +--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  2 +-
 src/client/views/linking/LinkFollowBox.tsx         |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  6 +--
 src/client/views/nodes/VideoBox.tsx                | 57 ++++++++++++----------
 src/new_fields/InkField.ts                         |  2 +-
 13 files changed, 58 insertions(+), 50 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 71b9038d4..98f56af01 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -74,6 +74,7 @@ export interface DocumentOptions {
     backgroundLayout?: string;
     chromeStatus?: string;
     curPage?: number;
+    currentTimecode?: number;
     documentText?: string;
     borderRounding?: string;
     schemaColumns?: List<SchemaHeaderField>;
@@ -120,7 +121,7 @@ export namespace Docs {
             }],
             [DocumentType.IMG, {
                 layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
-                options: { curPage: 0 }
+                options: {}
             }],
             [DocumentType.WEB, {
                 layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
@@ -136,7 +137,7 @@ export namespace Docs {
             }],
             [DocumentType.VID, {
                 layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType },
-                options: { curPage: 0 },
+                options: { currentTimecode: 0 },
             }],
             [DocumentType.AUDIO, {
                 layout: { view: AudioBox },
@@ -662,15 +663,17 @@ export namespace DocUtils {
         UndoManager.RunInBatch(() => {
             linkDocProto.type = DocumentType.LINK;
 
-            linkDocProto.targetContext = target.ctx;
-            linkDocProto.sourceContext = source.ctx;
             linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
             linkDocProto.linkDescription = description;
 
             linkDocProto.anchor1 = source.doc;
+            linkDocProto.anchor1Context = source.ctx;
+            linkDocProto.anchor1Timecode = source.doc.currentTimecode;
             linkDocProto.anchor1Groups = new List<Doc>([]);
             linkDocProto.anchor2 = target.doc;
+            linkDocProto.anchor2Context = target.ctx;
             linkDocProto.anchor2Groups = new List<Doc>([]);
+            linkDocProto.anchor2Timecode = target.doc.currentTimecode;
 
             LinkManager.Instance.addLink(linkDocProto);
 
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index cebb56bbe..3b2307073 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -336,7 +336,6 @@ export namespace DictationManager {
                     let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
                     newBox.autoHeight = true;
                     let proto = newBox.proto!;
-                    proto.page = -1;
                     let prompt = "Press alt + r to start dictating here...";
                     let head = 3;
                     let anchor = head + prompt.length;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 6fe97edbb..83a69465f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,4 +1,4 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, trace } from 'mobx';
 import { Doc, DocListCastAsync } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { Cast, NumCast, StrCast } from '../../new_fields/Types';
@@ -196,10 +196,13 @@ export class DocumentManager {
         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];
+        const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined];
+        const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [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;
+            const target = linkFollowDocs[reverse ? 1 : 0];
+            target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
             DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]);
         }
     }
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 1cfa8d644..9ab320eab 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -87,7 +87,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
                 color: InkingControl.Instance.selectedColor,
                 width: InkingControl.Instance.selectedWidth,
                 tool: InkingControl.Instance.selectedTool,
-                page: NumCast(this.props.Document.curPage, -1)
+                displayTimecode: NumCast(this.props.Document.currentTimecode, -1)
             });
             this.inkData = data;
         }
@@ -150,9 +150,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
 
     @computed
     get drawnPaths() {
-        let curPage = NumCast(this.props.Document.curPage, -1);
+        let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
         let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
-            if (strokeData.page === -1 || (Math.abs(Math.round(strokeData.page) - Math.round(curPage)) < 3)) {
+            if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
                 paths.push(<InkingStroke key={id} id={id}
                     line={strokeData.pathData}
                     count={strokeData.pathData.length}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 38d050e5c..62be1fc31 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -101,8 +101,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
 
     @action.bound
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
-        var curPage = NumCast(this.props.Document.curPage, -1);
-        Doc.GetProto(doc).page = curPage;
+        var curTime = NumCast(this.props.Document.currentTimecode, -1);
+        curTime !== -1 && (doc.displayTimecode = curTime);
         if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 52a41fc84..1ba35a52b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -51,7 +51,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
     ["title", ColumnType.String],
     ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number],
     ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
-    ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number]
+    ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
 ]);
 
 @observer
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 5185d9d0e..3d898b7de 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -21,7 +21,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     }
     private get uIButtons() {
         let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
-        let curTime = NumCast(this.props.Document.curPage);
+        let curTime = NumCast(this.props.Document.currentTimecode);
         return ([<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
             <span>{"" + Math.round(curTime)}</span>
             <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
@@ -85,7 +85,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
     onPointerMove = (e: PointerEvent) => {
         this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY);
         if (this._videoBox) {
-            this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.curPage, 0) + Math.sign(e.movementX) * 0.0333));
+            this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
         }
         e.stopImmediatePropagation();
     }
@@ -94,7 +94,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
         document.removeEventListener("pointermove", this.onPointerMove, true);
         document.removeEventListener("pointerup", this.onPointerUp, true);
         InkingControl.Instance.switchTool(InkTool.None);
-        this._isclick < 10 && (this.props.Document.curPage = 0);
+        this._isclick < 10 && (this.props.Document.currentTimecode = 0);
     }
     setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 7b29f2c2a..bfd3e6481 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
     public getActiveDocuments = () => {
         return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bf154d37d..eaf65b88c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -285,7 +285,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                     this.props.removeDocument(d);
                     d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                     d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
-                    d.page = -1;
+                    d.displayTimecode = undefined;
                     return d;
                 });
             }
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 2bff3ded4..b18aa5d63 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -128,7 +128,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
             runInAction(async () => {
                 this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
                 this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
-                let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc;
+                let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc;
                 runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
             });
         }
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 88cd2cdc4..1f3887608 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -65,9 +65,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
     public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); }
     public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); }
-    public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); }
+    public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
     public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
-    public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); }
+    public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
 
     @undoBatch
     @action
@@ -128,7 +128,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
                     <div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
                         <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div>
                 </button>
-                <input value={`${NumCast(this.props.Document.curPage)}`}
+                <input value={`${(this.Document.curPage || 1)}`}
                     onChange={e => this.gotoPage(Number(e.currentTarget.value))}
                     style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
                     onClick={action(() => this._pageControls = !this._pageControls)} />
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 573197117..e83aa8bea 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,7 +3,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
 import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface } from "../../../new_fields/Schema";
+import { makeInterface, createSchema } from "../../../new_fields/Schema";
 import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
 import { VideoField } from "../../../new_fields/URLField";
 import { RouteStore } from "../../../server/RouteStore";
@@ -16,16 +16,19 @@ import { DocumentDecorations } from "../DocumentDecorations";
 import { InkingControl } from "../InkingControl";
 import { documentSchema } from "./DocumentView";
 import { FieldView, FieldViewProps } from './FieldView';
-import { pageSchema } from "./ImageBox";
 import "./VideoBox.scss";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faVideo } from "@fortawesome/free-solid-svg-icons";
 import { Doc } from "../../../new_fields/Doc";
 import { ScriptField } from "../../../new_fields/ScriptField";
+import { positionSchema } from "./CollectionFreeFormDocumentView";
 var path = require('path');
 
-type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>;
-const VideoDocument = makeInterface(documentSchema, pageSchema);
+export const timeSchema = createSchema({
+    currentTimecode: "number",
+});
+type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
+const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
 
 library.add(faVideo);
 
@@ -97,11 +100,11 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
     }
 
     @action public Snapshot() {
-        let width = NumCast(this.props.Document.width);
-        let height = NumCast(this.props.Document.height);
+        let width = this.Document.width || 0;
+        let height = this.Document.height || 0;
         var canvas = document.createElement('canvas');
         canvas.width = 640;
-        canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+        canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
         var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
         if (ctx) {
             ctx.rect(0, 0, canvas.width, canvas.height);
@@ -112,24 +115,25 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
         if (!this._videoRef) { // can't find a way to take snapshots of videos
             let b = Docs.Create.ButtonDocument({
-                x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
-                width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString()
+                x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+                width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString()
             });
-            b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`);
+            b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
         } else {
             //convert to desired file format
             var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
-            let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
-            VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+            let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString()));
+            VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => {
                 if (returnedFilename) {
                     let url = this.choosePath(Utils.prepend(returnedFilename));
                     let imageSummary = Docs.Create.ImageDocument(url, {
-                        x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
-                        width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+                        x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+                        width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
                     });
+                    imageSummary.isButton = true;
                     this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
-                    DocUtils.MakeLink({doc:imageSummary}, {doc: this.props.Document}, "snapshot from " + this.props.Document.title, "video frame snapshot");
+                    DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot");
                 }
             });
         }
@@ -137,8 +141,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     @action
     updateTimecode = () => {
-        this.player && (this.props.Document.curPage = this.player.currentTime);
-        this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime());
+        this.player && (this.Document.currentTimecode = this.player.currentTime);
+        this._youtubePlayer && (this.Document.currentTimecode = this._youtubePlayer.getCurrentTime());
     }
 
     componentDidMount() {
@@ -146,12 +150,12 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
         if (this.youtubeVideoId) {
             let youtubeaspect = 400 / 315;
-            var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
-            var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+            var nativeWidth = (this.Document.nativeWidth || 0);
+            var nativeHeight = (this.Document.nativeHeight || 0);
             if (!nativeWidth || !nativeHeight) {
                 if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
                 this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect;
-                this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect;
+                this.Document.height = (this.Document.width || 0) / youtubeaspect;
             }
         }
     }
@@ -168,10 +172,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         if (vref) {
             this._videoRef!.ontimeupdate = this.updateTimecode;
             vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
-            if (this._reactionDisposer) this._reactionDisposer();
-            this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
-                !this.Playing && (vref.currentTime = this.Document.curPage || 0)
-                , { fireImmediately: true });
+            this._reactionDisposer && this._reactionDisposer();
+            this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
+                time => !this.Playing && (vref.currentTime = time), { fireImmediately: true });
         }
     }
 
@@ -242,7 +245,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         let onYoutubePlayerReady = (event: any) => {
             this._reactionDisposer && this._reactionDisposer();
             this._youtubeReactionDisposer && this._youtubeReactionDisposer();
-            this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0));
+            this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0));
             this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
                 let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
                 iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -263,9 +266,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
         this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
         this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
         let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
-        let start = untracked(() => Math.round(NumCast(this.props.Document.curPage)));
+        let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
         return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
-            onLoad={this.youtubeIframeLoaded} className={`${style}`} width={NumCast(this.props.Document.nativeWidth, 640)} height={NumCast(this.props.Document.nativeHeight, 390)}
+            onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
             src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
         ></iframe>;
     }
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 8f64c1c2e..e381d0218 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -16,7 +16,7 @@ export interface StrokeData {
     color: string;
     width: string;
     tool: InkTool;
-    page: number;
+    displayTimecode: number;
 }
 
 export type InkData = Map<string, StrokeData>;
-- 
cgit v1.2.3-70-g09d2


From c4b4b8f8961bfd6deddd9da7e6a02990ad526c2f Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 7 Oct 2019 01:27:52 -0400
Subject: small fixes to link clicking in text box.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  1 +
 src/client/views/nodes/FormattedTextBox.tsx        | 28 +++++++---------------
 2 files changed, 9 insertions(+), 20 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bfd3e6481..38488f033 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,6 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        if (e.nativeEvent.cancelBubble) return;
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index bdb7c2941..c87185a3e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -18,7 +18,7 @@ import { RichTextField } from "../../../new_fields/RichTextField";
 import { RichTextUtils } from '../../../new_fields/RichTextUtils';
 import { createSchema, makeInterface } from "../../../new_fields/Schema";
 import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange, timenow, Utils } from '../../../Utils';
+import { numberRange, timenow, Utils, emptyFunction } from '../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from "../../DocServer";
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -76,7 +76,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     private _proseRef?: HTMLDivElement;
     private _editorView: Opt<EditorView>;
     private _applyingChange: boolean = false;
-    private _linkClicked = "";
     private _nodeClicked: any;
     private _undoTyping?: UndoManager.Batch;
     private _searchReactionDisposer?: Lambda;
@@ -117,7 +116,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     public static getToolTip(ev: EditorView) {
-        return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev, undefined);
+        return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev);
     }
 
     @undoBatch
@@ -750,7 +749,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._searchReactionDisposer && this._searchReactionDisposer();
         this._editorView && this._editorView.destroy();
     }
-    public static firstTarget: () => void;
+    public static firstTarget: () => void = emptyFunction;
     onPointerDown = (e: React.PointerEvent): void => {
         if ((e.nativeEvent as any).formattedHandled) return;
         (e.nativeEvent as any).formattedHandled = true;
@@ -800,16 +799,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onClick = (e: React.MouseEvent): void => {
-        let ctrlKey = e.ctrlKey;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
             let location: string;
             if ((e.target as any).attributes.location) {
                 location = (e.target as any).attributes.location.value;
             }
-            for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
-                href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
-            }
             let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
             let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
             if (node) {
@@ -821,18 +816,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
             if (href) {
                 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 =>
+                    let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                    if (linkClicked) {
+                        DocServer.GetRefField(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();
+                                DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false);
+                        });
                     }
                 } else {
                     let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) });
                     this.props.addDocument && this.props.addDocument(webDoc);
-                    this._linkClicked = webDoc[Id];
                 }
                 e.stopPropagation();
                 e.preventDefault();
@@ -862,11 +855,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
         }
         this._editorView!.focus();
-        if (this._linkClicked) {
-            this._linkClicked = "";
-            e.preventDefault();
-            e.stopPropagation();
-        }
     }
     onMouseDown = (e: React.MouseEvent): void => {
         if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
-- 
cgit v1.2.3-70-g09d2


From c7746aea51bd6891ac10f152bd8f09e5d8ddcfd9 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 7 Oct 2019 08:16:34 -0400
Subject: initial tests

---
 src/client/util/RichTextSchema.tsx                                 | 7 +++++++
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx      | 2 +-
 src/client/views/nodes/DocumentView.tsx                            | 3 +++
 src/client/views/nodes/FormattedTextBox.tsx                        | 4 +++-
 4 files changed, 14 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 1109fd292..f5749a01b 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -770,8 +770,15 @@ export class DashDocView {
         let self = this;
         this._dashSpan.onclick = function (e: any) {
             FormattedTextBox.firstTarget && FormattedTextBox.firstTarget();
+        };
+        this._dashSpan.onpointermove = function (e: any) {
+            (e as any).formattedHandled = true;
+        };
+        this._dashSpan.onpointerup = function (e: any) {
             e.stopPropagation();
         };
+        this._dashSpan.onpointerdown = function (e: any) {
+        };
         this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 38488f033..cf76945e0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.nativeEvent.cancelBubble) return;
+        if (e.nativeEvent.cancelBubble || (e.nativeEvent as any).formattedHandled) return;
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e50008fdf..5a9202d40 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -202,6 +202,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             preventDefault && e.preventDefault();
         }
+        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
     }
 
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
@@ -249,8 +250,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         document.removeEventListener("pointerup", this.onPointerUp);
         document.addEventListener("pointermove", this.onPointerMove);
         document.addEventListener("pointerup", this.onPointerUp);
+        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
     }
     onPointerMove = (e: PointerEvent): void => {
+        if ((e as any).formattedHandled) { e.stopPropagation(); return; }
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c87185a3e..9396e3ea1 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -751,7 +751,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
     public static firstTarget: () => void = emptyFunction;
     onPointerDown = (e: React.PointerEvent): void => {
-        if ((e.nativeEvent as any).formattedHandled) return;
+        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
         (e.nativeEvent as any).formattedHandled = true;
         let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
         pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
@@ -799,6 +799,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
     }
 
     onClick = (e: React.MouseEvent): void => {
+        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
+        (e.nativeEvent as any).formattedHandled = true;
         if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
             let href = (e.target as any).href;
             let location: string;
-- 
cgit v1.2.3-70-g09d2


From 8c9d58c506d986d5dbdd087b429a449c8283ac12 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 7 Oct 2019 12:59:41 -0400
Subject: many changes to support nested documents within a  text box

---
 src/client/util/RichTextSchema.tsx                 | 86 ++++++++++++----------
 src/client/util/TooltipTextMenu.tsx                |  9 ++-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 src/client/views/nodes/FormattedTextBox.tsx        | 39 +++++-----
 5 files changed, 73 insertions(+), 66 deletions(-)

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

diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f5749a01b..29ec5b23c 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,24 +1,23 @@
+import { action, observable, runInAction, reaction, IReactionDisposer } from "mobx";
 import { baseKeymap, toggleMark } from "prosemirror-commands";
 import { redo, undo } from "prosemirror-history";
 import { keymap } from "prosemirror-keymap";
 import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
 import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, TextSelection, NodeSelection } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
 import { StepMap } from "prosemirror-transform";
 import { EditorView } from "prosemirror-view";
-import { Doc } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import * as ReactDOM from 'react-dom';
+import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
+import { DocumentView } from "../views/nodes/DocumentView";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
-import React = require("react");
-import { action, Lambda, observable, reaction, computed, runInAction, trace } from "mobx";
-import { observer } from "mobx-react";
-import * as ReactDOM from 'react-dom';
-import { DocumentView } from "../views/nodes/DocumentView";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils";
 import { Transform } from "./Transform";
-import { NumCast } from "../../new_fields/Types";
+import React = require("react");
+import { BoolCast } from "../../new_fields/Types";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 
 const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -716,7 +715,16 @@ export class DashDocView {
     _handle: HTMLElement;
     _dashSpan: HTMLDivElement;
     _outer: HTMLElement;
-    constructor(node: any, view: any, getPos: any, addDocTab: any) {
+    _dashDoc: Doc | undefined;
+    _reactionDisposer: IReactionDisposer | undefined;
+    _textBox: FormattedTextBox;
+
+    getDocTransform = () => {
+        let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
+        return new Transform(-translateX, -translateY, 1).scale(1 / scale);
+    }
+    constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+        this._textBox = tbox;
         this._handle = document.createElement("span");
         this._dashSpan = document.createElement("div");
         this._outer = document.createElement("span");
@@ -741,19 +749,28 @@ export class DashDocView {
         this._handle.style.right = "-10px";
         DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
             if (dashDoc instanceof Doc) {
-                let scale = () => 100 / NumCast(dashDoc.nativeWidth, 100);
+                self._dashDoc = dashDoc;
+                if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") {
+                    view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+                }
+                this._reactionDisposer && this._reactionDisposer();
+                this._reactionDisposer = reaction(() => {
+                    return dashDoc[HeightSym]();
+                }, () => {
+                    this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px";
+                });
                 ReactDOM.render(<DocumentView
-                    fitToBox={true}
+                    fitToBox={BoolCast(dashDoc.fitToBox)}
                     Document={dashDoc}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
                     ruleProvider={undefined}
-                    ScreenToLocalTransform={Transform.Identity}
-                    addDocTab={returnFalse}
+                    ScreenToLocalTransform={this.getDocTransform}
+                    addDocTab={self._textBox.props.addDocTab}
                     pinToPres={returnFalse}
                     renderDepth={1}
-                    PanelWidth={() => 100}
-                    PanelHeight={() => 100}
+                    PanelWidth={self._dashDoc![WidthSym]}
+                    PanelHeight={self._dashDoc![HeightSym]}
                     focus={emptyFunction}
                     backgroundColor={returnEmptyString}
                     parentActive={returnFalse}
@@ -763,24 +780,16 @@ export class DashDocView {
                     getScale={returnOne}
                     ContainingCollectionView={undefined}
                     ContainingCollectionDoc={undefined}
-                    ContentScaling={scale}
-                ></DocumentView>, this._dashSpan);
+                    ContentScaling={returnOne}
+                />, this._dashSpan);
             }
         });
         let self = this;
-        this._dashSpan.onclick = function (e: any) {
-            FormattedTextBox.firstTarget && FormattedTextBox.firstTarget();
-        };
-        this._dashSpan.onpointermove = function (e: any) {
-            (e as any).formattedHandled = true;
-        };
-        this._dashSpan.onpointerup = function (e: any) {
-            e.stopPropagation();
-        };
-        this._dashSpan.onpointerdown = function (e: any) {
-        };
+        this._outer.onpointerenter = function (e: any) { self.selectNode(); };
+        this._outer.onpointerleave = function (e: any) { self.deselectNode(); };
         this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
         this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+        this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
         this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
         this._handle.onpointerdown = function (e: any) {
             e.preventDefault();
@@ -789,19 +798,20 @@ export class DashDocView {
             const startY = e.pageY;
             const startWidth = parseFloat(node.attrs.width);
             const startHeight = parseFloat(node.attrs.height);
-            const onpointermove = (e: any) => {
-                const diffInPx = e.pageX - startX;
-                const diffInPy = e.pageY - startY;
-                self._outer.style.width = `${startWidth + diffInPx}`;
-                self._outer.style.height = `${startHeight + diffInPy}`;
-            };
+            const onpointermove = action((e: any) => {
+                let { scale } = Utils.GetScreenTransform(self._handle as HTMLElement);
+                const diffInPx = (e.pageX - startX) / scale;
+                const diffInPy = (e.pageY - startY) / scale;
+                self._dashDoc!.width = startWidth + diffInPx;
+                self._dashDoc!.height = startHeight + diffInPy;
+                self._outer.style.width = `${self._dashDoc!.width}`;
+                self._outer.style.height = `${self._dashDoc!.height}`;
+            });
 
             const onpointerup = () => {
                 document.removeEventListener("pointermove", onpointermove);
                 document.removeEventListener("pointerup", onpointerup);
-                let pos = view.state.selection.from;
                 view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
-                view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
             };
 
             document.addEventListener("pointermove", onpointermove);
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 9116ef995..55c6e6609 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -186,7 +186,7 @@ export class TooltipTextMenu {
 
         this.updateListItemDropdown(":", this.listTypeBtnDom);
 
-        this.update(view, undefined, undefined);
+        this.updateInternal(view, undefined, undefined);
         TooltipTextMenu.Toolbar = this.wrapper;
     }
     public static Toolbar: HTMLDivElement | undefined;
@@ -439,7 +439,7 @@ export class TooltipTextMenu {
         let tr = state.tr;
         tr.addMark(state.selection.from, state.selection.to, mark);
         let content = tr.selection.content();
-        let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+        let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
         dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
         return true;
     }
@@ -587,7 +587,7 @@ export class TooltipTextMenu {
             class: "summarize",
             execEvent: "",
             run: (state, dispatch) => {
-                TooltipTextMenu.insertStar(state, dispatch);
+                TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
             }
 
         });
@@ -849,8 +849,9 @@ export class TooltipTextMenu {
         }
     }
 
+    update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) }
     //updates the tooltip menu when the selection changes
-    update(view: EditorView, lastState: EditorState | undefined, props: any) {
+    private updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) {
         this.view = view;
         let state = view.state;
         props && (this.editorProps = props);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cf76945e0..38488f033 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.nativeEvent.cancelBubble || (e.nativeEvent as any).formattedHandled) return;
+        if (e.nativeEvent.cancelBubble) return;
         this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
             document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5a9202d40..a670d280d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -202,7 +202,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             }
             preventDefault && e.preventDefault();
         }
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
     }
 
     buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
@@ -234,7 +233,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        if (e.nativeEvent.cancelBubble) return;
+        if (e.nativeEvent.cancelBubble && e.button === 0) return;
         this._downX = e.clientX;
         this._downY = e.clientY;
         this._hitTemplateDrag = false;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9396e3ea1..c030d4bce 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -268,9 +268,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image;
                 node = model.create({ src: url, docid: link[Id] });
             } else {
+                let alias = Doc.MakeAlias(target);
+                alias.fitToBox = true;
                 node = schema.nodes.dashDoc.create({
                     width: target[WidthSym](), height: target[HeightSym](),
-                    title: "dashDoc", docid: target[Id],
+                    title: "dashDoc", docid: alias[Id],
                     float: "none"
                 });
             }
@@ -700,7 +702,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 },
                 dispatchTransaction: this.dispatchTransaction,
                 nodeViews: {
-                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self.props.addDocTab); },
+                    dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
                     image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
                     star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
                     ordered_list(node, view, getPos) { return new OrderedListView(); },
@@ -749,10 +751,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         this._searchReactionDisposer && this._searchReactionDisposer();
         this._editorView && this._editorView.destroy();
     }
-    public static firstTarget: () => void = emptyFunction;
     onPointerDown = (e: React.PointerEvent): void => {
-        if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
-        (e.nativeEvent as any).formattedHandled = true;
         let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
         pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
         if (this.props.onClick && e.button === 0) {
@@ -764,16 +763,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
             e.preventDefault();
         }
-        FormattedTextBox.firstTarget = () => {  // this is here to support nested text boxes.  when that happens, the click event will propagate through prosemirror to the outer editor.  In RichTextSchema, the outer editor calls this function to revert the focus/selection
-            if (pos && pos.pos > 0) {
-                let node = this._editorView!.state.doc.nodeAt(pos.pos);
-                if (!node || (node.type !== this._editorView!.state.schema.nodes.dashDoc && node.type !== this._editorView!.state.schema.nodes.image &&
-                    pos.pos !== this._editorView!.state.selection.from)) {
-                    this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new TextSelection(this._editorView!.state.doc.resolve(pos!.pos))));
-                    this._editorView!.focus();
-                }
-            }
-        };
     }
 
     onPointerUp = (e: React.PointerEvent): void => {
@@ -832,7 +821,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 e.stopPropagation();
                 e.preventDefault();
             }
-
         }
         // this hackiness handles clicking on the list item bullets to do expand/collapse.  the bullets are ::before pseudo elements so there's no real way to hit test against them.
         if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
@@ -858,14 +846,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
         }
         this._editorView!.focus();
     }
-    onMouseDown = (e: React.MouseEvent): void => {
-        if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
-            e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible
+    onMouseUp = (e: React.MouseEvent): void => {
+        e.stopPropagation();
+
+        // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there are nested prosemirrors.  We only want the lowest level prosemirror to be invoked.
+        if ((this._editorView as any).mouseDown) {
+            let originalUpHandler = (this._editorView as any).mouseDown.up;
+            (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler);
+            (this._editorView as any).mouseDown.up = (e: MouseEvent) => {
+                !(e as any).formattedHandled && originalUpHandler(e);
+                e.stopPropagation();
+                (e as any).formattedHandled = true;
+            };
+            (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up);
         }
     }
 
     tooltipTextMenuPlugin() {
-        let myprops = this.props;
         let self = FormattedTextBox;
         return new Plugin({
             view(_editorView) {
@@ -944,7 +941,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 onBlur={this.onBlur}
                 onPointerUp={this.onPointerUp}
                 onPointerDown={this.onPointerDown}
-                onMouseDown={this.onMouseDown}
+                onMouseUp={this.onMouseUp}
                 onWheel={this.onPointerWheel}
                 onPointerEnter={action(() => this._entered = true)}
                 onPointerLeave={action(() => this._entered = false)}
-- 
cgit v1.2.3-70-g09d2

