aboutsummaryrefslogtreecommitdiff
path: root/src/client/apis/hypothesis/HypothesisUtils.ts
blob: 16f132e97e0f63c908d75fb03bdb31c5dcc5c655 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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, 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, simulateMouseClick } from "../../../Utils";
import { LinkDescriptionPopup } from "../../views/nodes/LinkDescriptionPopup";
import { Id } from "../../../fields/FieldSymbols";
import { DocumentView } from "../../views/nodes/DocumentView";

export namespace Hypothesis {

    // Retrieve a WebDocument with the given url exists, create and return a new 
    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 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?
        }));

        return results.length ? results[0] : undefined;
    };

    // 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})`;
        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
    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 (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
        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;

            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 = 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);
                }
            });
        }
    };
}