From e0316c21838613df0fbf43df6a9ca5d696c52f47 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 30 Jul 2019 20:19:58 -0400
Subject: more interesting speech commands and command manager pattern for
 DictationManager

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

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

diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c595a4c56..3f88ed98c 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -22,6 +22,24 @@ export enum CollectionViewType {
     Masonry
 }
 
+export namespace CollectionViewType {
+
+    const stringMapping = new Map<string, CollectionViewType>([
+        ["invalid", CollectionViewType.Invalid],
+        ["freeform", CollectionViewType.Freeform],
+        ["schema", CollectionViewType.Schema],
+        ["docking", CollectionViewType.Docking],
+        ["tree", CollectionViewType.Tree],
+        ["stacking", CollectionViewType.Stacking],
+        ["masonry", CollectionViewType.Masonry]
+    ]);
+
+    export const ValueOf = (value: string) => {
+        return stringMapping.get(value.toLowerCase());
+    };
+
+}
+
 export interface CollectionRenderProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
     removeDocument: (document: Doc) => boolean;
-- 
cgit v1.2.3-70-g09d2


From 982961c71fdd9d5dcd02ea33a2b631076a6a1f4b Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 3 Aug 2019 13:31:12 -0400
Subject: refactor

---
 src/client/util/DictationManager.ts                | 307 +++++++++++----------
 src/client/views/GlobalKeyHandler.ts               |  14 +-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 4 files changed, 165 insertions(+), 162 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index b0866a826..b51d309a1 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -11,183 +11,190 @@ import { listSpec } from "../../new_fields/Schema";
 import { AudioField, ImageField } from "../../new_fields/URLField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 
-namespace CORE {
-    export interface IWindow extends Window {
-        webkitSpeechRecognition: any;
-    }
-}
-
-const ConstructorMap = new Map<DocumentType, CastCtor>([
-    [DocumentType.COL, listSpec(Doc)],
-    [DocumentType.AUDIO, AudioField],
-    [DocumentType.IMG, ImageField],
-    [DocumentType.HIST, HistogramField],
-    [DocumentType.IMPORT, listSpec(Doc)]
-]);
-
-const tryCast = (view: DocumentView, type: DocumentType) => {
-    let ctor = ConstructorMap.get(type);
-    if (!ctor) {
-        return false;
-    }
-    return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
-};
+export namespace DictationManager {
 
-const validate = (target: DocumentView, types: DocumentType[]) => {
-    for (let type of types) {
-        if (tryCast(target, type)) {
-            return true;
+    namespace CORE {
+        export interface IWindow extends Window {
+            webkitSpeechRecognition: any;
         }
     }
-    return false;
-};
-
-const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
-export type IndependentAction = (target: DocumentView) => any | Promise<any>;
-export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
-export type RegistrationEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
-export type ActionPredicate = (target: DocumentView) => boolean;
-export type RegexEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] };
-
-export default class DictationManager {
-    public static Instance = new DictationManager();
-    private recognizer: any;
-    private isListening = false;
-
-    constructor() {
-        this.recognizer = new webkitSpeechRecognition();
-        this.recognizer.interimResults = false;
-        this.recognizer.continuous = true;
-    }
 
-    finish = (handler: any, data: any) => {
-        handler(data);
-        this.stop();
-    }
+    const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
 
-    stop = () => {
-        this.recognizer.stop();
-        this.isListening = false;
-    }
+    let isListening = false;
+    const recognizer = (() => {
+        let initialized = new webkitSpeechRecognition();
+        initialized.interimResults = false;
+        initialized.continuous = true;
+        return initialized;
+    })();
 
-    listen = () => {
-        if (this.isListening) {
-            return undefined;
-        }
-        this.isListening = true;
-        this.recognizer.start();
-        return new Promise<string>((resolve, reject) => {
-            this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript);
-            this.recognizer.onerror = (e: any) => this.finish(reject, e);
-        });
-    }
+    export namespace Controls {
+
+        export const listen = () => {
+            if (isListening) {
+                return undefined;
+            }
+            isListening = true;
+            recognizer.start();
+            return new Promise<string>((resolve, reject) => {
+                recognizer.onresult = (e: any) => {
+                    resolve(e.results[0][0].transcript);
+                    stop();
+                };
+                recognizer.onerror = (e: any) => {
+                    reject(e);
+                    stop();
+                };
+            });
+        };
+
+        export const stop = () => {
+            recognizer.stop();
+            isListening = false;
+        };
 
-    public interpretNumber = (number: string) => {
-        let initial = parseInt(number);
-        if (!isNaN(initial)) {
-            return initial;
-        }
-        let converted = converter.wordsToNumbers(number, { fuzzy: true });
-        if (converted === null) {
-            return NaN;
-        }
-        return typeof converted === "string" ? parseInt(converted) : converted;
     }
 
-    @undoBatch
-    public execute = async (phrase: string) => {
-        let targets = SelectionManager.SelectedDocuments();
-        if (!targets || !targets.length) {
-            return;
-        }
+    export namespace Commands {
 
-        let entry = RegisteredCommands.Independent.get(phrase);
-        if (entry) {
-            let success = false;
-            for (let target of targets) {
-                if (!entry.restrictTo || validate(target, entry.restrictTo)) {
-                    await entry.action(target);
-                    success = true;
-                }
+        export type IndependentAction = (target: DocumentView) => any | Promise<any>;
+        export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
+
+        export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+        export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] };
+
+        export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
+        export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
+
+        export const execute = async (phrase: string) => {
+            let targets = SelectionManager.SelectedDocuments();
+            if (!targets || !targets.length) {
+                return;
             }
-            return success;
-        }
 
-        for (let entry of RegisteredCommands.Dependent) {
-            let regex = entry.expression;
-            let matches = regex.exec(phrase);
-            regex.lastIndex = 0;
-            if (matches !== null) {
+            let entry = Independent.get(phrase);
+            if (entry) {
                 let success = false;
+                let restrictTo = entry.restrictTo;
                 for (let target of targets) {
-                    if (!entry.restrictTo || validate(target, entry.restrictTo)) {
-                        await entry.action(target, matches);
+                    if (!restrictTo || validate(target, restrictTo)) {
+                        await entry.action(target);
                         success = true;
                     }
                 }
                 return success;
             }
-        }
 
-        return false;
-    }
+            for (let entry of Dependent) {
+                let regex = entry.expression;
+                let matches = regex.exec(phrase);
+                regex.lastIndex = 0;
+                if (matches !== null) {
+                    let success = false;
+                    let restrictTo = entry.restrictTo;
+                    for (let target of targets) {
+                        if (!restrictTo || validate(target, restrictTo)) {
+                            await entry.action(target, matches);
+                            success = true;
+                        }
+                    }
+                    return success;
+                }
+            }
 
-}
+            return false;
+        };
+
+        const ConstructorMap = new Map<DocumentType, CastCtor>([
+            [DocumentType.COL, listSpec(Doc)],
+            [DocumentType.AUDIO, AudioField],
+            [DocumentType.IMG, ImageField],
+            [DocumentType.HIST, HistogramField],
+            [DocumentType.IMPORT, listSpec(Doc)]
+        ]);
+
+        const tryCast = (view: DocumentView, type: DocumentType) => {
+            let ctor = ConstructorMap.get(type);
+            if (!ctor) {
+                return false;
+            }
+            return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
+        };
 
-export namespace RegisteredCommands {
+        const validate = (target: DocumentView, types: DocumentType[]) => {
+            for (let type of types) {
+                if (tryCast(target, type)) {
+                    return true;
+                }
+            }
+            return false;
+        };
 
-    export const Independent = new Map<string, RegistrationEntry>([
+        const interpretNumber = (number: string) => {
+            let initial = parseInt(number);
+            if (!isNaN(initial)) {
+                return initial;
+            }
+            let converted = converter.wordsToNumbers(number, { fuzzy: true });
+            if (converted === null) {
+                return NaN;
+            }
+            return typeof converted === "string" ? parseInt(converted) : converted;
+        };
 
-        ["clear", {
-            action: (target: DocumentView) => {
-                Doc.GetProto(target.props.Document).data = new List();
-            },
-            restrictTo: [DocumentType.COL]
-        }],
+        const Independent = new Map<string, IndependentEntry>([
 
-        ["open fields", {
-            action: (target: DocumentView) => {
-                let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
-                target.props.addDocTab(kvp, target.dataDoc, "onRight");
-            }
-        }]
-
-    ]);
-
-    export const Dependent = new Array<RegexEntry>(
-
-        {
-            expression: /create (\w+) documents of type (image|nested collection)/g,
-            action: (target: DocumentView, matches: RegExpExecArray) => {
-                let count = DictationManager.Instance.interpretNumber(matches[1]);
-                let what = matches[2];
-                let dataDoc = Doc.GetProto(target.props.Document);
-                let fieldKey = "data";
-                for (let i = 0; i < count; i++) {
-                    let created: Doc | undefined;
-                    switch (what) {
-                        case "image":
-                            created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
-                            break;
-                        case "nested collection":
-                            created = Docs.Create.FreeformDocument([], {});
-                            break;
-                    }
-                    created && Doc.AddDocToList(dataDoc, fieldKey, created);
+            ["clear", {
+                action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(),
+                restrictTo: [DocumentType.COL]
+            }],
+
+            ["open fields", {
+                action: (target: DocumentView) => {
+                    let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+                    target.props.addDocTab(kvp, target.dataDoc, "onRight");
                 }
+            }]
+
+        ]);
+
+        const Dependent = new Array<DependentEntry>(
+
+            {
+                expression: /create (\w+) documents of type (image|nested collection)/g,
+                action: (target: DocumentView, matches: RegExpExecArray) => {
+                    let count = interpretNumber(matches[1]);
+                    let what = matches[2];
+                    let dataDoc = Doc.GetProto(target.props.Document);
+                    let fieldKey = "data";
+                    for (let i = 0; i < count; i++) {
+                        let created: Doc | undefined;
+                        switch (what) {
+                            case "image":
+                                created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
+                                break;
+                            case "nested collection":
+                                created = Docs.Create.FreeformDocument([], {});
+                                break;
+                        }
+                        created && Doc.AddDocToList(dataDoc, fieldKey, created);
+                    }
+                },
+                restrictTo: [DocumentType.COL]
             },
-            restrictTo: [DocumentType.COL]
-        },
-
-        {
-            expression: /view as (freeform|stacking|masonry|schema|tree)/g,
-            action: (target: DocumentView, matches: RegExpExecArray) => {
-                let mode = CollectionViewType.ValueOf(matches[1]);
-                mode && (target.props.Document.viewType = mode);
-            },
-            restrictTo: [DocumentType.COL]
-        }
 
-    );
+            {
+                expression: /view as (freeform|stacking|masonry|schema|tree)/g,
+                action: (target: DocumentView, matches: RegExpExecArray) => {
+                    let mode = CollectionViewType.valueOf(matches[1]);
+                    mode && (target.props.Document.viewType = mode);
+                },
+                restrictTo: [DocumentType.COL]
+            }
+
+        );
+
+    }
 
 }
\ No newline at end of file
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 99345f04e..609136bb5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -5,10 +5,7 @@ import { MainView } from "./MainView";
 import { DragManager } from "../util/DragManager";
 import { action, runInAction } from "mobx";
 import { Doc } from "../../new_fields/Doc";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import DictationManager from "../util/DictationManager";
-import { ContextMenu } from "./ContextMenu";
-import { ContextMenuProps } from "./ContextMenuItem";
+import { DictationManager } from "../util/DictationManager";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -74,7 +71,7 @@ export default class KeyManager {
                 }
                 main.toggleColorPicker(true);
                 SelectionManager.DeselectAll();
-                DictationManager.Instance.stop();
+                DictationManager.Controls.stop();
                 main.dictationOverlayVisible = false;
                 main.dictationSuccess = undefined;
                 main.overlayTimeout && clearTimeout(main.overlayTimeout);
@@ -114,19 +111,18 @@ export default class KeyManager {
                 let main = MainView.Instance;
                 main.dictationOverlayVisible = true;
                 main.isListening = true;
-                let dictation = DictationManager.Instance;
-                let command = await dictation.listen();
+                let command = await DictationManager.Controls.listen();
                 main.isListening = false;
                 if (!command) {
                     break;
                 }
                 command = command.toLowerCase();
                 main.dictatedPhrase = command;
-                main.dictationSuccess = await dictation.execute(command);
+                main.dictationSuccess = await DictationManager.Commands.execute(command);
                 main.overlayTimeout = setTimeout(() => {
                     main.dictationOverlayVisible = false;
                     main.dictationSuccess = undefined;
-                }, 3000);
+                }, 2000);
                 stopPropagation = true;
                 preventDefault = true;
         }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 3f88ed98c..24604c812 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -34,7 +34,7 @@ export namespace CollectionViewType {
         ["masonry", CollectionViewType.Masonry]
     ]);
 
-    export const ValueOf = (value: string) => {
+    export const valueOf = (value: string) => {
         return stringMapping.get(value.toLowerCase());
     };
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 51661d1ae..c5d526a5a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,7 +41,7 @@ import { ClientUtils } from '../../util/ClientUtils';
 import { EditableView } from '../EditableView';
 import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
 import { DocumentDecorations } from '../DocumentDecorations';
-import DictationManager from '../../util/DictationManager';
+import { DictationManager } from '../../util/DictationManager';
 import { CollectionViewType } from '../collections/CollectionBaseView';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -539,7 +539,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     listen = async () => {
-        let transcript = await DictationManager.Instance.listen();
+        let transcript = await DictationManager.Controls.listen();
         transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 316c241d72fb83aad5f2bf9b143c317fdc906654 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 8 Aug 2019 12:23:47 -0400
Subject: fixed issues with jumptoDocument

---
 package.json                                       |  2 +-
 src/client/documents/Documents.ts                  |  3 +--
 src/client/util/DocumentManager.ts                 | 12 ++++++-----
 .../views/collections/CollectionBaseView.tsx       |  5 ++---
 src/client/views/nodes/DocumentView.tsx            | 24 +++++++++-------------
 src/client/views/nodes/LinkMenuItem.tsx            |  4 ++--
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/client/views/pdf/Page.tsx                      |  3 +--
 8 files changed, 25 insertions(+), 30 deletions(-)

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

diff --git a/package.json b/package.json
index 29a903d71..d699d1e6f 100644
--- a/package.json
+++ b/package.json
@@ -218,4 +218,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a8545206a..7dd853156 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -596,7 +596,7 @@ export namespace Docs {
 
 export namespace DocUtils {
 
-    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default", sourceContext?: Doc) {
+    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) {
         if (LinkManager.Instance.doesLinkExist(source, target)) return undefined;
         let sv = DocumentManager.Instance.getDocumentView(source);
         if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return;
@@ -610,7 +610,6 @@ export namespace DocUtils {
             linkDocProto.sourceContext = sourceContext;
             linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
             linkDocProto.linkDescription = description;
-            linkDocProto.linkTags = tags;
             linkDocProto.type = DocumentType.LINK;
 
             linkDocProto.anchor1 = source;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 0d46df406..7f526b247 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,5 +1,5 @@
 import { action, computed, observable } from 'mobx';
-import { Doc } from '../../new_fields/Doc';
+import { Doc, DocListCastAsync } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { Cast, NumCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
@@ -140,13 +140,15 @@ export class DocumentManager {
         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");
+            UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
         } else {
             if (!contextDoc) {
-                if (docContext) {
+                let docs = docContext ? await DocListCastAsync(docContext.data) : undefined;
+                let found = false;
+                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);
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index b53e83eb1..cad87ebcc 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -99,7 +99,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
         var curPage = NumCast(this.props.Document.curPage, -1);
         Doc.GetProto(doc).page = curPage;
-        if (curPage >= 0) {
+        if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
         allowDuplicates = true;
@@ -126,8 +126,7 @@ 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[Id] === doc[Id]) ? i : p, -1);
         PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
-            annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)
-        );
+            annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
 
         if (index !== -1) {
             value.splice(index, 1);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 861b53abf..c8eab85c2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -8,7 +8,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from '../../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
@@ -359,12 +359,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     if (!linkedFwdDocs.some(l => l instanceof Promise)) {
                         let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
                         let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => {
-                            this.props.focus(this.props.Document, true, 1);
-                            setTimeout(() =>
-                                this.props.addDocTab(document, undefined, maxLocation), 1000);
-                        }
-                            , linkedFwdPage[altKey ? 1 : 0], targetContext);
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
+                            document => {  // open up target if it's not already in view ...
+                                this.props.focus(this.props.Document, true, 1);  // by zooming into the button document first
+                                setTimeout(() => this.props.addDocTab(document, undefined, maxLocation), 1000); // then after the 1sec animation, open up the target in a new tab
+                            },
+                            linkedFwdPage[altKey ? 1 : 0], targetContext);
                     }
                 }
             }
@@ -445,13 +445,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let targetDoc = this.props.Document;
             targetDoc.targetContext = de.data.targetContext;
             let annotations = await DocListCastAsync(annotationDoc.annotations);
-            if (annotations) {
-                annotations.forEach(anno => anno.target = targetDoc);
-            }
-            let annotDoc = await Cast(annotationDoc.annotationOn, Doc);
-            if (annotDoc) {
-                DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(annotDoc.title)}`, "", StrCast(annotDoc.title));
-            }
+            annotations && annotations.forEach(anno => anno.target = targetDoc);
+
+            DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
         }
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc = de.data.linkSourceDocument;
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 1d4fcad69..a119eb39b 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -6,7 +6,7 @@ import { DocumentManager } from "../../util/DocumentManager";
 import { undoBatch } from "../../util/UndoManager";
 import './LinkMenu.scss';
 import React = require("react");
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, DocListCastAsync } from '../../../new_fields/Doc';
 import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { observable, action } from 'mobx';
 import { LinkManager } from '../../util/LinkManager';
@@ -52,7 +52,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         }
 
         if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(targetContext!));
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
         }
         else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
             DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7cd62f4e0..08674720d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -211,7 +211,7 @@ 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)}`, "", StrCast(this.props.Document.title));
+            DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`);
         }
         this._savedAnnotations.clear();
         this.Index = -1;
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 6bd98cbaa..7ca9d2d7d 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -112,8 +112,7 @@ export default class Page extends React.Component<IPageProps> {
                         if (!BoolCast(annotationDoc.linkedToDoc)) {
                             let annotations = await DocListCastAsync(annotationDoc.annotations);
                             annotations && annotations.forEach(anno => anno.target = targetDoc);
-                            let parentDoc = await Cast(annotationDoc.annotationOn, Doc);
-                            parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title))
+                            DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`)
                         }
                     }
                 },
-- 
cgit v1.2.3-70-g09d2

