diff options
Diffstat (limited to 'src/client/apis')
| -rw-r--r-- | src/client/apis/HypothesisAuthenticationManager.tsx | 160 | ||||
| -rw-r--r-- | src/client/apis/hypothesis/HypothesisUtils.ts | 154 | 
2 files changed, 154 insertions, 160 deletions
| diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx deleted file mode 100644 index c3e8d2fff..000000000 --- a/src/client/apis/HypothesisAuthenticationManager.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import MainViewModal from "../views/MainViewModal"; -import { Opt } from "../../fields/Doc"; -import { Networking } from "../Network"; -import "./HypothesisAuthenticationManager.scss"; -import { Scripting } from "../util/Scripting"; - -const prompt = "Paste authorization code here..."; - -@observer -export default class HypothesisAuthenticationManager extends React.Component<{}> { -    public static Instance: HypothesisAuthenticationManager; -    private authenticationLink: Opt<string> = undefined; -    @observable private openState = false; -    @observable private authenticationCode: Opt<string> = undefined; -    @observable private showPasteTargetState = false; -    @observable private success: Opt<boolean> = undefined; -    @observable private displayLauncher = true; -    @observable private credentials: string; -    private disposer: Opt<IReactionDisposer>; - -    private set isOpen(value: boolean) { -        runInAction(() => this.openState = value); -    } - -    private set shouldShowPasteTarget(value: boolean) { -        runInAction(() => this.showPasteTargetState = value); -    } - -    public cancel() { -        this.openState && this.resetState(0, 0); -    } - -    public fetchAccessToken = async (displayIfFound = false) => { -        const response: any = await Networking.FetchFromServer("/readHypothesisAccessToken"); -        // if this is an authentication url, activate the UI to register the new access token -        if (!response) { // new RegExp(AuthenticationUrl).test(response)) { -            this.isOpen = true; -            this.authenticationLink = response; -            return new Promise<string>(async resolve => { -                this.disposer?.(); -                this.disposer = reaction( -                    () => this.authenticationCode, -                    async authenticationCode => { -                        if (authenticationCode) { -                            this.disposer?.(); -                            Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode }); -                            runInAction(() => { -                                this.success = true; -                                this.credentials = response; -                            }); -                            this.resetState(); -                            resolve(authenticationCode); -                        } -                    } -                ); -            }); -        } - -        if (displayIfFound) { -            runInAction(() => { -                this.success = true; -                this.credentials = response; -            }); -            this.resetState(-1, -1); -            this.isOpen = true; -        } -        return response.access_token; -    } - -    resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => { -        if (!visibleForMS && !fadesOutInMS) { -            runInAction(() => { -                this.isOpen = false; -                this.success = undefined; -                this.displayLauncher = true; -                this.credentials = ""; -                this.shouldShowPasteTarget = false; -                this.authenticationCode = undefined; -            }); -            return; -        } -        this.authenticationCode = undefined; -        this.displayLauncher = false; -        this.shouldShowPasteTarget = false; -        if (visibleForMS > 0 && fadesOutInMS > 0) { -            setTimeout(action(() => { -                this.isOpen = false; -                setTimeout(action(() => { -                    this.success = undefined; -                    this.displayLauncher = true; -                    this.credentials = ""; -                }), fadesOutInMS); -            }), visibleForMS); -        } -    }); - -    constructor(props: {}) { -        super(props); -        HypothesisAuthenticationManager.Instance = this; -    } - -    private get renderPrompt() { -        return ( -            <div className={'authorize-container'}> - -                {this.displayLauncher ? <button -                    className={"dispatch"} -                    onClick={() => { -                        this.shouldShowPasteTarget = true; -                    }} -                    style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }} -                >Authorize a Hypothesis account...</button> : (null)} -                {this.showPasteTargetState ? <input -                    className={'paste-target'} -                    onChange={action(e => this.authenticationCode = e.currentTarget.value)} -                    placeholder={prompt} -                /> : (null)} -                {this.credentials ? -                    <> -                        <span -                            className={'welcome'} -                        >Welcome to Dash, {this.credentials} -                        </span> -                        <div -                            className={'disconnect'} -                            onClick={async () => { -                                await Networking.FetchFromServer("/revokeHypothesisAccessToken"); -                                this.resetState(0, 0); -                            }} -                        >Disconnect Account</div> -                    </> : (null)} -            </div> -        ); -    } - -    private get dialogueBoxStyle() { -        const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red"; -        return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 }; -    } - -    render() { -        return ( -            <MainViewModal -                isDisplayed={this.openState} -                interactive={true} -                contents={this.renderPrompt} -                // overlayDisplayedOpacity={0.9} -                dialogueBoxStyle={this.dialogueBoxStyle} -                overlayStyle={{ zIndex: 1001 }} -                closeOnExternalClick={action(() => this.isOpen = false)} -            /> -        ); -    } - -} - -Scripting.addGlobal("HypothesisAuthenticationManager", HypothesisAuthenticationManager);
\ 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..5c6e4d31d --- /dev/null +++ b/src/client/apis/hypothesis/HypothesisUtils.ts @@ -0,0 +1,154 @@ +import { StrCast, Cast } from "../../../fields/Types"; +import { SearchUtil } from "../../util/SearchUtil"; +import { action, runInAction } from "mobx"; +import { Doc } 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 } 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 }, +            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 +        })); +    }; + +    // 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; + +            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 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 | 
