aboutsummaryrefslogtreecommitdiff
path: root/src/client/apis
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/apis')
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx160
-rw-r--r--src/client/apis/hypothesis/HypothesisUtils.ts154
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