From 2b3fb53a5e0ebe21eef2406992257ef5e86b3b2d Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Fri, 31 Jul 2020 16:03:03 -0700 Subject: modify link completing from annotations, fix web drag & drop --- src/client/views/collections/CollectionSubView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections/CollectionSubView.tsx') 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(schemaCtor: (doc: Doc) => T, moreProps?: title: uriList, _width: 400, _height: 315, - _nativeWidth: 600, - _nativeHeight: 472.5 + _nativeWidth: 850, + _nativeHeight: 962, + UseCors: true })); return; } -- cgit v1.2.3-70-g09d2 From bd6b5a81480f7f56a65c13954e080281279b6627 Mon Sep 17 00:00:00 2001 From: Melissa Zhang 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/collections/CollectionSubView.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 = 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 { ; } + @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts + return !DocumentLinksButton.invisibleWebDoc ? null : +
+ 500} + PanelHeight={() => 800} + NativeHeight={() => 500} + NativeWidth={() => 800} + ContentScaling={returnOne} + docFilters={returnEmptyFilter} + /> +
; + } + render() { return (
@@ -753,8 +786,52 @@ export class MainView extends React.Component { {this.snapLines} +
); } + + makeWebRef = (ele: HTMLDivElement) => { + reaction(() => DocumentLinksButton.invisibleWebDoc, + invisibleDoc => { + ReactDOM.unmountComponentAtNode(ele); + invisibleDoc && ReactDOM.render( +
+ 500} + PanelHeight={() => 800} + NativeHeight={() => 500} + NativeWidth={() => 800} + ContentScaling={returnOne} + docFilters={returnEmptyFilter} + /> +
; +
, 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(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 { } @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 { 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 { 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; + public static invisibleWebRef = React.createRef(); + @action @undoBatch onLinkButtonMoved = (e: PointerEvent) => { if (this.props.InMenu && this.props.StartLink) { @@ -114,12 +116,12 @@ export class DocumentLinksButton extends React.Component Date: Tue, 4 Aug 2020 19:31:24 -0700 Subject: clean hypothes.is urls, change web doc default to non-annotation mode --- src/client/apis/hypothesis/HypothesisUtils.ts | 11 +++++------ src/client/documents/Documents.ts | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'src/client/views/collections/CollectionSubView.tsx') diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts index f3ff196a8..16f132e97 100644 --- a/src/client/apis/hypothesis/HypothesisUtils.ts +++ b/src/client/apis/hypothesis/HypothesisUtils.ts @@ -17,15 +17,16 @@ 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); - 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 + 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 web Doc 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; - 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[] = []; @@ -38,8 +39,6 @@ export namespace Hypothesis { 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] : undefined; }; @@ -111,7 +110,7 @@ export namespace Hypothesis { // 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 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 diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3ee7ed87a..725dacb5d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -708,7 +708,7 @@ export namespace Docs { } export function WebDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, { _fitWidth: true, _chromeStatus: url ? "disabled" : "enabled", isAnnotating: true, _lockedTransform: true, ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, { _fitWidth: true, _chromeStatus: url ? "disabled" : "enabled", isAnnotating: false, _lockedTransform: true, ...options }); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index b66da27b4..2c382fe88 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -351,7 +351,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: if (existingWebDoc) { this.addDocument(Doc.MakeAlias(existingWebDoc)); } else { - const cleanedUri = uriList.split("#annotations:")[0]; // clean hypothes.is URLs that scroll directly to an annotation + 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, { ...options, title: cleanedUri, -- cgit v1.2.3-70-g09d2 From 8c9e41ce30e37e55265a4ea8c52cd0567af227e4 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Wed, 5 Aug 2020 15:50:13 -0700 Subject: small bug fixes --- src/client/views/collections/CollectionLinearView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 8 +++++++- src/client/views/nodes/DocumentLinksButton.tsx | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections/CollectionSubView.tsx') diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 19d1ffa7b..3cf46dbed 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -176,7 +176,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { }} onPointerDown={e => e.stopPropagation()} > - Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in" : ""} {DocumentLinksButton.StartLink.title} + Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}
{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" : diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 2c382fe88..ce3654cf2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -349,7 +349,13 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: const existingWebDoc = await Hypothesis.findWebDoc(uriList); if (existingWebDoc) { - this.addDocument(Doc.MakeAlias(existingWebDoc)); + const alias = Doc.MakeAlias(existingWebDoc); + alias.x = options.x; + alias.y = options.y; + alias._nativeWidth = 850; + alias._nativeHeight = 962; + 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, { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index dcdaa3199..3057d87e8 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -92,6 +92,8 @@ export class DocumentLinksButton extends React.Component { if (this.props.InMenu && this.props.StartLink) { + DocumentLinksButton.AnnotationId = undefined; + DocumentLinksButton.AnnotationUri = undefined; if (DocumentLinksButton.StartLink === this.props.View.props.Document) { DocumentLinksButton.StartLink = undefined; } else { @@ -152,6 +154,7 @@ export class DocumentLinksButton extends React.Component 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/collections/CollectionSubView.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 @@ - +
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(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(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 { @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 { - 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 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 ?
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/collections/CollectionSubView.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 = 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 = 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