From 58b780563c7fc4a1496f5c676f2d14faddb096e0 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Fri, 3 Jul 2020 17:32:40 -0700
Subject: merge with master

---
 src/client/views/MainView.tsx | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f6db1af66..c0edf7032 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -99,7 +99,7 @@ export class MainView extends React.Component {
         window.removeEventListener("keydown", KeyManager.Instance.handle);
         window.addEventListener("keydown", KeyManager.Instance.handle);
         window.addEventListener("paste", KeyManager.Instance.paste as any);
-        document.addEventListener("dash", (e: any) => {  // event used by chrome plugin to tell Dash which document to focus on 
+        document.addEventListener("dash", (e: any) => {  // event used by chrome plugin to tell Dash which document to focus on
             const id = FormattedTextBox.GetDocFromUrl(e.detail);
             DocServer.GetRefField(id).then(doc => {
                 if (doc instanceof Doc) {
@@ -107,6 +107,10 @@ export class MainView extends React.Component {
                 }
             });
         });
+        document.addEventListener("linkComplete", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an annotation has been linked
+            const annotatedUrl = e.details;
+            console.log("This website " + annotatedUrl + " has a linked annotation");
+        });
     }
 
     componentWillUnMount() {
-- 
cgit v1.2.3-70-g09d2


From fbc7a328016af60052dd3f33c2d906e6c6447a5f Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Mon, 6 Jul 2020 23:41:34 -0700
Subject: set up linking for hypothesis annotations

---
 src/client/documents/Documents.ts                  | 19 +++++++++++++++
 src/client/views/MainView.tsx                      |  8 +++----
 .../views/collections/CollectionLinearView.tsx     |  2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     | 28 +++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 5 files changed, 44 insertions(+), 15 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f81c25bab..ae34e5909 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -50,6 +50,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { Networking } from "../Network";
 import { Upload } from "../../server/SharedMediaTypes";
+import { Hypothesis } from "../apis/hypothesis/HypothesisApiUtils";
 const path = require('path');
 
 export interface DocumentOptions {
@@ -882,6 +883,24 @@ export namespace DocUtils {
         return linkDoc;
     }
 
+    export function MakeHypothesisLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", sourceAnnotationId: string, id?: string) {
+        const sv = DocumentManager.Instance.getDocumentView(source.doc);
+        if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
+        if (target.doc === Doc.UserDoc()) return undefined;
+
+        const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id);
+        linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
+        Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") "  + self.anchor2?.title');
+
+        console.log("sourceAnnotationId, should be url?", sourceAnnotationId, StrCast(source.doc.data));
+        Doc.GetProto(linkDoc).sourceRedirectUrl = Hypothesis.makeAnnotationUrl(sourceAnnotationId, StrCast(source.doc.data));
+        // Doc.GetProto(linkDoc).targetRedirectUrl = undefined;
+
+        Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
+        Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
+
+        return linkDoc;
+    }
 
     export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
         let created: Doc | undefined;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 65d585a2a..4c3add663 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -108,10 +108,10 @@ export class MainView extends React.Component {
                 }
             });
         });
-        document.addEventListener("linkComplete", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an annotation has been linked
-            const annotatedUrl = e.details;
-            console.log("This website " + annotatedUrl + " has a linked annotation");
-        });
+        // document.addEventListener("linkComplete", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an annotation has been linked
+        //     const annotatedUrl = e.details;
+        //     console.log("This website " + annotatedUrl + " has a linked annotation");
+        // });
     }
 
     componentWillUnMount() {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7cbe5c19d..fd8eb506a 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -154,7 +154,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                 }}
                     onPointerDown={e => e.stopPropagation()} >
                     <span className="bottomPopup-text" >
-                        Creating link from: {DocumentLinksButton.StartLink.title} </span>
+                        Creating link from: {(DocumentLinksButton.AnnotationId ? "Annotation in " : "")} {DocumentLinksButton.StartLink.title} </span>
                     <span className="bottomPopup-exit" onClick={this.exitLongLinks}
                     >Exit</span>
 
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 37d47700f..f12472b01 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -7,12 +7,13 @@ import { UndoManager } from "../../util/UndoManager";
 import './DocumentLinksButton.scss';
 import { DocumentView } from "./DocumentView";
 import React = require("react");
-import { DocUtils } from "../../documents/Documents";
+import { DocUtils, Docs } from "../../documents/Documents";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { LinkDocPreview } from "./LinkDocPreview";
 import { LinkCreatedBox } from "./LinkCreatedBox";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Document } from "../../../fields/documentSchemas";
+import { StrCast } from "../../../fields/Types";
 
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -29,6 +30,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     private _linkButton = React.createRef<HTMLDivElement>();
 
     @observable public static StartLink: DocumentView | undefined;
+    @observable public static AnnotationId: string | undefined;
 
     componentDidMount() {
         // window.addEventListener("linkStarted", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created
@@ -37,12 +39,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         //         DocumentLinksButton.StartLink = element;
         //     }));
         // });
-        window.addEventListener("fakeLinkStarted", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created
-            console.log("Helo fake link");
-            SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
-                DocumentLinksButton.StartLink = element;
-            }));
-        });
+        window.addEventListener("fakeLinkStarted", action((e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created
+            console.log("Helo fake started link");
+            const id = e.detail;
+            const source = SelectionManager.SelectedDocuments()[0];
+            DocumentLinksButton.AnnotationId = id;
+            DocumentLinksButton.StartLink = source;
+        }));
     }
 
     @action
@@ -101,12 +104,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             if (doubleTap) {
                 if (DocumentLinksButton.StartLink === this.props.View) {
                     DocumentLinksButton.StartLink = undefined;
+                    DocumentLinksButton.AnnotationId = undefined;
+                    console.log("reset to undefined (completeLink)");
                     // action((e: React.PointerEvent<HTMLDivElement>) => {
                     //     Doc.UnBrushDoc(this.props.View.Document);
                     // });
                 } else {
                     DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
-                        DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                        (DocumentLinksButton.AnnotationId ?
+                            DocUtils.MakeHypothesisLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "hypothesis annotation", DocumentLinksButton.AnnotationId) :
+                            DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag"));
 
                     runInAction(() => {
                         LinkCreatedBox.popupX = e.screenX;
@@ -123,12 +130,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     finishLinkClick = (e: React.MouseEvent) => {
         if (DocumentLinksButton.StartLink === this.props.View) {
             DocumentLinksButton.StartLink = undefined;
+            console.log("reset to undefined (finisheLinkClick)");
             // action((e: React.PointerEvent<HTMLDivElement>) => {
             //     Doc.UnBrushDoc(this.props.View.Document);
             // });
         } else {
             DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View &&
-                DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+                (DocumentLinksButton.AnnotationId ?
+                    DocUtils.MakeHypothesisLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "hypothesis annotation", DocumentLinksButton.AnnotationId) :
+                    DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag"));
 
             runInAction(() => {
                 LinkCreatedBox.popupX = e.screenX;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f42b72dbd..128a56c19 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -776,7 +776,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         cm.addItem({
             description: "pretend we made an annotation", event: () => {
                 document.dispatchEvent(new CustomEvent("fakeLinkStarted", {
-                    detail: "hello this is fake",
+                    detail: "fakefakefakeid",
                     bubbles: true
                 }));
             }, icon: "eye"
-- 
cgit v1.2.3-70-g09d2


From 4f16c24aa5221db8670fa69b7f7bee0f897fb203 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Fri, 10 Jul 2020 13:31:13 -0700
Subject: automatically starts link when an annotation is created

---
 src/client/apis/hypothesis/HypothesisApiUtils.ts |  3 ++-
 src/client/views/MainView.tsx                    |  4 ---
 src/client/views/nodes/DocumentLinksButton.tsx   | 33 ++++++++----------------
 src/client/views/nodes/DocumentView.tsx          |  9 -------
 4 files changed, 13 insertions(+), 36 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts
index 9cd4b9f80..9ba481c8c 100644
--- a/src/client/apis/hypothesis/HypothesisApiUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts
@@ -65,7 +65,8 @@ export namespace Hypothesis {
 
     // Construct an URL which will scroll the web page to a specific annotation's position
     export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => {
-        return `https://hyp.is/${annotationId}/${baseUrl}`;
+        return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version
+        // return baseUrl + '#annotations:' + annotationId;
     };
 
     // Extract username from Hypothe.is's userId format
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cf1129895..81195b550 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -111,10 +111,6 @@ export class MainView extends React.Component {
                 }
             });
         });
-        // document.addEventListener("linkComplete", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an annotation has been linked
-        //     const annotatedUrl = e.details;
-        //     console.log("This website " + annotatedUrl + " has a linked annotation");
-        // });
     }
 
     componentWillUnMount() {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 223d9fbf8..535711193 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -38,27 +38,17 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @observable public static AnnotationUri: string | undefined;
 
     componentDidMount() {
-        // window.addEventListener("annotationCreated", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created
-        //     const id = e.details;
-        //     const source = SelectionManager.SelectedDocuments()[0];
-        //     runInAction(() => {
-        //         DocumentLinksButton.AnnotationId = id;
-        //         DocumentLinksButton.StartLink = source;
-        //     });
-        // });
-        console.log("window", window);
-        window.addEventListener("fakeAnnotationCreated", async (e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created
-            if (e.handled) return;
-            e.handled = true;
-            console.log("Helo fake annotation make");
-            // const id = e.detail;
-            const response = await Hypothesis.getPlaceholderId("bobzel", "placeholder"); // delete once eventListening between client & Dash works
-            const source = SelectionManager.SelectedDocuments()[0];
-            response && runInAction(() => {
-                DocumentLinksButton.AnnotationId = response.id;
-                DocumentLinksButton.AnnotationUri = response.uri;
-                DocumentLinksButton.StartLink = source;
-            });
+        window.addEventListener("message", async (e: any) => {
+            if (e.origin === "http://localhost:1050" && e.data.message === "annotation created") {
+                console.log("DASH RECEIVED MESSAGE:", e.data.message);
+                const response = await Hypothesis.getPlaceholderId("melissaz", "placeholder"); // delete once eventListening between client & Dash works
+                const source = SelectionManager.SelectedDocuments()[0];
+                response && runInAction(() => {
+                    DocumentLinksButton.AnnotationId = response.id;
+                    DocumentLinksButton.AnnotationUri = response.uri;
+                    DocumentLinksButton.StartLink = source;
+                });
+            }
         });
     }
 
@@ -174,7 +164,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 // if the link is to a Hypothes.is annotation
                 if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
                     const sourceUrl = DocumentLinksButton.AnnotationUri; // the URL of the annotation's source web page
-                    console.log("sourceAnnotationId, url", DocumentLinksButton.AnnotationId, sourceUrl);
                     Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                     Doc.GetProto(linkDoc as Doc).annotationUrl = Hypothesis.makeAnnotationUrl(DocumentLinksButton.AnnotationId, sourceUrl); // redirect web doc to this URL when following link
                     Hypothesis.dispatchLinkRequest(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f766c3867..2f19eb266 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -774,15 +774,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         const cm = ContextMenu.Instance;
         if (!cm) return;
 
-        cm.addItem({
-            description: "pretend we made an annotation", event: () => {
-                document.dispatchEvent(new CustomEvent("fakeAnnotationCreated", {
-                    detail: "fakefakefakeid",
-                    bubbles: true
-                }));
-            }, icon: "eye"
-        });
-
         const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
         Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
-- 
cgit v1.2.3-70-g09d2


From 5ca85299fc42e5881b313053cf6c6fd2df572e12 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Fri, 24 Jul 2020 17:49:47 -0700
Subject: fix listeners for annotation listening

---
 src/client/views/MainView.tsx                  | 29 +++++++++++++++++++
 src/client/views/nodes/DocumentLinksButton.tsx | 39 --------------------------
 2 files changed, 29 insertions(+), 39 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 225fb2e8e..6d18b3177 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -66,6 +66,8 @@ import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
 import CollectionMenu from './collections/CollectionMenu';
+import { Hypothesis } from '../apis/hypothesis/HypothesisApiUtils';
+import { SelectionManager } from '../util/SelectionManager';
 
 @observer
 export class MainView extends React.Component {
@@ -113,6 +115,33 @@ export class MainView extends React.Component {
                 }
             });
         });
+        window.addEventListener("message", async (e: any) => { // listen for a new Hypothes.is annotation from an iframe inside Dash
+            // start link from new Hypothes.is annotation
+            // TODO: pass in placeholderId directly from client, move
+            if (e.origin === "http://localhost:1050" && e.data.message === "annotation created") {
+                console.log("DASH received message: annotation created");
+                const response = await Hypothesis.getPlaceholderId("placeholder");
+                const source = SelectionManager.SelectedDocuments()[0];
+                response && runInAction(() => {
+                    DocumentLinksButton.AnnotationId = response.id;
+                    DocumentLinksButton.AnnotationUri = response.uri;
+                    DocumentLinksButton.StartLink = source;
+                });
+            }
+        });
+        document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to to link an existing annotation to Dash
+            const annotationId = e.detail.id;
+            const annotationUri = e.detail.uri;
+            console.log(annotationId, annotationUri);
+            // const source = await Hypothesis.getWebDoc(annotationUri);
+
+            const source = SelectionManager.SelectedDocuments()[0]; // TO BE FIXED, currently link just starts from whichever doc is selected
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+                DocumentLinksButton.StartLink = source;
+            });
+        });
     }
 
     componentWillUnMount() {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 57fa26b80..8b4dc2957 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -39,43 +39,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @observable public static AnnotationId: string | undefined;
     @observable public static AnnotationUri: string | undefined;
 
-    componentDidMount() {
-        window.addEventListener("message", this.newAnnotation); // listen for a new Hypothes.is annotation from an iframe inside Dash
-        document.addEventListener("linkAnnotationToDash", this.linkAnnotation); // listen for event from Hypothes.is extension to link an existing annotation
-    }
-
-    // start link from new Hypothes.is annotation
-    // TODO: pass in placeholderId directly from client
-    @action
-    newAnnotation = async (e: any) => {
-        if (e.origin === "http://localhost:1050" && e.data.message === "annotation created") {
-            console.log("DASH received message: annotation created");
-            window.removeEventListener("message", this.newAnnotation);
-            const response = await Hypothesis.getPlaceholderId("placeholder");
-            const source = SelectionManager.SelectedDocuments()[0];
-            response && runInAction(() => {
-                DocumentLinksButton.AnnotationId = response.id;
-                DocumentLinksButton.AnnotationUri = response.uri;
-                DocumentLinksButton.StartLink = source;
-            });
-        }
-    }
-
-    @action
-    linkAnnotation = async (e: any) => { // event sent by hypothes.is plugin to tell Dash which annotation we're linking from
-        const annotationId = e.detail.id;
-        const annotationUri = e.detail.uri;
-        console.log(annotationId, annotationUri);
-        // const source = await Hypothesis.getWebDoc(annotationUri);
-
-        const source = SelectionManager.SelectedDocuments()[0]; // TO BE FIXED, currently link just starts from whichever doc is selected
-        runInAction(() => {
-            DocumentLinksButton.AnnotationId = annotationId;
-            DocumentLinksButton.AnnotationUri = annotationUri;
-            DocumentLinksButton.StartLink = source;
-        });
-    }
-
     @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this.props.InMenu && this.props.StartLink) {
@@ -179,8 +142,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         if (DocumentLinksButton.StartLink === this.props.View) {
             DocumentLinksButton.StartLink = undefined;
             DocumentLinksButton.AnnotationId = undefined;
-            window.removeEventListener("message", this.newAnnotation);
-            window.addEventListener("message", this.newAnnotation);
         } else {
             if (this.props.InMenu && !!!this.props.StartLink) {
                 if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
-- 
cgit v1.2.3-70-g09d2


From b1308d059c78e516c6062e4dd6f92dd409cf17a7 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Sat, 25 Jul 2020 15:38:58 -0700
Subject: search for source web doc when linkToDash is called

---
 src/client/apis/hypothesis/HypothesisApiUtils.ts | 22 ++++++++++++----------
 src/client/views/MainView.tsx                    |  4 ++--
 2 files changed, 14 insertions(+), 12 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts
index 4c9fef45c..cf265479a 100644
--- a/src/client/apis/hypothesis/HypothesisApiUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts
@@ -4,6 +4,7 @@ import { SearchUtil } from "../../util/SearchUtil";
 import { action } from "mobx";
 import { Doc } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
+import { WebField } from "../../../fields/URLField";
 
 export namespace Hypothesis {
 
@@ -75,8 +76,7 @@ export namespace Hypothesis {
         const annotation = await fetchAnnotation(annotationId);
         const regex = new RegExp(`(\[[^[]*)\(${linkUrl.replace('/', '\/')}\)`);
         const target = regex.exec(annotation.text); // use regex to extract the link to be deleted, which is written in [title](hyperlink) format
-        target && console.log(target);
-        // target && editAnnotation(annotationId, annotation.text.replace(target[0], ''));
+        target && editAnnotation(annotationId, annotation.text.replace(target[0], ''));
     };
 
     // Finds the most recent placeholder annotation created and returns its ID
@@ -100,18 +100,20 @@ export namespace Hypothesis {
     };
 
     // Return corres
-    export const getWebDocs = async (uri: string) => {
+    export const getSourceWebDoc = async (uri: string) => {
         const results: Doc[] = [];
-        await SearchUtil.Search(uri, true).then(action(async (res: SearchUtil.DocSearchResult) => {
+        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
             const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
-            const filteredDocs = docs.filter(doc => doc.type === DocumentType.WEB && doc.data);
-
-            console.log("docs", docs);
-            console.log("FILTEREDDOCS: ", filteredDocs);
+            const filteredDocs = docs.filter(doc =>
+                doc.type === DocumentType.WEB && doc.data
+            );
             filteredDocs.forEach(doc => {
-                results.push(doc);
+                console.log(Cast(doc.data, WebField)?.url.href);
+                if (uri === Cast(doc.data, WebField)?.url.href) results.push(doc); // TODO check history? imperfect matches?
             });
         }));
-        return results;
+
+        // TODO: open & return new Web doc with given uri if no matching Web docs are found
+        return results.length ? results[0] : undefined;
     };
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6d18b3177..605b81506 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -132,8 +132,8 @@ export class MainView extends React.Component {
         document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to to link an existing annotation to Dash
             const annotationId = e.detail.id;
             const annotationUri = e.detail.uri;
-            console.log(annotationId, annotationUri);
-            // const source = await Hypothesis.getWebDoc(annotationUri);
+            const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
+            console.log("sourceDoc: ", sourceDoc ? sourceDoc.title : "not found");
 
             const source = SelectionManager.SelectedDocuments()[0]; // TO BE FIXED, currently link just starts from whichever doc is selected
             runInAction(() => {
-- 
cgit v1.2.3-70-g09d2


From 7157310f1b4d7a9f409c46f35c10c8db4a66b0af Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Tue, 28 Jul 2020 19:00:02 -0700
Subject: scroll to annotations within dash, handle annotation editing in
 client

---
 src/client/apis/hypothesis/HypothesisApiUtils.ts | 22 +++++++++++-----------
 src/client/views/MainView.tsx                    | 19 +++----------------
 src/client/views/linking/LinkMenuItem.tsx        |  4 ++--
 3 files changed, 16 insertions(+), 29 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts
index 1158dcc11..e2fb91af9 100644
--- a/src/client/apis/hypothesis/HypothesisApiUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts
@@ -5,6 +5,7 @@ import { action } from "mobx";
 import { Doc } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { WebField } from "../../../fields/URLField";
+import { DocumentManager } from "../../util/DocumentManager";
 
 export namespace Hypothesis {
 
@@ -49,9 +50,9 @@ export namespace Hypothesis {
     };
 
     export const editAnnotation = async (annotationId: string, newText: string) => {
-        console.log("DASH dispatching editRequest");
+        console.log("DASH dispatching editAnnotation");
         const credentials = await getCredentials();
-        document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editRequest", {
+        document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editAnnotation", {
             detail: { newText: newText, id: annotationId, apiKey: credentials.apiKey },
             bubbles: true
         }));
@@ -76,14 +77,6 @@ export namespace Hypothesis {
         editAnnotation(annotationId, out);
     };
 
-    // Finds the most recent placeholder annotation created and returns its ID
-    export const getPlaceholderId = async (searchKeyWord: string) => {
-        const getResponse = await Hypothesis.searchAnnotation(searchKeyWord);
-        const id = getResponse.rows.length > 0 ? getResponse.rows[0].id : undefined;
-        const uri = getResponse.rows.length > 0 ? getResponse.rows[0].uri : undefined;
-        return id ? { id, uri } : undefined;
-    };
-
     // Construct an URL which will scroll the web page to a specific annotation's position
     export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => {
         return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version
@@ -111,6 +104,13 @@ export namespace Hypothesis {
         }));
 
         // TODO: open & return new Web doc with given uri if no matching Web docs are found
-        return results.length ? results[0] : undefined;
+        return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined;
+    };
+
+    export const scrollToAnnotation = (annotationId: string) => {
+        document.dispatchEvent(new CustomEvent("scrollToAnnotation", {
+            detail: annotationId,
+            bubbles: true
+        }));
     };
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 605b81506..64538d015 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -115,27 +115,14 @@ export class MainView extends React.Component {
                 }
             });
         });
-        window.addEventListener("message", async (e: any) => { // listen for a new Hypothes.is annotation from an iframe inside Dash
-            // start link from new Hypothes.is annotation
-            // TODO: pass in placeholderId directly from client, move
-            if (e.origin === "http://localhost:1050" && e.data.message === "annotation created") {
-                console.log("DASH received message: annotation created");
-                const response = await Hypothesis.getPlaceholderId("placeholder");
-                const source = SelectionManager.SelectedDocuments()[0];
-                response && runInAction(() => {
-                    DocumentLinksButton.AnnotationId = response.id;
-                    DocumentLinksButton.AnnotationUri = response.uri;
-                    DocumentLinksButton.StartLink = source;
-                });
-            }
-        });
-        document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to to link an existing annotation to Dash
+        document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to link an annotation to Dash
             const annotationId = e.detail.id;
             const annotationUri = e.detail.uri;
             const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
             console.log("sourceDoc: ", sourceDoc ? sourceDoc.title : "not found");
 
-            const source = SelectionManager.SelectedDocuments()[0]; // TO BE FIXED, currently link just starts from whichever doc is selected
+            // TO BE FIXED, currently cannot start links from new webpages that don't exist in Dash
+            const source = sourceDoc || SelectionManager.SelectedDocuments()[0];
             runInAction(() => {
                 DocumentLinksButton.AnnotationId = annotationId;
                 DocumentLinksButton.AnnotationUri = annotationUri;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index cae90cd0c..8f7a2481d 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -154,14 +154,14 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
 
-        // this.props.linkDoc.linksToAnnotation && (Doc.GetProto(this.props.destinationDoc).data = new WebField(StrCast(this.props.linkDoc.annotationUrl))); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation
         if (this.props.linkDoc.followLinkLocation && this.props.linkDoc.followLinkLocation !== "Default") {
             this.props.addDocTab(this.props.destinationDoc, StrCast(this.props.linkDoc.followLinkLocation));
         } else {
             DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
         }
 
-        this.props.linkDoc.linksToAnnotation && setTimeout(() => window.open(StrCast(this.props.linkDoc.annotationUrl), '_blank'), 1000); // open external page if hypothes.is annotation
+        // this.props.linkDoc.linksToAnnotation && setTimeout(() => window.open(StrCast(this.props.linkDoc.annotationUrl), '_blank'), 1000); // open external page if hypothes.is annotation
+        this.props.linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId));
     }
 
     @undoBatch
-- 
cgit v1.2.3-70-g09d2


From fabb86b78874a38619d821b315eaf9ce204e1afe Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Tue, 28 Jul 2020 21:27:38 -0700
Subject: "handle editing annotations in client, bypass authorization issues"

---
 .../apis/HypothesisAuthenticationManager.tsx       |   2 +-
 src/client/apis/hypothesis/HypothesisApiUtils.ts   | 116 ---------------------
 src/client/apis/hypothesis/HypothesisUtils.ts      |  77 ++++++++++++++
 src/client/documents/Documents.ts                  |   1 -
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/linking/LinkMenuItem.tsx          |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   1 -
 8 files changed, 81 insertions(+), 122 deletions(-)
 delete mode 100644 src/client/apis/hypothesis/HypothesisApiUtils.ts
 create mode 100644 src/client/apis/hypothesis/HypothesisUtils.ts

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
index d4a81b3eb..653f21a7a 100644
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -6,7 +6,7 @@ import { Opt } from "../../fields/Doc";
 import { Networking } from "../Network";
 import "./HypothesisAuthenticationManager.scss";
 import { Scripting } from "../util/Scripting";
-import { Hypothesis } from "./hypothesis/HypothesisApiUtils";
+import { Hypothesis } from "./hypothesis/HypothesisUtils";
 
 const prompt = "Paste authorization code here...";
 
diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts
deleted file mode 100644
index e2fb91af9..000000000
--- a/src/client/apis/hypothesis/HypothesisApiUtils.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import { StrCast, Cast } from "../../../fields/Types";
-import HypothesisAuthenticationManager from "../HypothesisAuthenticationManager";
-import { SearchUtil } from "../../util/SearchUtil";
-import { action } from "mobx";
-import { Doc } from "../../../fields/Doc";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { WebField } from "../../../fields/URLField";
-import { DocumentManager } from "../../util/DocumentManager";
-
-export namespace Hypothesis {
-
-    const getCredentials = async () => HypothesisAuthenticationManager.Instance.fetchAccessToken();
-
-    export const fetchAnnotation = async (annotationId: string) => {
-        const response = await fetch(`https://api.hypothes.is/api/annotations/${annotationId}`);
-        if (response.ok) {
-            return response.json();
-        } else {
-            throw new Error('DASH: Error in fetchAnnotation GET request');
-        }
-    };
-
-    /**
-     * Searches for annotations authored by the current user that contain @param searchKeyWord 
-     */
-    export const searchAnnotation = async (searchKeyWord: string) => {
-        const credentials = await getCredentials();
-        const base = 'https://api.hypothes.is/api/search';
-        const request = base + `?user=acct:${credentials.username}@hypothes.is&text=${searchKeyWord}`;
-        console.log("DASH Querying " + request);
-        const response = await fetch(request);
-        if (response.ok) {
-            return response.json();
-        } else {
-            throw new Error('DASH: Error in searchAnnotation GET request');
-        }
-    };
-
-    export const fetchUser = async (apiKey: string) => {
-        const response = await fetch('https://api.hypothes.is/api/profile', {
-            headers: {
-                'Authorization': `Bearer ${apiKey}`,
-            },
-        });
-        if (response.ok) {
-            return response.json();
-        } else {
-            throw new Error('DASH: Error in fetchUser GET request');
-        }
-    };
-
-    export const editAnnotation = async (annotationId: string, newText: string) => {
-        console.log("DASH dispatching editAnnotation");
-        const credentials = await getCredentials();
-        document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editAnnotation", {
-            detail: { newText: newText, id: annotationId, apiKey: credentials.apiKey },
-            bubbles: true
-        }));
-    };
-
-    /**
-     * Edit an annotation with ID @param annotationId to add a hyperlink to a Dash document, which needs to be 
-     * written in the format [@param title](@param url)
-     */
-    export const makeLink = async (title: string, url: string, annotationId: string) => {
-        const oldAnnotation = await fetchAnnotation(annotationId);
-        const oldText = StrCast(oldAnnotation.text);
-        const newHyperlink = `[${title}\n](${url})`;
-        const newText = oldText === "placeholder" ? newHyperlink : oldText + '\n\n' + newHyperlink; // if this is not the first link in the annotation, add link on new line
-        await editAnnotation(annotationId, newText);
-    };
-
-    export const deleteLink = async (annotationId: string, linkUrl: string) => {
-        const annotation = await fetchAnnotation(annotationId);
-        const regex = new RegExp(`\\[[^\\]]*\\]\\(${linkUrl}\\)`); // finds the link (written in [title](hyperlink) format) to be deleted
-        const out = annotation.text.replace(regex, "");
-        editAnnotation(annotationId, out);
-    };
-
-    // Construct an URL which will scroll the web page to a specific annotation's position
-    export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => {
-        return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version
-        // return baseUrl + '#annotations:' + annotationId;
-    };
-
-    // Extract username from Hypothe.is's userId format
-    export const extractUsername = (userid: string) => {
-        const regex = new RegExp('(?<=\:)(.*?)(?=\@)/');
-        return regex.exec(userid)![0];
-    };
-
-    // Return corres
-    export const getSourceWebDoc = async (uri: string) => {
-        const results: Doc[] = [];
-        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
-            const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
-            const filteredDocs = docs.filter(doc =>
-                doc.type === DocumentType.WEB && doc.data
-            );
-            filteredDocs.forEach(doc => {
-                console.log(Cast(doc.data, WebField)?.url.href);
-                if (uri === Cast(doc.data, WebField)?.url.href) results.push(doc); // TODO check history? imperfect matches?
-            });
-        }));
-
-        // TODO: open & return new Web doc with given uri if no matching Web docs are found
-        return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined;
-    };
-
-    export const scrollToAnnotation = (annotationId: string) => {
-        document.dispatchEvent(new CustomEvent("scrollToAnnotation", {
-            detail: annotationId,
-            bubbles: true
-        }));
-    };
-}
\ No newline at end of file
diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
new file mode 100644
index 000000000..e15620a91
--- /dev/null
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -0,0 +1,77 @@
+import { StrCast, Cast } from "../../../fields/Types";
+import HypothesisAuthenticationManager from "../HypothesisAuthenticationManager";
+import { SearchUtil } from "../../util/SearchUtil";
+import { action } from "mobx";
+import { Doc } from "../../../fields/Doc";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { WebField } from "../../../fields/URLField";
+import { DocumentManager } from "../../util/DocumentManager";
+
+export namespace Hypothesis {
+    export const fetchUser = async (apiKey: string) => {
+        const response = await fetch('https://api.hypothes.is/api/profile', {
+            headers: {
+                'Authorization': `Bearer ${apiKey}`,
+            },
+        });
+        if (response.ok) {
+            return response.json();
+        } else {
+            throw new Error('DASH: Error in fetchUser GET request');
+        }
+    };
+
+    // Send Hypothes.is client request to edit an annotation to add a Dash hyperlink
+    export const makeLink = async (title: string, url: string, annotationId: string) => {
+        const newHyperlink = `[${title}\n](${url})`;
+        document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
+            detail: { newHyperlink: newHyperlink, id: annotationId },
+            bubbles: true
+        }));
+    };
+
+    // Send Hypothes.is client request to edit an annotation to find and remove a dash hyperlink
+    export const deleteLink = async (annotationId: string, linkUrl: string) => {
+        document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", {
+            detail: { targetUrl: linkUrl, id: annotationId },
+            bubbles: true
+        }));
+    };
+
+    // Construct an URL which will scroll the web page to a specific annotation's position
+    export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => {
+        return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version
+        // return baseUrl + '#annotations:' + annotationId;
+    };
+
+    // Extract username from Hypothe.is's userId format
+    export const extractUsername = (userid: string) => {
+        const regex = new RegExp('(?<=\:)(.*?)(?=\@)/');
+        return regex.exec(userid)![0];
+    };
+
+    // Return corres
+    export const getSourceWebDoc = async (uri: string) => {
+        const results: Doc[] = [];
+        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
+            const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
+            const filteredDocs = docs.filter(doc =>
+                doc.type === DocumentType.WEB && doc.data
+            );
+            filteredDocs.forEach(doc => {
+                console.log(Cast(doc.data, WebField)?.url.href);
+                if (uri === Cast(doc.data, WebField)?.url.href) results.push(doc); // TODO check history? imperfect matches?
+            });
+        }));
+
+        // TODO: open & return new Web doc with given uri if no matching Web docs are found
+        return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined;
+    };
+
+    export const scrollToAnnotation = (annotationId: string) => {
+        document.dispatchEvent(new CustomEvent("scrollToAnnotation", {
+            detail: annotationId,
+            bubbles: true
+        }));
+    };
+}
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f1d4ec46c..529a25bd9 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -50,7 +50,6 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
 import { Networking } from "../Network";
 import { Upload } from "../../server/SharedMediaTypes";
-import { Hypothesis } from "../apis/hypothesis/HypothesisApiUtils";
 const path = require('path');
 
 export interface DocumentOptions {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 64538d015..5bf9ac2a3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -66,7 +66,7 @@ import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
 import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
 import CollectionMenu from './collections/CollectionMenu';
-import { Hypothesis } from '../apis/hypothesis/HypothesisApiUtils';
+import { Hypothesis } from '../apis/hypothesis/HypothesisUtils';
 import { SelectionManager } from '../util/SelectionManager';
 
 @observer
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 8f7a2481d..475133010 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -15,7 +15,7 @@ import { setupMoveUpEvents, emptyFunction, Utils } from '../../../Utils';
 import { DocumentView } from '../nodes/DocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import { Hypothesis } from '../../apis/hypothesis/HypothesisApiUtils';
+import { Hypothesis } from '../../apis/hypothesis/HypothesisUtils';
 import { Id } from '../../../fields/FieldSymbols';
 import { Tooltip } from '@material-ui/core';
 import { DocumentType } from '../../documents/DocumentTypes';
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 8b4dc2957..e68a85664 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,7 +17,7 @@ import { StrCast } from "../../../fields/Types";
 
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
 import { LinkManager } from "../../util/LinkManager";
-import { Hypothesis } from "../../apis/hypothesis/HypothesisApiUtils";
+import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
 import { Id } from "../../../fields/FieldSymbols";
 import { Tooltip } from "@material-ui/core";
 const higflyout = require("@hig/flyout");
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 555e58bb0..998c6798e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -42,7 +42,6 @@ import React = require("react");
 import { DocumentLinksButton } from './DocumentLinksButton';
 import { MobileInterface } from '../../../mobile/MobileInterface';
 import { LinkCreatedBox } from './LinkCreatedBox';
-import { Hypothesis } from '../../apis/hypothesis/HypothesisApiUtils';
 import { LinkDescriptionPopup } from './LinkDescriptionPopup';
 import { LinkManager } from '../../util/LinkManager';
 
-- 
cgit v1.2.3-70-g09d2


From 3e6dfbb32b9cafaafe178c8e8e427207a17325e2 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Thu, 30 Jul 2020 21:13:08 -0700
Subject: change DocumentLinksButton.StartLink to use Doc instead of
 DocumentView

---
 src/client/apis/hypothesis/HypothesisUtils.ts  | 19 ++++-----
 src/client/views/MainView.tsx                  | 45 ++++++++++++++-------
 src/client/views/nodes/DocumentLinksButton.tsx | 55 +++++++++++++++++++-------
 3 files changed, 80 insertions(+), 39 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
index a9d807976..855964bf6 100644
--- a/src/client/apis/hypothesis/HypothesisUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -3,8 +3,8 @@ import { SearchUtil } from "../../util/SearchUtil";
 import { action } from "mobx";
 import { Doc } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
-import { WebField } from "../../../fields/URLField";
-import { DocumentManager } from "../../util/DocumentManager";
+import { Docs } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
 
 export namespace Hypothesis {
 
@@ -39,20 +39,21 @@ export namespace Hypothesis {
 
     // Return corres
     export const getSourceWebDoc = async (uri: string) => {
+        const currentDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+        if (StrCast(currentDoc.data) === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
+
         const results: Doc[] = [];
         await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
             const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
             const filteredDocs = docs.filter(doc =>
-                doc.type === DocumentType.WEB && doc.data
+                doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
             );
-            filteredDocs.forEach(doc => {
-                console.log(uri, Cast(doc.data, WebField)?.url.href, uri === Cast(doc.data, WebField)?.url.href);
-                (uri === Cast(doc.data, WebField)?.url.href) && results.push(doc); // TODO check history? imperfect matches?
-            });
+            filteredDocs.forEach(doc => { uri === StrCast(doc.data) && results.push(doc); }); // TODO check history? imperfect matches?
         }));
 
-        // TODO: open & return new Web doc with given uri if no matching Web docs are found
-        return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined;
+        results.forEach(doc => console.log(doc.title, StrCast(doc.data)));
+
+        return results.length ? results[0] : Docs.Create.WebDocument(uri, { _nativeWidth: 850, _nativeHeight: 962, _width: 600 }); // create and return a new Web doc with given uri if no matching docs are found
     };
 
     export const scrollToAnnotation = (annotationId: string) => {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index dd8433866..93e4d1a6f 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,10 +55,8 @@ import { LinkDocPreview } from './nodes/LinkDocPreview';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
-import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
 import CollectionMenu from './collections/CollectionMenu';
 import { Hypothesis } from '../apis/hypothesis/HypothesisUtils';
-import { SelectionManager } from '../util/SelectionManager';
 
 @observer
 export class MainView extends React.Component {
@@ -107,19 +105,37 @@ export class MainView extends React.Component {
             });
         });
         document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to link an annotation to Dash
-            const annotationId = e.detail.id;
-            const annotationUri = e.detail.uri;
-            const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
-            console.log("sourceDoc: ", sourceDoc ? sourceDoc.title : "not found");
-
-            // TO BE FIXED, currently cannot start links from new webpages that don't exist in Dash
-            const source = sourceDoc || SelectionManager.SelectedDocuments()[0];
-            runInAction(() => {
-                DocumentLinksButton.AnnotationId = annotationId;
-                DocumentLinksButton.AnnotationUri = annotationUri;
-                DocumentLinksButton.StartLink = source;
-            });
+            if (!DocumentLinksButton.StartLink) { // starts link only if there are none already started (else, a listener in DocumentLinksButton will handle link completion)
+                const annotationId = e.detail.id;
+                const annotationUri = e.detail.uri;
+                const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
+                console.log("sourceDoc: ", sourceDoc.title);
+
+                runInAction(() => {
+                    DocumentLinksButton.AnnotationId = annotationId;
+                    DocumentLinksButton.AnnotationUri = annotationUri;
+                    DocumentLinksButton.StartLink = sourceDoc;
+                });
+            }
         });
+
+        // reaction(() => SelectionManager.SelectedDocuments(), selected => {
+        //     console.log("selection changed");
+        //     const selectedWebDocs = selected.map(docView => docView.props.Document).filter(doc => doc.type === DocumentType.WEB);
+        //     const urls = selectedWebDocs.map(doc => Cast(doc.data, WebField)?.url.href).filter(url => url !== undefined);
+        //     console.log("urls", urls);
+
+        //     const frame = document.getElementById('hyp_sidebar') as HTMLIFrameElement;
+        //     console.log("contentwindow?", frame.contentDocument);
+        //     if (frame.contentWindow) {
+        //         frame.contentWindow.postMessage("hello sidebar", window.origin);
+        //     }
+
+        //     document.dispatchEvent(new CustomEvent('showAnnotations', {
+        //         detail: urls,
+        //         bubbles: true
+        //     }));
+        // });
     }
 
     componentWillUnMount() {
@@ -623,7 +639,6 @@ export class MainView extends React.Component {
             <SettingsManager />
             <GroupManager />
             <GoogleAuthenticationManager />
-            <HypothesisAuthenticationManager />
             <DocumentDecorations />
             <CollectionMenu />
             <FormatShapePane />
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 445ab6cd4..581d84eae 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -3,6 +3,7 @@ import { Tooltip } from "@material-ui/core";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast } from "../../../fields/Doc";
+import { DocumentType } from "../../documents/DocumentTypes";
 import { emptyFunction, setupMoveUpEvents, returnFalse, Utils } from "../../../Utils";
 import { TraceMobx } from "../../../fields/util";
 import { DocUtils } from "../../documents/Documents";
@@ -34,10 +35,36 @@ interface DocumentLinksButtonProps {
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
     private _linkButton = React.createRef<HTMLDivElement>();
 
-    @observable public static StartLink: DocumentView | undefined;
+    @observable public static StartLink: Doc | undefined;
     @observable public static AnnotationId: string | undefined;
     @observable public static AnnotationUri: string | undefined;
 
+    componentDidMount() {
+        document.addEventListener("linkAnnotationToDash", this.onLinkFromAnnotation);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener("linkAnnotationToDash", this.onLinkFromAnnotation);
+    }
+
+    onLinkFromAnnotation = async (e: any) => {
+        const annotationUri = e.detail.uri;
+        const sourceDoc = DocumentLinksButton.StartLink && await Hypothesis.getSourceWebDoc(annotationUri);
+
+        this.props.View.props.Document.type === DocumentType.WEB && console.log(sourceDoc === this.props.View.props.Document,
+            sourceDoc,
+            this.props.View.props.Document,
+            sourceDoc!.title,
+            this.props.View.props.Document,
+            sourceDoc!.data,
+            this.props.View.props.Document.data);
+
+        if (sourceDoc === this.props.View.props.Document && sourceDoc !== DocumentLinksButton.StartLink) {
+            this.finishLinkClick(20, 20);
+            console.log("completed link from annotation");
+        }
+    }
+
     @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this.props.InMenu && this.props.StartLink) {
@@ -73,7 +100,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu && this.props.StartLink) {
                 //action(() => Doc.BrushDoc(this.props.View.Document));
-                DocumentLinksButton.StartLink = this.props.View;
+                DocumentLinksButton.StartLink = this.props.View.props.Document;
             } else if (!this.props.InMenu) {
                 DocumentLinksButton.EditLink = this.props.View;
                 DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
@@ -84,7 +111,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @action @undoBatch
     onLinkClick = (e: React.MouseEvent): void => {
         if (this.props.InMenu && this.props.StartLink) {
-            DocumentLinksButton.StartLink = this.props.View;
+            DocumentLinksButton.StartLink = this.props.View.props.Document;
             //action(() => Doc.BrushDoc(this.props.View.Document));
         } else if (!this.props.InMenu) {
             DocumentLinksButton.EditLink = this.props.View;
@@ -96,21 +123,20 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
             if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
-                if (DocumentLinksButton.StartLink === this.props.View) {
+                if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
                     DocumentLinksButton.StartLink = undefined;
                     DocumentLinksButton.AnnotationId = undefined;
                 } else {
-                    if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
-                        const sourceDoc = DocumentLinksButton.StartLink.props.Document;
+                    if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
+                        const sourceDoc = DocumentLinksButton.StartLink;
                         const targetDoc = this.props.View.props.Document;
                         const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
 
                         // TODO: Not currently possible to drag to complete links to annotations
                         if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                            const sourceUrl = DocumentLinksButton.AnnotationUri;
                             Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                             Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-                            Doc.GetProto(linkDoc as Doc).annotationUrl = Hypothesis.makeAnnotationUrl(DocumentLinksButton.AnnotationId, sourceUrl); // redirect web doc to this URL when following link
+                            Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
                             Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
                         }
 
@@ -139,13 +165,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @action @undoBatch
     finishLinkClick = (screenX: number, screenY: number) => {
-        if (DocumentLinksButton.StartLink === this.props.View) {
+        if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
             DocumentLinksButton.StartLink = undefined;
             DocumentLinksButton.AnnotationId = undefined;
         } else {
             if (this.props.InMenu && !!!this.props.StartLink) {
-                if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
-                    const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
+                if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
+                    const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink }, { doc: this.props.View.props.Document }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
                     // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks ().  see note above in onLInkButtonMoved
                     runInAction(() => DocumentLinksButton.StartLink!._link = this.props.View._link = linkDoc);
                     setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
@@ -153,11 +179,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
                     // if the link is to a Hypothes.is annotation
                     if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                        const sourceUrl = DocumentLinksButton.AnnotationUri; // the URL of the annotation's source web page
                         const targetDoc = this.props.View.props.Document;
                         Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                         Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-                        Doc.GetProto(linkDoc as Doc).annotationUrl = Hypothesis.makeAnnotationUrl(DocumentLinksButton.AnnotationId, sourceUrl); // redirect web doc to this URL when following link
+                        Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
                         Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
                     }
 
@@ -231,10 +256,10 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length}
 
             </div>
-            {DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
+            {DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? <div className={"documentLinksButton-endLink"}
                 style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
                 onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e.screenX, e.screenY)} /> : (null)}
-            {DocumentLinksButton.StartLink === this.props.View && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
+            {DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
                 style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
         </div>;
 
-- 
cgit v1.2.3-70-g09d2


From 2b3fb53a5e0ebe21eef2406992257ef5e86b3b2d Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Fri, 31 Jul 2020 16:03:03 -0700
Subject: modify link completing from annotations, fix web drag & drop

---
 src/client/views/MainView.tsx                      | 19 ++++++---
 src/client/views/collections/CollectionSubView.tsx |  5 ++-
 src/client/views/nodes/DocumentLinksButton.tsx     | 46 ++++++++++++----------
 3 files changed, 43 insertions(+), 27 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 93e4d1a6f..954b8b998 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -105,17 +105,26 @@ export class MainView extends React.Component {
             });
         });
         document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to link an annotation to Dash
-            if (!DocumentLinksButton.StartLink) { // starts link only if there are none already started (else, a listener in DocumentLinksButton will handle link completion)
-                const annotationId = e.detail.id;
-                const annotationUri = e.detail.uri;
-                const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
-                console.log("sourceDoc: ", sourceDoc.title);
+            const annotationId = e.detail.id;
+            const annotationUri = e.detail.uri;
+            const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
+            console.log("sourceDoc: ", sourceDoc.title);
 
+            if (!DocumentLinksButton.StartLink) { // starts link only if there are none already started (else, a listener in DocumentLinksButton will handle link completion)
                 runInAction(() => {
                     DocumentLinksButton.AnnotationId = annotationId;
                     DocumentLinksButton.AnnotationUri = annotationUri;
                     DocumentLinksButton.StartLink = sourceDoc;
                 });
+            } else { // if a link's already started in Dash, send event to DocumentLinksButton tofinish the link to the annotation
+                document.dispatchEvent(new CustomEvent<{ id: string, uri: string, sourceDoc: Doc }>("completeLinkToAnnotation", {
+                    detail: {
+                        id: annotationId,
+                        uri: annotationUri,
+                        sourceDoc: sourceDoc
+                    },
+                    bubbles: true
+                }));
             }
         });
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9f78c15eb..2957f004b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -353,8 +353,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                     title: uriList,
                     _width: 400,
                     _height: 315,
-                    _nativeWidth: 600,
-                    _nativeHeight: 472.5
+                    _nativeWidth: 850,
+                    _nativeHeight: 962,
+                    UseCors: true
                 }));
                 return;
             }
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 581d84eae..be7c3e135 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -11,13 +11,14 @@ import { DragManager } from "../../util/DragManager";
 import { LinkManager } from "../../util/LinkManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "./DocumentView";
-import { StrCast } from "../../../fields/Types";
+import { StrCast, Cast } from "../../../fields/Types";
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
 import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
 import { Id } from "../../../fields/FieldSymbols";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 import React = require("react");
 import './DocumentLinksButton.scss';
+import { WebField } from "../../../fields/URLField";
 
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -40,27 +41,30 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @observable public static AnnotationUri: string | undefined;
 
     componentDidMount() {
-        document.addEventListener("linkAnnotationToDash", this.onLinkFromAnnotation);
+        document.addEventListener("completeLinkToAnnotation", this.onLinkFromAnnotation);
     }
 
     componentWillUnmount() {
-        document.removeEventListener("linkAnnotationToDash", this.onLinkFromAnnotation);
+        document.removeEventListener("completeLinkToAnnotation", this.onLinkFromAnnotation);
     }
 
+    @action
     onLinkFromAnnotation = async (e: any) => {
-        const annotationUri = e.detail.uri;
-        const sourceDoc = DocumentLinksButton.StartLink && await Hypothesis.getSourceWebDoc(annotationUri);
-
-        this.props.View.props.Document.type === DocumentType.WEB && console.log(sourceDoc === this.props.View.props.Document,
-            sourceDoc,
-            this.props.View.props.Document,
-            sourceDoc!.title,
-            this.props.View.props.Document,
-            sourceDoc!.data,
-            this.props.View.props.Document.data);
-
-        if (sourceDoc === this.props.View.props.Document && sourceDoc !== DocumentLinksButton.StartLink) {
-            this.finishLinkClick(20, 20);
+        const annotationId: string = e.detail.id;
+        const annotationUri: string = e.detail.uri;
+        const sourceDoc: Doc = e.detail.sourceDoc;
+
+        // DocumentLinksButton.StartLink && this.props.View.props.Document.type === DocumentType.WEB && console.log(
+        //     sourceDoc.title,
+        //     this.props.View.props.Document.title,
+        //     sourceDoc.data,
+        //     this.props.View.props.Document.data,
+        //     Doc.AreProtosEqual(sourceDoc, this.props.View.props.Document));
+
+        if (Doc.AreProtosEqual(sourceDoc, this.props.View.props.Document) && sourceDoc !== DocumentLinksButton.StartLink) {
+            DocumentLinksButton.AnnotationId = annotationId;
+            DocumentLinksButton.AnnotationUri = annotationUri;
+            this.finishLinkClick(500, 100);
             console.log("completed link from annotation");
         }
     }
@@ -122,7 +126,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @action @undoBatch
     completeLink = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
-            if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
+            if (doubleTap && !this.props.StartLink) {
                 if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
                     DocumentLinksButton.StartLink = undefined;
                     DocumentLinksButton.AnnotationId = undefined;
@@ -169,7 +173,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             DocumentLinksButton.StartLink = undefined;
             DocumentLinksButton.AnnotationId = undefined;
         } else {
-            if (this.props.InMenu && !!!this.props.StartLink) {
+            if (!this.props.StartLink) {
                 if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
                     const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink }, { doc: this.props.View.props.Document }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
                     // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks ().  see note above in onLInkButtonMoved
@@ -179,11 +183,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
                     // if the link is to a Hypothes.is annotation
                     if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                        const targetDoc = this.props.View.props.Document;
+                        // figure out whether the StartLink doc or the current doc is the one to be linked to the annotation (the one NOT containing the annotation)
+                        const toBeLinked: Doc = DocumentLinksButton.AnnotationUri === Cast(DocumentLinksButton.StartLink.data, WebField)?.url.href ?
+                            this.props.View.props.Document : DocumentLinksButton.StartLink;
                         Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                         Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
                         Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                        Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
+                        Hypothesis.makeLink(StrCast(toBeLinked.title), Utils.prepend("/doc/" + toBeLinked[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
                     }
 
                     runInAction(() => {
-- 
cgit v1.2.3-70-g09d2


From 1713f5415294b996e6dbbe9ca45f0f4511c69824 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Mon, 3 Aug 2020 14:52:59 -0700
Subject: linking bug fixes, WebDocument size fixes

---
 src/client/apis/hypothesis/HypothesisUtils.ts      | 111 +++++++++++++++++----
 src/client/views/MainView.tsx                      |  43 +-------
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +-
 src/client/views/linking/LinkMenuItem.tsx          |   7 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  38 +------
 5 files changed, 103 insertions(+), 98 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
index 855964bf6..5c6e4d31d 100644
--- a/src/client/apis/hypothesis/HypothesisUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -1,15 +1,45 @@
 import { StrCast, Cast } from "../../../fields/Types";
 import { SearchUtil } from "../../util/SearchUtil";
-import { action } from "mobx";
+import { action, runInAction } from "mobx";
 import { Doc } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
-import { Docs } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { SelectionManager } from "../../util/SelectionManager";
+import { WebField } from "../../../fields/URLField";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentLinksButton } from "../../views/nodes/DocumentLinksButton";
+import { LinkManager } from "../../util/LinkManager";
+import { TaskCompletionBox } from "../../views/nodes/TaskCompletedBox";
+import { Utils } from "../../../Utils";
+import { LinkDescriptionPopup } from "../../views/nodes/LinkDescriptionPopup";
+import { Id } from "../../../fields/FieldSymbols";
 
 export namespace Hypothesis {
 
+    // Return web doc with the given uri, or create and create a new doc with the given uri
+    export const getSourceWebDoc = async (uri: string) => {
+        const currentDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+        console.log(Cast(currentDoc.data, WebField)?.url.href === uri, uri, Cast(currentDoc.data, WebField)?.url.href);
+        if (Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
+
+        const results: Doc[] = [];
+        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
+            const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
+            const filteredDocs = docs.filter(doc =>
+                doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
+            );
+            filteredDocs.forEach(doc => console.log("web docs:", doc.title, Cast(doc.data, WebField)?.url.href));
+            filteredDocs.forEach(doc => { uri === Cast(doc.data, WebField)?.url.href && results.push(doc); }); // TODO check history? imperfect matches?
+        }));
+
+        results.forEach(doc => console.log(doc.title, StrCast(doc.data)));
+
+        return results.length ? results[0] : Docs.Create.WebDocument(uri, { _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
+    };
+
     // Send Hypothes.is client request to edit an annotation to add a Dash hyperlink
     export const makeLink = async (title: string, url: string, annotationId: string) => {
+        console.log("SEND addLink");
         const newHyperlink = `[${title}\n](${url})`;
         document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
             detail: { newHyperlink: newHyperlink, id: annotationId },
@@ -25,22 +55,56 @@ export namespace Hypothesis {
         }));
     };
 
-    // Construct an URL which will automatically scroll the web page to a specific annotation's position
-    export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => {
-        console.log("baseUrl", baseUrl, annotationId);
-        return `${baseUrl}#annotations:${annotationId}`;
-    };
+    // listen for event from Hypothes.is plugin to link an annotation to Dash
+    export const linkListener = async (e: any) => {
+        const annotationId: string = e.detail.id;
+        const annotationUri: string = e.detail.uri;
+        const sourceDoc: Doc = await getSourceWebDoc(annotationUri);
+
+        if (!DocumentLinksButton.StartLink) { // start link if there were none already started 
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+                DocumentLinksButton.StartLink = sourceDoc;
+            });
+        } else if (!Doc.AreProtosEqual(sourceDoc, DocumentLinksButton.StartLink)) { // if a link has already been started, complete the link to the sourceDoc
+            console.log("completing link", sourceDoc.title);
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+            });
+
+            const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink }, { doc: sourceDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
+            LinkManager.currentLink = linkDoc;
 
-    // Extract username from Hypothe.is's userId format
-    export const extractUsername = (userid: string) => {
-        const regex = new RegExp('(?<=\:)(.*?)(?=\@)/');
-        return regex.exec(userid)![0];
+            Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
+            Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
+            Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
+            makeLink(StrCast(DocumentLinksButton.StartLink.title), Utils.prepend("/doc/" + DocumentLinksButton.StartLink[Id]), StrCast(DocumentLinksButton.AnnotationId)); // update and link placeholder annotation
+
+            runInAction(() => {
+                if (linkDoc) {
+                    TaskCompletionBox.textDisplayed = "Link Created";
+                    TaskCompletionBox.popupX = screenX;
+                    TaskCompletionBox.popupY = screenY - 133;
+                    TaskCompletionBox.taskCompleted = true;
+
+                    if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
+                        LinkDescriptionPopup.popupX = screenX;
+                        LinkDescriptionPopup.popupY = screenY - 100;
+                        LinkDescriptionPopup.descriptionPopup = true;
+                    }
+                    setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
+                }
+            });
+        }
     };
 
-    // Return corres
-    export const getSourceWebDoc = async (uri: string) => {
+    // Return web doc with the given uri, or create and create a new doc with the given uri
+    export const getSourceWebDocView = async (uri: string) => {
         const currentDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-        if (StrCast(currentDoc.data) === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
+        console.log(Cast(currentDoc.data, WebField)?.url.href === uri, uri, Cast(currentDoc.data, WebField)?.url.href);
+        if (Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
 
         const results: Doc[] = [];
         await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
@@ -48,12 +112,23 @@ export namespace Hypothesis {
             const filteredDocs = docs.filter(doc =>
                 doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
             );
-            filteredDocs.forEach(doc => { uri === StrCast(doc.data) && results.push(doc); }); // TODO check history? imperfect matches?
+            filteredDocs.forEach(doc => console.log("web docs:", doc.title, Cast(doc.data, WebField)?.url.href));
+            filteredDocs.forEach(doc => { uri === Cast(doc.data, WebField)?.url.href && results.push(doc); }); // TODO check history? imperfect matches?
         }));
 
-        results.forEach(doc => console.log(doc.title, StrCast(doc.data)));
+        results.forEach(doc => {
+            const docView = DocumentManager.Instance.getFirstDocumentView(doc);
+            if (docView) {
+                console.log(doc.title, StrCast(doc.data));
+                return docView;
+            }
+        });
+
+        return undefined;
+    };
 
-        return results.length ? results[0] : Docs.Create.WebDocument(uri, { _nativeWidth: 850, _nativeHeight: 962, _width: 600 }); // create and return a new Web doc with given uri if no matching docs are found
+    export const createInvisibleDoc = (uri: string) => {
+        const newDoc = Docs.Create.WebDocument(uri, { _nativeWidth: 0, _nativeHeight: 0, _width: 0, UseCors: true });
     };
 
     export const scrollToAnnotation = (annotationId: string) => {
@@ -71,7 +146,7 @@ export namespace Hypothesis {
                 detail: annotationId,
                 bubbles: true
             }));
-        }, 200);
+        }, 250);
 
         document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client
         setTimeout(() => !success && clearTimeout(interval), 10000); // give up if no success after 10s
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 954b8b998..5c34233b4 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -104,47 +104,7 @@ export class MainView extends React.Component {
                 }
             });
         });
-        document.addEventListener("linkAnnotationToDash", async (e: any) => {  // listen for event from Hypothes.is plugin to link an annotation to Dash
-            const annotationId = e.detail.id;
-            const annotationUri = e.detail.uri;
-            const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri);
-            console.log("sourceDoc: ", sourceDoc.title);
-
-            if (!DocumentLinksButton.StartLink) { // starts link only if there are none already started (else, a listener in DocumentLinksButton will handle link completion)
-                runInAction(() => {
-                    DocumentLinksButton.AnnotationId = annotationId;
-                    DocumentLinksButton.AnnotationUri = annotationUri;
-                    DocumentLinksButton.StartLink = sourceDoc;
-                });
-            } else { // if a link's already started in Dash, send event to DocumentLinksButton tofinish the link to the annotation
-                document.dispatchEvent(new CustomEvent<{ id: string, uri: string, sourceDoc: Doc }>("completeLinkToAnnotation", {
-                    detail: {
-                        id: annotationId,
-                        uri: annotationUri,
-                        sourceDoc: sourceDoc
-                    },
-                    bubbles: true
-                }));
-            }
-        });
-
-        // reaction(() => SelectionManager.SelectedDocuments(), selected => {
-        //     console.log("selection changed");
-        //     const selectedWebDocs = selected.map(docView => docView.props.Document).filter(doc => doc.type === DocumentType.WEB);
-        //     const urls = selectedWebDocs.map(doc => Cast(doc.data, WebField)?.url.href).filter(url => url !== undefined);
-        //     console.log("urls", urls);
-
-        //     const frame = document.getElementById('hyp_sidebar') as HTMLIFrameElement;
-        //     console.log("contentwindow?", frame.contentDocument);
-        //     if (frame.contentWindow) {
-        //         frame.contentWindow.postMessage("hello sidebar", window.origin);
-        //     }
-
-        //     document.dispatchEvent(new CustomEvent('showAnnotations', {
-        //         detail: urls,
-        //         bubbles: true
-        //     }));
-        // });
+        document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener);
     }
 
     componentWillUnMount() {
@@ -152,6 +112,7 @@ export class MainView extends React.Component {
         window.removeEventListener("pointerdown", this.globalPointerDown);
         window.removeEventListener("pointerup", this.globalPointerUp);
         window.removeEventListener("paste", KeyManager.Instance.paste as any);
+        document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener);
     }
 
     constructor(props: Readonly<{}>) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 764758eee..c3d81bda4 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -74,7 +74,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
         if (e.key === "?") {
             ContextMenu.Instance.setDefaultItem("?", (str: string) => {
                 const textDoc = Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, {
-                    _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 800, isAnnotating: false,
+                    _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false,
                     title: "bing", UseCors: true
                 });
                 this.props.addDocTab(textDoc, "onRight");
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 8503bcbeb..40a16961a 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -11,7 +11,7 @@ import { ContextMenu } from '../ContextMenu';
 import './LinkMenuItem.scss';
 import React = require("react");
 import { DocumentManager } from '../../util/DocumentManager';
-import { setupMoveUpEvents, emptyFunction, Utils } from '../../../Utils';
+import { setupMoveUpEvents, emptyFunction, Utils, simulateMouseClick } from '../../../Utils';
 import { DocumentView } from '../nodes/DocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
@@ -20,6 +20,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { Tooltip } from '@material-ui/core';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { undoBatch } from '../../util/UndoManager';
+import { WebField } from '../../../fields/URLField';
 library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
 
 
@@ -156,7 +157,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         const linkDoc = this.props.linkDoc;
 
         if (linkDoc.followLinkLocation === "openExternal" && this.props.destinationDoc.type === DocumentType.WEB) {
-            window.open(Hypothesis.makeAnnotationUrl(StrCast(linkDoc.annotationId), StrCast(linkDoc.annotationUri)), '_blank');
+            window.open(`${StrCast(linkDoc.annotationUri)}#annotations:${StrCast(linkDoc.annotationId)}`, '_blank');
             return;
         }
 
@@ -244,7 +245,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
                                     <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /></div>
                                 <p className="linkMenu-destination-title"
                                     onPointerDown={this.followDefault}>
-                                    {this.props.linkDoc.linksToAnnotation ? "Annotation in" : ""} {title}
+                                    {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? "Annotation in" : ""} {title}
                                 </p>
                             </div>
                             {this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index be7c3e135..3736cd3b2 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -40,35 +40,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @observable public static AnnotationId: string | undefined;
     @observable public static AnnotationUri: string | undefined;
 
-    componentDidMount() {
-        document.addEventListener("completeLinkToAnnotation", this.onLinkFromAnnotation);
-    }
-
-    componentWillUnmount() {
-        document.removeEventListener("completeLinkToAnnotation", this.onLinkFromAnnotation);
-    }
-
-    @action
-    onLinkFromAnnotation = async (e: any) => {
-        const annotationId: string = e.detail.id;
-        const annotationUri: string = e.detail.uri;
-        const sourceDoc: Doc = e.detail.sourceDoc;
-
-        // DocumentLinksButton.StartLink && this.props.View.props.Document.type === DocumentType.WEB && console.log(
-        //     sourceDoc.title,
-        //     this.props.View.props.Document.title,
-        //     sourceDoc.data,
-        //     this.props.View.props.Document.data,
-        //     Doc.AreProtosEqual(sourceDoc, this.props.View.props.Document));
-
-        if (Doc.AreProtosEqual(sourceDoc, this.props.View.props.Document) && sourceDoc !== DocumentLinksButton.StartLink) {
-            DocumentLinksButton.AnnotationId = annotationId;
-            DocumentLinksButton.AnnotationUri = annotationUri;
-            this.finishLinkClick(500, 100);
-            console.log("completed link from annotation");
-        }
-    }
-
     @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this.props.InMenu && this.props.StartLink) {
@@ -181,15 +152,12 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
                     LinkManager.currentLink = linkDoc;
 
-                    // if the link is to a Hypothes.is annotation
-                    if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                        // figure out whether the StartLink doc or the current doc is the one to be linked to the annotation (the one NOT containing the annotation)
-                        const toBeLinked: Doc = DocumentLinksButton.AnnotationUri === Cast(DocumentLinksButton.StartLink.data, WebField)?.url.href ?
-                            this.props.View.props.Document : DocumentLinksButton.StartLink;
+                    if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
+                        const targetDoc = this.props.View.props.Document;
                         Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                         Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
                         Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                        Hypothesis.makeLink(StrCast(toBeLinked.title), Utils.prepend("/doc/" + toBeLinked[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
+                        Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // edit annotation to add a Dash hyperlink to the linked doc
                     }
 
                     runInAction(() => {
-- 
cgit v1.2.3-70-g09d2


From bd6b5a81480f7f56a65c13954e080281279b6627 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Tue, 4 Aug 2020 16:53:36 -0700
Subject: "added invisibleDoc and simulated clicks to load annotations"

---
 src/Utils.ts                                       |   4 +-
 src/client/apis/hypothesis/HypothesisUtils.ts      | 133 +++++++++++----------
 src/client/views/MainView.tsx                      |  79 +++++++++++-
 src/client/views/collections/CollectionSubView.tsx |  26 ++--
 src/client/views/linking/LinkMenuItem.tsx          |  15 ++-
 src/client/views/nodes/DocumentLinksButton.tsx     |  14 ++-
 6 files changed, 181 insertions(+), 90 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/Utils.ts b/src/Utils.ts
index 0b057dc23..a764ebea0 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -505,7 +505,7 @@ export function clearStyleSheetRules(sheet: any) {
     return false;
 }
 
-export function simulateMouseClick(element: Element, x: number, y: number, sx: number, sy: number) {
+export function simulateMouseClick(element: Element, x: number, y: number, sx: number, sy: number, rightClick = true) {
     ["pointerdown", "pointerup"].map(event => element.dispatchEvent(
         new PointerEvent(event, {
             view: window,
@@ -519,7 +519,7 @@ export function simulateMouseClick(element: Element, x: number, y: number, sx: n
             screenY: sy,
         })));
 
-    element.dispatchEvent(
+    rightClick && element.dispatchEvent(
         new MouseEvent("contextmenu", {
             view: window,
             bubbles: true,
diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
index 5c6e4d31d..f3ff196a8 100644
--- a/src/client/apis/hypothesis/HypothesisUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -1,7 +1,7 @@
 import { StrCast, Cast } from "../../../fields/Types";
 import { SearchUtil } from "../../util/SearchUtil";
 import { action, runInAction } from "mobx";
-import { Doc } from "../../../fields/Doc";
+import { Doc, Opt } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { SelectionManager } from "../../util/SelectionManager";
@@ -10,17 +10,23 @@ import { DocumentManager } from "../../util/DocumentManager";
 import { DocumentLinksButton } from "../../views/nodes/DocumentLinksButton";
 import { LinkManager } from "../../util/LinkManager";
 import { TaskCompletionBox } from "../../views/nodes/TaskCompletedBox";
-import { Utils } from "../../../Utils";
+import { Utils, simulateMouseClick } from "../../../Utils";
 import { LinkDescriptionPopup } from "../../views/nodes/LinkDescriptionPopup";
 import { Id } from "../../../fields/FieldSymbols";
+import { DocumentView } from "../../views/nodes/DocumentView";
 
 export namespace Hypothesis {
 
-    // Return web doc with the given uri, or create and create a new doc with the given uri
     export const getSourceWebDoc = async (uri: string) => {
-        const currentDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-        console.log(Cast(currentDoc.data, WebField)?.url.href === uri, uri, Cast(currentDoc.data, WebField)?.url.href);
-        if (Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
+        const result = await findWebDoc(uri);
+        return result || Docs.Create.WebDocument(uri, { _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
+    };
+
+    // Search for a web Doc whose url field matches the given uri, return undefined if not found
+    export const findWebDoc = async (uri: string) => {
+        const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+        currentDoc && console.log(Cast(currentDoc.data, WebField)?.url.href === uri, uri, Cast(currentDoc.data, WebField)?.url.href);
+        if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
 
         const results: Doc[] = [];
         await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
@@ -34,17 +40,64 @@ export namespace Hypothesis {
 
         results.forEach(doc => console.log(doc.title, StrCast(doc.data)));
 
-        return results.length ? results[0] : Docs.Create.WebDocument(uri, { _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
+        return results.length ? results[0] : undefined;
     };
 
-    // Send Hypothes.is client request to edit an annotation to add a Dash hyperlink
-    export const makeLink = async (title: string, url: string, annotationId: string) => {
+    // Ask Hypothes.is client to edit an annotation to add a Dash hyperlink
+    export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => {
+        // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client
+        // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done
+        !DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
+
+        var success = false;
+        const onSuccess = action(() => {
+            console.log("EDITSUCCESS");
+            clearTimeout(interval);
+            DocumentLinksButton.invisibleWebDoc = undefined;
+            document.removeEventListener("editSuccess", onSuccess);
+            success = true;
+        });
+
         console.log("SEND addLink");
         const newHyperlink = `[${title}\n](${url})`;
-        document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
-            detail: { newHyperlink: newHyperlink, id: annotationId },
-            bubbles: true
-        }));
+        const interval = setInterval(() => // keep trying to scroll every 250ms until annotations have loaded and editing is successful
+            !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
+                detail: { newHyperlink: newHyperlink, id: annotationId },
+                bubbles: true
+            })), 300);
+
+        setTimeout(action(() => {
+            if (!success) {
+                clearInterval(interval);
+                DocumentLinksButton.invisibleWebDoc = undefined;
+            }
+        }), 15000); // give up if no success after 15s
+
+        document.addEventListener("editSuccess", onSuccess);
+    };
+
+    export const scrollToAnnotation = (annotationId: string, target: Doc) => {
+        var success = false;
+        const onSuccess = () => {
+            console.log("scroll success!!");
+            document.removeEventListener('scrollSuccess', onSuccess);
+            clearInterval(interval);
+            success = true;
+        };
+
+        const interval = setInterval(() => { // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful
+            console.log("send scroll");
+            document.dispatchEvent(new CustomEvent('scrollToAnnotation', {
+                detail: annotationId,
+                bubbles: true
+            }));
+            const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
+            const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
+        }, 300);
+
+        document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client
+        setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
     };
 
     // Send Hypothes.is client request to edit an annotation to find and remove a dash hyperlink
@@ -80,7 +133,7 @@ export namespace Hypothesis {
             Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
             Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
             Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-            makeLink(StrCast(DocumentLinksButton.StartLink.title), Utils.prepend("/doc/" + DocumentLinksButton.StartLink[Id]), StrCast(DocumentLinksButton.AnnotationId)); // update and link placeholder annotation
+            makeLink(StrCast(DocumentLinksButton.StartLink.title), Utils.prepend("/doc/" + DocumentLinksButton.StartLink[Id]), StrCast(DocumentLinksButton.AnnotationId), sourceDoc); // update and link placeholder annotation
 
             runInAction(() => {
                 if (linkDoc) {
@@ -99,56 +152,4 @@ export namespace Hypothesis {
             });
         }
     };
-
-    // Return web doc with the given uri, or create and create a new doc with the given uri
-    export const getSourceWebDocView = async (uri: string) => {
-        const currentDoc = SelectionManager.SelectedDocuments()[0].props.Document;
-        console.log(Cast(currentDoc.data, WebField)?.url.href === uri, uri, Cast(currentDoc.data, WebField)?.url.href);
-        if (Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the current doc is the source, only resort to Search otherwise
-
-        const results: Doc[] = [];
-        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
-            const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
-            const filteredDocs = docs.filter(doc =>
-                doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
-            );
-            filteredDocs.forEach(doc => console.log("web docs:", doc.title, Cast(doc.data, WebField)?.url.href));
-            filteredDocs.forEach(doc => { uri === Cast(doc.data, WebField)?.url.href && results.push(doc); }); // TODO check history? imperfect matches?
-        }));
-
-        results.forEach(doc => {
-            const docView = DocumentManager.Instance.getFirstDocumentView(doc);
-            if (docView) {
-                console.log(doc.title, StrCast(doc.data));
-                return docView;
-            }
-        });
-
-        return undefined;
-    };
-
-    export const createInvisibleDoc = (uri: string) => {
-        const newDoc = Docs.Create.WebDocument(uri, { _nativeWidth: 0, _nativeHeight: 0, _width: 0, UseCors: true });
-    };
-
-    export const scrollToAnnotation = (annotationId: string) => {
-        var success = false;
-        const onSuccess = () => {
-            console.log("scroll success!!");
-            document.removeEventListener('scrollSuccess', onSuccess);
-            clearTimeout(interval);
-            success = true;
-        };
-
-        const interval = setInterval(() => { // keep trying to scroll every 200ms until annotations have loaded and scrolling is successful
-            console.log("send scroll");
-            document.dispatchEvent(new CustomEvent('scrollToAnnotation', {
-                detail: annotationId,
-                bubbles: true
-            }));
-        }, 250);
-
-        document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client
-        setTimeout(() => !success && clearTimeout(interval), 10000); // give up if no success after 10s
-    };
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fccfe325a..ef44e0a4e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -14,7 +14,7 @@ import { listSpec } from '../../fields/Schema';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, FieldValue, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
-import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
+import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils, simulateMouseClick } from '../../Utils';
 import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { Docs, DocumentOptions } from '../documents/Documents';
@@ -60,6 +60,8 @@ import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { Hypothesis } from '../apis/hypothesis/HypothesisUtils';
 import { undoBatch } from '../util/UndoManager';
+import { WebBox } from './nodes/WebBox';
+import * as ReactDOM from 'react-dom';
 
 @observer
 export class MainView extends React.Component {
@@ -720,6 +722,37 @@ export class MainView extends React.Component {
         </div>;
     }
 
+    @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
+        return !DocumentLinksButton.invisibleWebDoc ? null :
+            <div style={{ position: 'absolute', left: 50, top: 50, display: 'block', width: '500px', height: '1000px' }} ref={DocumentLinksButton.invisibleWebRef}>
+                <WebBox
+                    fieldKey={"data"}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    Document={DocumentLinksButton.invisibleWebDoc}
+                    LibraryPath={emptyPath}
+                    dropAction={"move"}
+                    isSelected={returnFalse}
+                    select={returnFalse}
+                    rootSelected={returnFalse}
+                    renderDepth={0}
+                    addDocTab={returnFalse}
+                    pinToPres={returnFalse}
+                    ScreenToLocalTransform={Transform.Identity}
+                    bringToFront={returnFalse}
+                    active={returnFalse}
+                    whenActiveChanged={returnFalse}
+                    focus={returnFalse}
+                    PanelWidth={() => 500}
+                    PanelHeight={() => 800}
+                    NativeHeight={() => 500}
+                    NativeWidth={() => 800}
+                    ContentScaling={returnOne}
+                    docFilters={returnEmptyFilter}
+                />
+            </div>;
+    }
+
     render() {
         return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
 
@@ -753,8 +786,52 @@ export class MainView extends React.Component {
             <OverlayView />
             <TimelineMenu />
             {this.snapLines}
+            <div ref={this.makeWebRef} style={{ position: 'absolute', left: -1000, top: -1000, display: 'block', width: '200px', height: '800px' }} />
         </div >);
     }
+
+    makeWebRef = (ele: HTMLDivElement) => {
+        reaction(() => DocumentLinksButton.invisibleWebDoc,
+            invisibleDoc => {
+                ReactDOM.unmountComponentAtNode(ele);
+                invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
+                    <div style={{ position: 'absolute', left: -1000, top: -1000, display: 'block', width: '200px', height: '800px' }} ref={DocumentLinksButton.invisibleWebRef}>
+                        <WebBox
+                            fieldKey={"data"}
+                            ContainingCollectionView={undefined}
+                            ContainingCollectionDoc={undefined}
+                            Document={invisibleDoc}
+                            LibraryPath={emptyPath}
+                            dropAction={"move"}
+                            isSelected={returnFalse}
+                            select={returnFalse}
+                            rootSelected={returnFalse}
+                            renderDepth={0}
+                            addDocTab={returnFalse}
+                            pinToPres={returnFalse}
+                            ScreenToLocalTransform={Transform.Identity}
+                            bringToFront={returnFalse}
+                            active={returnFalse}
+                            whenActiveChanged={returnFalse}
+                            focus={returnFalse}
+                            PanelWidth={() => 500}
+                            PanelHeight={() => 800}
+                            NativeHeight={() => 500}
+                            NativeWidth={() => 800}
+                            ContentScaling={returnOne}
+                            docFilters={returnEmptyFilter}
+                        />
+                    </div>;
+                </span>, ele);
+
+                const interval = setInterval(() => {
+                    console.log("clicked");
+                    simulateMouseClick(ele, 50, 50, 50, 50);
+                }, 500);
+
+                setTimeout(() => clearInterval(interval), 10000);
+            });
+    }
 }
 Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
 Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; });
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5906282f1..b66da27b4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -346,15 +346,22 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                 // }
             }
             if (uriList) {
-                this.addDocument(Docs.Create.WebDocument(uriList, {
-                    ...options,
-                    title: uriList,
-                    _width: 400,
-                    _height: 315,
-                    _nativeWidth: 850,
-                    _nativeHeight: 962,
-                    UseCors: true
-                }));
+                const existingWebDoc = await Hypothesis.findWebDoc(uriList);
+
+                if (existingWebDoc) {
+                    this.addDocument(Doc.MakeAlias(existingWebDoc));
+                } else {
+                    const cleanedUri = uriList.split("#annotations:")[0]; // clean hypothes.is URLs that scroll directly to an annotation
+                    this.addDocument(Docs.Create.WebDocument(uriList, {
+                        ...options,
+                        title: cleanedUri,
+                        _width: 400,
+                        // _height: 315,
+                        _nativeWidth: 850,
+                        _nativeHeight: 962,
+                        UseCors: true
+                    }));
+                }
                 return;
             }
 
@@ -437,4 +444,5 @@ import { CollectionView, CollectionViewType } from "./CollectionView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { OverlayView } from "../OverlayView";
 import { setTimeout } from "timers";
+import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
 
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index b29754d45..681f6ae54 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,9 +1,9 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
-import { action, observable } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { Cast, StrCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
@@ -151,7 +151,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     }
 
     @action.bound
-    async followDefault() {
+    followDefault() {
         DocumentLinksButton.EditLink = undefined;
         LinkDocPreview.LinkInfo = undefined;
         const linkDoc = this.props.linkDoc;
@@ -167,7 +167,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
             DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
         }
 
-        linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId));
+        linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId), this.props.destinationDoc);
     }
 
     @undoBatch
@@ -176,8 +176,11 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(StrCast(this.props.linkDoc.annotationId), Utils.prepend("/doc/" + this.props.sourceDoc[Id])); // delete hyperlink in annotation
         this.props.linkDoc.linksToAnnotation && console.log("annotationId", this.props.linkDoc.annotationId);
         LinkManager.Instance.deleteLink(this.props.linkDoc);
-        LinkDocPreview.LinkInfo = undefined;
-        DocumentLinksButton.EditLink = undefined;
+
+        runInAction(() => {
+            LinkDocPreview.LinkInfo = undefined;
+            DocumentLinksButton.EditLink = undefined;
+        });
     }
 
     @undoBatch
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 00477874b..dcdaa3199 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -2,11 +2,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from "@material-ui/core";
 import { action, computed, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { emptyFunction, setupMoveUpEvents, returnFalse, Utils, emptyPath } from "../../../Utils";
 import { TraceMobx } from "../../../fields/util";
-import { DocUtils } from "../../documents/Documents";
+import { DocUtils, Docs } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
 import { LinkManager } from "../../util/LinkManager";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -18,7 +18,6 @@ import { Id } from "../../../fields/FieldSymbols";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 import React = require("react");
 import './DocumentLinksButton.scss';
-import { WebField } from "../../../fields/URLField";
 
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -40,6 +39,9 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
     @observable public static AnnotationId: string | undefined;
     @observable public static AnnotationUri: string | undefined;
 
+    @observable public static invisibleWebDoc: Opt<Doc>;
+    public static invisibleWebRef = React.createRef<HTMLDivElement>();
+
     @action @undoBatch
     onLinkButtonMoved = (e: PointerEvent) => {
         if (this.props.InMenu && this.props.StartLink) {
@@ -114,12 +116,12 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     const targetDoc = this.props.View.props.Document;
                     const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
 
-                    // TODO: Not currently possible to drag to complete links to annotations
+                    // currently possible to drag to complete links to annotations
                     if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
                         Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                         Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
                         Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                        Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
+                        Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId, sourceDoc); // update and link placeholder annotation
                     }
 
                     LinkManager.currentLink = linkDoc;
@@ -163,7 +165,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                     Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                     Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
                     Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                    Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // edit annotation to add a Dash hyperlink to the linked doc
+                    Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId, DocumentLinksButton.StartLink); // edit annotation to add a Dash hyperlink to the linked doc
                 }
 
                 if (linkDoc) {
-- 
cgit v1.2.3-70-g09d2


From 8d7eef677e03846397e49177d9821f5cdc3df6b2 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Tue, 4 Aug 2020 19:44:14 -0700
Subject: stop clicking on invisible WebDoc in MainView.tsx after editSuccess

---
 src/client/apis/hypothesis/HypothesisUtils.ts | 10 +++++-----
 src/client/views/MainView.tsx                 | 15 ++++++++++++---
 2 files changed, 17 insertions(+), 8 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
index 16f132e97..8d941cac6 100644
--- a/src/client/apis/hypothesis/HypothesisUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -50,16 +50,16 @@ export namespace Hypothesis {
 
         var success = false;
         const onSuccess = action(() => {
-            console.log("EDITSUCCESS");
+            console.log("EDIT SUCCESS");
+            success = true;
             clearTimeout(interval);
             DocumentLinksButton.invisibleWebDoc = undefined;
             document.removeEventListener("editSuccess", onSuccess);
-            success = true;
         });
 
-        console.log("SEND addLink");
+        console.log("send addLink");
         const newHyperlink = `[${title}\n](${url})`;
-        const interval = setInterval(() => // keep trying to scroll every 250ms until annotations have loaded and editing is successful
+        const interval = setInterval(() => // keep trying to edit until annotations have loaded and editing is successful
             !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
                 detail: { newHyperlink: newHyperlink, id: annotationId },
                 bubbles: true
@@ -70,7 +70,7 @@ export namespace Hypothesis {
                 clearInterval(interval);
                 DocumentLinksButton.invisibleWebDoc = undefined;
             }
-        }), 15000); // give up if no success after 15s
+        }), 12000); // give up if no success after 12s
 
         document.addEventListener("editSuccess", onSuccess);
     };
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ef44e0a4e..11ffcc734 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -824,12 +824,21 @@ export class MainView extends React.Component {
                     </div>;
                 </span>, ele);
 
-                const interval = setInterval(() => {
+                var success = false;
+                const onSuccess = () => {
+                    console.log("EDIT SUCCESS");
+                    success = true;
+                    clearTimeout(interval);
+                    document.removeEventListener("editSuccess", onSuccess);
+                };
+
+                const interval = setInterval(() => { // keep trying to click until annotations have loaded and editing is successful
                     console.log("clicked");
-                    simulateMouseClick(ele, 50, 50, 50, 50);
+                    !success && simulateMouseClick(ele, 50, 50, 50, 50);
                 }, 500);
 
-                setTimeout(() => clearInterval(interval), 10000);
+                setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
+                document.addEventListener("editSuccess", onSuccess);
             });
     }
 }
-- 
cgit v1.2.3-70-g09d2


From 6263a4539576ce92f97a02b9e7bde2804e01a6df Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Sat, 8 Aug 2020 15:28:55 -0700
Subject: clean up code, fix delete link bugs

---
 deploy/index.html                                  |   2 +-
 src/client/apis/hypothesis/HypothesisUtils.ts      | 149 +++++++++++----------
 src/client/views/MainView.tsx                      |   6 +-
 src/client/views/collections/CollectionSubView.tsx |  10 +-
 src/client/views/linking/LinkMenuItem.tsx          |   3 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |  33 +++--
 src/server/index.ts                                |   1 -
 7 files changed, 106 insertions(+), 98 deletions(-)

(limited to 'src/client/views/MainView.tsx')

diff --git a/deploy/index.html b/deploy/index.html
index 990ca510d..b99ca040d 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -9,7 +9,7 @@
     <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
 </head>
 
-<body style="display:flex">
+<body style="display:flex" id="dash-body">
     <!-- <script src="https://hypothes.is/embed.js" async></script> -->
     <div id="root" style="position:relative;width:100%;height:100%"></div>
     <script src="/bundle.js"></script>
diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
index de068c7d4..2a192c8e1 100644
--- a/src/client/apis/hypothesis/HypothesisUtils.ts
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -3,28 +3,31 @@ import { SearchUtil } from "../../util/SearchUtil";
 import { action, runInAction } from "mobx";
 import { Doc, Opt } from "../../../fields/Doc";
 import { DocumentType } from "../../documents/DocumentTypes";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { Docs } from "../../documents/Documents";
 import { SelectionManager } from "../../util/SelectionManager";
 import { WebField } from "../../../fields/URLField";
 import { DocumentManager } from "../../util/DocumentManager";
 import { DocumentLinksButton } from "../../views/nodes/DocumentLinksButton";
-import { LinkManager } from "../../util/LinkManager";
-import { TaskCompletionBox } from "../../views/nodes/TaskCompletedBox";
-import { Utils, simulateMouseClick } from "../../../Utils";
-import { LinkDescriptionPopup } from "../../views/nodes/LinkDescriptionPopup";
-import { Id } from "../../../fields/FieldSymbols";
+import { simulateMouseClick, Utils } from "../../../Utils";
 import { DocumentView } from "../../views/nodes/DocumentView";
+import { Id } from "../../../fields/FieldSymbols";
 
 export namespace Hypothesis {
 
-    // Retrieve a WebDocument with the given url exists, create and return a new 
+    /**
+     * Retrieve a WebDocument with the given url, prioritizing results that are on screen.  
+     * If none exist, create and return a new WebDocument.
+     */
     export const getSourceWebDoc = async (uri: string) => {
         const result = await findWebDoc(uri);
         console.log(result ? "existing doc found" : "existing doc NOT found");
         return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
     };
 
-    // Search for a WebDocument whose url field matches the given uri, return undefined if not found
+
+    /**
+     * Search for a WebDocument whose url field matches the given uri, return undefined if not found
+     */
     export const findWebDoc = async (uri: string) => {
         const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
         if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
@@ -36,7 +39,6 @@ export namespace Hypothesis {
                 doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
             );
             filteredDocs.forEach(doc => {
-                console.log("web docs:", doc.title, Cast(doc.data, WebField)?.url.href);
                 uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? 
             });
         }));
@@ -45,7 +47,36 @@ export namespace Hypothesis {
         return onScreenResults.length ? onScreenResults[0] : (results.length ? results[0] : undefined); // prioritize results that are currently on the screen
     };
 
-    // Ask Hypothes.is client to edit an annotation to add a Dash hyperlink
+    /**
+     * listen for event from Hypothes.is plugin to link an annotation to Dash
+     */
+    export const linkListener = async (e: any) => {
+        const annotationId: string = e.detail.id;
+        const annotationUri: string = StrCast(e.detail.uri).split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation 
+        const sourceDoc: Doc = await getSourceWebDoc(annotationUri);
+
+        if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself)
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+                DocumentLinksButton.StartLink = sourceDoc;
+            });
+        } else { // if a link has already been started, complete the link to sourceDoc
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+            });
+            const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc);
+            const rect = document.body.getBoundingClientRect();
+            const x = rect.x + rect.width / 2;
+            const y = 250;
+            DocumentLinksButton.finishLinkClick(x, y, DocumentLinksButton.StartLink, sourceDoc, false, endLinkView);
+        }
+    };
+
+    /**
+     *  Send message to Hypothes.is client to edit an annotation to add a Dash hyperlink
+     */
     export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => {
         // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client
         // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done
@@ -53,14 +84,13 @@ export namespace Hypothesis {
 
         var success = false;
         const onSuccess = action(() => {
-            console.log("EDIT SUCCESS");
+            console.log("Edit success!!");
             success = true;
             clearTimeout(interval);
             DocumentLinksButton.invisibleWebDoc = undefined;
             document.removeEventListener("editSuccess", onSuccess);
         });
 
-        console.log("send addLink");
         const newHyperlink = `[${title}\n](${url})`;
         const interval = setInterval(() => // keep trying to edit until annotations have loaded and editing is successful
             !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
@@ -73,22 +103,58 @@ export namespace Hypothesis {
                 clearInterval(interval);
                 DocumentLinksButton.invisibleWebDoc = undefined;
             }
-        }), 12000); // give up if no success after 12s
+        }), 10000); // give up if no success after 10s
+        document.addEventListener("editSuccess", onSuccess);
+    };
+
+    /**
+     * Send message Hypothes.is client request to edit an annotation to find and delete the target Dash hyperlink
+     */
+    export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => {
+        if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation
+
+        !DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
+
+        var success = false;
+        const onSuccess = action(() => {
+            console.log("Edit success!");
+            success = true;
+            clearTimeout(interval);
+            DocumentLinksButton.invisibleWebDoc = undefined;
+            document.removeEventListener("editSuccess", onSuccess);
+        });
+
+        const annotationId = StrCast(linkDoc.annotationId);
+        const linkUrl = Utils.prepend("/doc/" + sourceDoc[Id]);
+        const interval = setInterval(() => {// keep trying to edit until annotations have loaded and editing is successful
+            !success && document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", {
+                detail: { targetUrl: linkUrl, id: annotationId },
+                bubbles: true
+            }));
+        }, 300);
 
+        setTimeout(action(() => {
+            if (!success) {
+                clearInterval(interval);
+                DocumentLinksButton.invisibleWebDoc = undefined;
+            }
+        }), 10000); // give up if no success after 10s
         document.addEventListener("editSuccess", onSuccess);
     };
 
+    /**
+     *  Send message to Hypothes.is client to scroll to an annotation when it loads
+     */
     export const scrollToAnnotation = (annotationId: string, target: Doc) => {
         var success = false;
         const onSuccess = () => {
-            console.log("scroll success!!");
+            console.log("Scroll success!!");
             document.removeEventListener('scrollSuccess', onSuccess);
             clearInterval(interval);
             success = true;
         };
 
         const interval = setInterval(() => { // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful
-            console.log("send scroll");
             document.dispatchEvent(new CustomEvent('scrollToAnnotation', {
                 detail: annotationId,
                 bubbles: true
@@ -101,57 +167,4 @@ export namespace Hypothesis {
         document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client
         setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
     };
-
-    // Send Hypothes.is client request to edit an annotation to find and remove a dash hyperlink
-    export const deleteLink = async (annotationId: string, linkUrl: string) => {
-        document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", {
-            detail: { targetUrl: linkUrl, id: annotationId },
-            bubbles: true
-        }));
-    };
-
-    // listen for event from Hypothes.is plugin to link an annotation to Dash
-    export const linkListener = async (e: any) => {
-        const annotationId: string = e.detail.id;
-        const annotationUri: string = StrCast(e.detail.uri).split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation 
-        const isStart = (e.detail.isLinkStart === "true");
-        const sourceDoc: Doc = await getSourceWebDoc(annotationUri);
-
-        if (!DocumentLinksButton.StartLink) { // start link if there were none already started 
-            runInAction(() => {
-                DocumentLinksButton.AnnotationId = annotationId;
-                DocumentLinksButton.AnnotationUri = annotationUri;
-                DocumentLinksButton.StartLink = sourceDoc;
-            });
-        } else if (!Doc.AreProtosEqual(sourceDoc, DocumentLinksButton.StartLink)) { // if a link has already been started, complete the link to the sourceDoc
-            runInAction(() => {
-                DocumentLinksButton.AnnotationId = annotationId;
-                DocumentLinksButton.AnnotationUri = annotationUri;
-            });
-
-            const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink }, { doc: sourceDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
-            LinkManager.currentLink = linkDoc;
-
-            Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
-            Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-            Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-            makeLink(StrCast(DocumentLinksButton.StartLink.title), Utils.prepend("/doc/" + DocumentLinksButton.StartLink[Id]), StrCast(DocumentLinksButton.AnnotationId), sourceDoc); // update and link placeholder annotation
-
-            runInAction(() => {
-                if (linkDoc) {
-                    TaskCompletionBox.textDisplayed = "Link Created";
-                    TaskCompletionBox.popupX = 60;
-                    TaskCompletionBox.popupY = 60;
-                    TaskCompletionBox.taskCompleted = true;
-
-                    if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
-                        LinkDescriptionPopup.popupX = 60;
-                        LinkDescriptionPopup.popupY = 93;
-                        LinkDescriptionPopup.descriptionPopup = true;
-                    }
-                    setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
-                }
-            });
-        }
-    };
 }
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3cb4fbb17..8f9b9e0a0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -880,14 +880,14 @@ export class MainView extends React.Component {
 
                 var success = false;
                 const onSuccess = () => {
-                    console.log("EDIT SUCCESS");
                     success = true;
                     clearTimeout(interval);
                     document.removeEventListener("editSuccess", onSuccess);
                 };
 
-                const interval = setInterval(() => { // keep trying to click until annotations have loaded and editing is successful
-                    console.log("clicked");
+                // For some reason, Hypothes.is annotations don't load until a click is registered on the page, 
+                // so we keep simulating clicks until annotations have loaded and editing is successful
+                const interval = setInterval(() => {
                     !success && simulateMouseClick(ele, 50, 50, 50, 50);
                 }, 500);
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4d9ed358b..e045b351f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -403,7 +403,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             }
             if (uriList) {
                 const existingWebDoc = await Hypothesis.findWebDoc(uriList);
-
                 if (existingWebDoc) {
                     const alias = Doc.MakeAlias(existingWebDoc);
                     alias.x = options.x;
@@ -413,16 +412,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                     alias._width = 400;
                     this.addDocument(alias);
                 } else {
-                    const cleanedUri = uriList.split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
-                    this.addDocument(Docs.Create.WebDocument(uriList, {
+                    const newDoc = Docs.Create.WebDocument(uriList, {
                         ...options,
-                        title: cleanedUri,
+                        title: uriList.split("#annotations:")[0],
                         _width: 400,
                         _height: 315,
                         _nativeWidth: 850,
                         _nativeHeight: 962,
                         UseCors: true
-                    }));
+                    });
+                    newDoc.data = new WebField(uriList.split("#annotations:")[0]); // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
+                    this.addDocument(newDoc);
                 }
                 return;
             }
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 17c2f42bf..de9e25f48 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -173,8 +173,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
     @undoBatch
     @action
     deleteLink = (): void => {
-        this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(StrCast(this.props.linkDoc.annotationId), Utils.prepend("/doc/" + this.props.sourceDoc[Id])); // delete hyperlink in annotation
-        this.props.linkDoc.linksToAnnotation && console.log("annotationId", this.props.linkDoc.annotationId);
+        this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
         LinkManager.Instance.deleteLink(this.props.linkDoc);
 
         runInAction(() => {
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index e8f7c8e9f..1e6f663d3 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -114,15 +114,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                 } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
                     const sourceDoc = DocumentLinksButton.StartLink;
                     const targetDoc = this.props.View.props.Document;
-                    const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
-
-                    // currently possible to drag to complete links to annotations
-                    if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
-                        Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
-                        Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
-                        Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                        Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId, sourceDoc); // update and link placeholder annotation
-                    }
+                    const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "long drag");
 
                     LinkManager.currentLink = linkDoc;
 
@@ -148,24 +140,29 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
             }
         })));
     }
-    finishLinkClick = undoBatch(action((screenX: number, screenY: number) => {
-        if (DocumentLinksButton.StartLink === this.props.View.props.Document) {
+
+    public static finishLinkClick = undoBatch(action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView,) => {
+        if (startLink === endLink) {
             DocumentLinksButton.StartLink = undefined;
             DocumentLinksButton.AnnotationId = undefined;
             DocumentLinksButton.AnnotationUri = undefined;
-        } else if (!this.props.StartLink && DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) {
-            const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink }, { doc: this.props.View.props.Document }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
+            //!this.props.StartLink 
+        } else if (startLink !== endLink) {
+            const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
             // this notifies any of the subviews that a document is made so that they can make finer-grained hyperlinks ().  see note above in onLInkButtonMoved
-            DocumentLinksButton.StartLink._link = this.props.View._link = linkDoc;
-            setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
+            if (endLinkView) {
+                startLink._link = endLinkView._link = linkDoc;
+                setTimeout(action(() => startLink._link = endLinkView._link = undefined), 0);
+            }
             LinkManager.currentLink = linkDoc;
 
             if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation
-                const targetDoc = this.props.View.props.Document;
                 Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
                 Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
                 Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
-                Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId, DocumentLinksButton.StartLink); // edit annotation to add a Dash hyperlink to the linked doc
+                const dashHyperlink = Utils.prepend("/doc/" + (startIsAnnotation ? endLink[Id] : startLink[Id]));
+                Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId,
+                    (startIsAnnotation ? startLink : endLink)); // edit annotation to add a Dash hyperlink to the linked doc
             }
 
             if (linkDoc) {
@@ -247,7 +244,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
                         border: DocumentLinksButton.StartLink ? "" : "none"
                     }}
                     onPointerDown={DocumentLinksButton.StartLink ? this.completeLink : emptyFunction}
-                    onClick={e => DocumentLinksButton.StartLink ? this.finishLinkClick(e.screenX, e.screenY) : emptyFunction} /> : (null)
+                    onClick={e => DocumentLinksButton.StartLink ? DocumentLinksButton.finishLinkClick(e.screenX, e.screenY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View) : emptyFunction} /> : (null)
             }
             {
                 DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
diff --git a/src/server/index.ts b/src/server/index.ts
index 40fc5222f..97a90825d 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -8,7 +8,6 @@ import DeleteManager from "./ApiManagers/DeleteManager";
 import DownloadManager from './ApiManagers/DownloadManager';
 import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
 import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import HypothesisManager from "./ApiManagers/HypothesisManager";
 import PDFManager from "./ApiManagers/PDFManager";
 import { SearchManager } from './ApiManagers/SearchManager';
 import SessionManager from "./ApiManagers/SessionManager";
-- 
cgit v1.2.3-70-g09d2


From 1733759b776f0596c3a8561d2c735a4b7c7d54d3 Mon Sep 17 00:00:00 2001
From: Melissa Zhang <mzhang19096@gmail.com>
Date: Sat, 8 Aug 2020 15:37:26 -0700
Subject: move HypothesisUtils.ts to Utils folder

---
 src/client/apis/hypothesis/HypothesisUtils.ts      | 170 ---------------------
 src/client/util/HypothesisUtils.ts                 | 170 +++++++++++++++++++++
 src/client/views/MainView.tsx                      |   2 +-
 src/client/views/collections/CollectionSubView.tsx |   2 +-
 src/client/views/linking/LinkMenuItem.tsx          |   2 +-
 src/client/views/nodes/DocumentLinksButton.tsx     |   2 +-
 6 files changed, 174 insertions(+), 174 deletions(-)
 delete mode 100644 src/client/apis/hypothesis/HypothesisUtils.ts
 create mode 100644 src/client/util/HypothesisUtils.ts

(limited to 'src/client/views/MainView.tsx')

diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
deleted file mode 100644
index 2a192c8e1..000000000
--- a/src/client/apis/hypothesis/HypothesisUtils.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import { StrCast, Cast } from "../../../fields/Types";
-import { SearchUtil } from "../../util/SearchUtil";
-import { action, runInAction } from "mobx";
-import { Doc, Opt } from "../../../fields/Doc";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { Docs } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
-import { WebField } from "../../../fields/URLField";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DocumentLinksButton } from "../../views/nodes/DocumentLinksButton";
-import { simulateMouseClick, Utils } from "../../../Utils";
-import { DocumentView } from "../../views/nodes/DocumentView";
-import { Id } from "../../../fields/FieldSymbols";
-
-export namespace Hypothesis {
-
-    /**
-     * Retrieve a WebDocument with the given url, prioritizing results that are on screen.  
-     * If none exist, create and return a new WebDocument.
-     */
-    export const getSourceWebDoc = async (uri: string) => {
-        const result = await findWebDoc(uri);
-        console.log(result ? "existing doc found" : "existing doc NOT found");
-        return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
-    };
-
-
-    /**
-     * Search for a WebDocument whose url field matches the given uri, return undefined if not found
-     */
-    export const findWebDoc = async (uri: string) => {
-        const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
-        if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
-
-        const results: Doc[] = [];
-        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
-            const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
-            const filteredDocs = docs.filter(doc =>
-                doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
-            );
-            filteredDocs.forEach(doc => {
-                uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? 
-            });
-        }));
-
-        const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
-        return onScreenResults.length ? onScreenResults[0] : (results.length ? results[0] : undefined); // prioritize results that are currently on the screen
-    };
-
-    /**
-     * listen for event from Hypothes.is plugin to link an annotation to Dash
-     */
-    export const linkListener = async (e: any) => {
-        const annotationId: string = e.detail.id;
-        const annotationUri: string = StrCast(e.detail.uri).split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation 
-        const sourceDoc: Doc = await getSourceWebDoc(annotationUri);
-
-        if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself)
-            runInAction(() => {
-                DocumentLinksButton.AnnotationId = annotationId;
-                DocumentLinksButton.AnnotationUri = annotationUri;
-                DocumentLinksButton.StartLink = sourceDoc;
-            });
-        } else { // if a link has already been started, complete the link to sourceDoc
-            runInAction(() => {
-                DocumentLinksButton.AnnotationId = annotationId;
-                DocumentLinksButton.AnnotationUri = annotationUri;
-            });
-            const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc);
-            const rect = document.body.getBoundingClientRect();
-            const x = rect.x + rect.width / 2;
-            const y = 250;
-            DocumentLinksButton.finishLinkClick(x, y, DocumentLinksButton.StartLink, sourceDoc, false, endLinkView);
-        }
-    };
-
-    /**
-     *  Send message to Hypothes.is client to edit an annotation to add a Dash hyperlink
-     */
-    export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => {
-        // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client
-        // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done
-        !DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
-
-        var success = false;
-        const onSuccess = action(() => {
-            console.log("Edit success!!");
-            success = true;
-            clearTimeout(interval);
-            DocumentLinksButton.invisibleWebDoc = undefined;
-            document.removeEventListener("editSuccess", onSuccess);
-        });
-
-        const newHyperlink = `[${title}\n](${url})`;
-        const interval = setInterval(() => // keep trying to edit until annotations have loaded and editing is successful
-            !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
-                detail: { newHyperlink: newHyperlink, id: annotationId },
-                bubbles: true
-            })), 300);
-
-        setTimeout(action(() => {
-            if (!success) {
-                clearInterval(interval);
-                DocumentLinksButton.invisibleWebDoc = undefined;
-            }
-        }), 10000); // give up if no success after 10s
-        document.addEventListener("editSuccess", onSuccess);
-    };
-
-    /**
-     * Send message Hypothes.is client request to edit an annotation to find and delete the target Dash hyperlink
-     */
-    export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => {
-        if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation
-
-        !DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
-
-        var success = false;
-        const onSuccess = action(() => {
-            console.log("Edit success!");
-            success = true;
-            clearTimeout(interval);
-            DocumentLinksButton.invisibleWebDoc = undefined;
-            document.removeEventListener("editSuccess", onSuccess);
-        });
-
-        const annotationId = StrCast(linkDoc.annotationId);
-        const linkUrl = Utils.prepend("/doc/" + sourceDoc[Id]);
-        const interval = setInterval(() => {// keep trying to edit until annotations have loaded and editing is successful
-            !success && document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", {
-                detail: { targetUrl: linkUrl, id: annotationId },
-                bubbles: true
-            }));
-        }, 300);
-
-        setTimeout(action(() => {
-            if (!success) {
-                clearInterval(interval);
-                DocumentLinksButton.invisibleWebDoc = undefined;
-            }
-        }), 10000); // give up if no success after 10s
-        document.addEventListener("editSuccess", onSuccess);
-    };
-
-    /**
-     *  Send message to Hypothes.is client to scroll to an annotation when it loads
-     */
-    export const scrollToAnnotation = (annotationId: string, target: Doc) => {
-        var success = false;
-        const onSuccess = () => {
-            console.log("Scroll success!!");
-            document.removeEventListener('scrollSuccess', onSuccess);
-            clearInterval(interval);
-            success = true;
-        };
-
-        const interval = setInterval(() => { // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful
-            document.dispatchEvent(new CustomEvent('scrollToAnnotation', {
-                detail: annotationId,
-                bubbles: true
-            }));
-            const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
-            const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-            targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
-        }, 300);
-
-        document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client
-        setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
-    };
-}
\ No newline at end of file
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
new file mode 100644
index 000000000..9ede94e4b
--- /dev/null
+++ b/src/client/util/HypothesisUtils.ts
@@ -0,0 +1,170 @@
+import { StrCast, Cast } from "../../fields/Types";
+import { SearchUtil } from "./SearchUtil";
+import { action, runInAction } from "mobx";
+import { Doc, Opt } from "../../fields/Doc";
+import { DocumentType } from "../documents/DocumentTypes";
+import { Docs } from "../documents/Documents";
+import { SelectionManager } from "./SelectionManager";
+import { WebField } from "../../fields/URLField";
+import { DocumentManager } from "./DocumentManager";
+import { DocumentLinksButton } from "../views/nodes/DocumentLinksButton";
+import { simulateMouseClick, Utils } from "../../Utils";
+import { DocumentView } from "../views/nodes/DocumentView";
+import { Id } from "../../fields/FieldSymbols";
+
+export namespace Hypothesis {
+
+    /**
+     * Retrieve a WebDocument with the given url, prioritizing results that are on screen.  
+     * If none exist, create and return a new WebDocument.
+     */
+    export const getSourceWebDoc = async (uri: string) => {
+        const result = await findWebDoc(uri);
+        console.log(result ? "existing doc found" : "existing doc NOT found");
+        return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found
+    };
+
+
+    /**
+     * Search for a WebDocument whose url field matches the given uri, return undefined if not found
+     */
+    export const findWebDoc = async (uri: string) => {
+        const currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+        if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
+
+        const results: Doc[] = [];
+        await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => {
+            const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc));
+            const filteredDocs = docs.filter(doc =>
+                doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data
+            );
+            filteredDocs.forEach(doc => {
+                uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? 
+            });
+        }));
+
+        const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
+        return onScreenResults.length ? onScreenResults[0] : (results.length ? results[0] : undefined); // prioritize results that are currently on the screen
+    };
+
+    /**
+     * listen for event from Hypothes.is plugin to link an annotation to Dash
+     */
+    export const linkListener = async (e: any) => {
+        const annotationId: string = e.detail.id;
+        const annotationUri: string = StrCast(e.detail.uri).split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation 
+        const sourceDoc: Doc = await getSourceWebDoc(annotationUri);
+
+        if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself)
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+                DocumentLinksButton.StartLink = sourceDoc;
+            });
+        } else { // if a link has already been started, complete the link to sourceDoc
+            runInAction(() => {
+                DocumentLinksButton.AnnotationId = annotationId;
+                DocumentLinksButton.AnnotationUri = annotationUri;
+            });
+            const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc);
+            const rect = document.body.getBoundingClientRect();
+            const x = rect.x + rect.width / 2;
+            const y = 250;
+            DocumentLinksButton.finishLinkClick(x, y, DocumentLinksButton.StartLink, sourceDoc, false, endLinkView);
+        }
+    };
+
+    /**
+     *  Send message to Hypothes.is client to edit an annotation to add a Dash hyperlink
+     */
+    export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => {
+        // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client
+        // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done
+        !DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
+
+        var success = false;
+        const onSuccess = action(() => {
+            console.log("Edit success!!");
+            success = true;
+            clearTimeout(interval);
+            DocumentLinksButton.invisibleWebDoc = undefined;
+            document.removeEventListener("editSuccess", onSuccess);
+        });
+
+        const newHyperlink = `[${title}\n](${url})`;
+        const interval = setInterval(() => // keep trying to edit until annotations have loaded and editing is successful
+            !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", {
+                detail: { newHyperlink: newHyperlink, id: annotationId },
+                bubbles: true
+            })), 300);
+
+        setTimeout(action(() => {
+            if (!success) {
+                clearInterval(interval);
+                DocumentLinksButton.invisibleWebDoc = undefined;
+            }
+        }), 10000); // give up if no success after 10s
+        document.addEventListener("editSuccess", onSuccess);
+    };
+
+    /**
+     * Send message Hypothes.is client request to edit an annotation to find and delete the target Dash hyperlink
+     */
+    export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => {
+        if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation
+
+        !DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
+
+        var success = false;
+        const onSuccess = action(() => {
+            console.log("Edit success!");
+            success = true;
+            clearTimeout(interval);
+            DocumentLinksButton.invisibleWebDoc = undefined;
+            document.removeEventListener("editSuccess", onSuccess);
+        });
+
+        const annotationId = StrCast(linkDoc.annotationId);
+        const linkUrl = Utils.prepend("/doc/" + sourceDoc[Id]);
+        const interval = setInterval(() => {// keep trying to edit until annotations have loaded and editing is successful
+            !success && document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", {
+                detail: { targetUrl: linkUrl, id: annotationId },
+                bubbles: true
+            }));
+        }, 300);
+
+        setTimeout(action(() => {
+            if (!success) {
+                clearInterval(interval);
+                DocumentLinksButton.invisibleWebDoc = undefined;
+            }
+        }), 10000); // give up if no success after 10s
+        document.addEventListener("editSuccess", onSuccess);
+    };
+
+    /**
+     *  Send message to Hypothes.is client to scroll to an annotation when it loads
+     */
+    export const scrollToAnnotation = (annotationId: string, target: Doc) => {
+        var success = false;
+        const onSuccess = () => {
+            console.log("Scroll success!!");
+            document.removeEventListener('scrollSuccess', onSuccess);
+            clearInterval(interval);
+            success = true;
+        };
+
+        const interval = setInterval(() => { // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful
+            document.dispatchEvent(new CustomEvent('scrollToAnnotation', {
+                detail: annotationId,
+                bubbles: true
+            }));
+            const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
+            const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+            targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
+        }, 300);
+
+        document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client
+        setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s
+    };
+}
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 8f9b9e0a0..f5dccd567 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -58,7 +58,7 @@ import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { OverlayView } from './OverlayView';
 import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
-import { Hypothesis } from '../apis/hypothesis/HypothesisUtils';
+import { Hypothesis } from '../util/HypothesisUtils';
 import { undoBatch } from '../util/UndoManager';
 import { WebBox } from './nodes/WebBox';
 import * as ReactDOM from 'react-dom';
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e045b351f..72aece284 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -506,5 +506,5 @@ import { CollectionView, CollectionViewType } from "./CollectionView";
 import { SelectionManager } from "../../util/SelectionManager";
 import { OverlayView } from "../OverlayView";
 import { setTimeout } from "timers";
-import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
+import { Hypothesis } from "../../util/HypothesisUtils";
 
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index de9e25f48..b95fccf2a 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -15,7 +15,7 @@ import { setupMoveUpEvents, emptyFunction, Utils, simulateMouseClick } from '../
 import { DocumentView } from '../nodes/DocumentView';
 import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
-import { Hypothesis } from '../../apis/hypothesis/HypothesisUtils';
+import { Hypothesis } from '../../util/HypothesisUtils';
 import { Id } from '../../../fields/FieldSymbols';
 import { Tooltip } from '@material-ui/core';
 import { DocumentType } from '../../documents/DocumentTypes';
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 1e6f663d3..31dd33fc1 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -13,7 +13,7 @@ import { undoBatch, UndoManager } from "../../util/UndoManager";
 import { DocumentView } from "./DocumentView";
 import { StrCast, Cast } from "../../../fields/Types";
 import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
+import { Hypothesis } from "../../util/HypothesisUtils";
 import { Id } from "../../../fields/FieldSymbols";
 import { TaskCompletionBox } from "./TaskCompletedBox";
 import React = require("react");
-- 
cgit v1.2.3-70-g09d2

