diff options
-rw-r--r-- | deploy/index.html | 2 | ||||
-rw-r--r-- | src/client/apis/hypothesis/HypothesisUtils.ts | 149 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 10 | ||||
-rw-r--r-- | src/client/views/linking/LinkMenuItem.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentLinksButton.tsx | 33 | ||||
-rw-r--r-- | src/server/index.ts | 1 |
7 files changed, 106 insertions, 98 deletions
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"; |