From 901610007e7b33b1c3db3c93aa6e96dacd414256 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Tue, 7 Jul 2020 17:57:11 -0700 Subject: Follow link directly to annotations --- src/client/views/linking/LinkMenuItem.tsx | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 6af474513..76f802c0c 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -15,6 +15,7 @@ import { setupMoveUpEvents, emptyFunction } from '../../../Utils'; import { DocumentView } from '../nodes/DocumentView'; import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; +import { WebField } from '../../../fields/URLField'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt); @@ -151,6 +152,10 @@ export class LinkMenuItem extends React.Component { DocumentLinksButton.EditLink = undefined; LinkDocPreview.LinkInfo = undefined; + const redirectUrl = StrCast(this.props.linkDoc.annotationUrl, null); + redirectUrl && (this.props.destinationDoc.data = new WebField(redirectUrl)); // If the link is to an annotation, go to annotation + console.log(redirectUrl, "redirectUrl"); + if (this.props.linkDoc.follow) { if (this.props.linkDoc.follow === "Default") { DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false); -- cgit v1.2.3-70-g09d2 From 467ddce06a4cf9e3b61abf733f20c60b257c94db Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Wed, 8 Jul 2020 21:11:35 -0700 Subject: implemented much more link functionality, redirects to annotation URL when link followed --- src/client/apis/hypothesis/HypothesisApiUtils.ts | 28 ++++++++++- src/client/documents/Documents.ts | 19 -------- src/client/views/linking/LinkMenuItem.tsx | 3 +- src/client/views/nodes/DocumentLinksButton.tsx | 61 +++++++++++++++++------- src/client/views/nodes/DocumentView.tsx | 2 +- src/server/database.ts | 2 +- 6 files changed, 72 insertions(+), 43 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts index dc7e1f988..bf9f4ea99 100644 --- a/src/client/apis/hypothesis/HypothesisApiUtils.ts +++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts @@ -1,7 +1,9 @@ +import { StrCast } from "../../../fields/Types"; + export namespace Hypothesis { - export const getAnnotation = async (username: String, searchParam: String) => { + export const getAnnotation = async (username: String, searchKeyWord: String) => { const base = 'https://api.hypothes.is/api/search'; - const request = base + `?user=acct:${username}@hypothes.is&text=${searchParam}`; + const request = base + `?user=acct:${username}@hypothes.is&text=${searchKeyWord}`; console.log("DASH Querying " + request); const response = await fetch(request); if (response.ok) { @@ -11,7 +13,29 @@ export namespace Hypothesis { } }; + export const getPlaceholderId = async (username: String, searchKeyWord: String) => { + const getResponse = await Hypothesis.getAnnotation(username, searchKeyWord); + const id = getResponse.rows.length > 0 ? getResponse.rows[0].id : undefined; + return StrCast(id); + }; + + // Send request to Hypothes.is client to modify a placeholder annotation into a hyperlink to Dash + export const dispatchLinkRequest = (title: string, url: string, annotationId: string) => { + console.log("DASH dispatching linkRequest"); + document.dispatchEvent(new CustomEvent<{ url: string, title: string, id: string }>("linkRequest", { + detail: { url: url, title: title, id: annotationId }, + bubbles: true + })); + }; + + // Construct an URL which will scroll the web page to a specific annotation's position export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => { return `https://hyp.is/${annotationId}/${baseUrl}`; }; + + // export const checkValidApiKey = async (apiKey: string) => { + // const response = await fetch("https://api.hypothes.is/api/profile", { + + // }); + // }; } \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 763321a85..7b85d2e46 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -925,25 +925,6 @@ export namespace DocUtils { return linkDoc; } - export function MakeHypothesisLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", sourceAnnotationId: string, id?: string) { - const sv = DocumentManager.Instance.getDocumentView(source.doc); - if (sv && sv.props.ContainingCollectionDoc === target.doc) return; - if (target.doc === Doc.UserDoc()) return undefined; - - const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView" }, id); - linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null); - Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2?.title'); - - const sourceUrl = StrCast(source.doc.data.url); // The URL of the annotation's source web page - console.log("sourceAnnotationId, url", sourceAnnotationId, sourceUrl); - Doc.GetProto(linkDoc).annotationUrl = Hypothesis.makeAnnotationUrl(sourceAnnotationId, sourceUrl); - - Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)"); - Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)"); - - return linkDoc; - } - export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { let created: Doc | undefined; let layout: ((fieldKey: string) => string) | undefined; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 76f802c0c..9c21bca35 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -153,8 +153,7 @@ export class LinkMenuItem extends React.Component { LinkDocPreview.LinkInfo = undefined; const redirectUrl = StrCast(this.props.linkDoc.annotationUrl, null); - redirectUrl && (this.props.destinationDoc.data = new WebField(redirectUrl)); // If the link is to an annotation, go to annotation - console.log(redirectUrl, "redirectUrl"); + redirectUrl && (Doc.GetProto(this.props.destinationDoc).data = new WebField(redirectUrl)); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation if (this.props.linkDoc.follow) { if (this.props.linkDoc.follow === "Default") { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 61143a6af..839f83f78 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -1,7 +1,7 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../fields/Doc"; -import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils"; +import { emptyFunction, setupMoveUpEvents, returnFalse, Utils } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { UndoManager } from "../../util/UndoManager"; import './DocumentLinksButton.scss'; @@ -17,6 +17,7 @@ import { StrCast } from "../../../fields/Types"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { LinkManager } from "../../util/LinkManager"; +import { Hypothesis } from "../../apis/hypothesis/HypothesisApiUtils"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -35,19 +36,25 @@ export class DocumentLinksButton extends React.Component { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created - // const annotatedUrl = e.details; - // SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { - // DocumentLinksButton.StartLink = element; - // })); + // window.addEventListener("annotationCreated", (e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created + // const id = e.details; + // const source = SelectionManager.SelectedDocuments()[0]; + // runInAction(() => { + // DocumentLinksButton.AnnotationId = id; + // DocumentLinksButton.StartLink = source; + // }); // }); - window.addEventListener("fakeLinkStarted", action((e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created - console.log("Helo fake started link"); - const id = e.detail; + console.log("window", window); + window.addEventListener("fakeAnnotationCreated", async (e: any) => { // event used by Hypothes.is plugin to tell Dash when an unlinked annotation has been created + console.log("Helo fake annotation make"); + // const id = e.detail; + const id = await Hypothesis.getPlaceholderId("melissaz", "placeholder"); // delete once eventListening between client & Dash works const source = SelectionManager.SelectedDocuments()[0]; - DocumentLinksButton.AnnotationId = id; - DocumentLinksButton.StartLink = source; - })); + runInAction(() => { + DocumentLinksButton.AnnotationId = id; + DocumentLinksButton.StartLink = source; + }); + }); } @action @@ -114,9 +121,17 @@ export class DocumentLinksButton extends React.Component { LinkCreatedBox.popupX = e.screenX; @@ -139,15 +154,25 @@ export class DocumentLinksButton extends React.Component { if (DocumentLinksButton.StartLink === this.props.View) { DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.AnnotationId = undefined; console.log("reset to undefined (finisheLinkClick)"); // action((e: React.PointerEvent) => { // Doc.UnBrushDoc(this.props.View.Document); // }); } else { if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) { - const linkDoc = DocumentLinksButton.AnnotationId ? - DocUtils.MakeHypothesisLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "hypothesis annotation", DocumentLinksButton.AnnotationId) : - DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag"); + const sourceDoc = DocumentLinksButton.StartLink.props.Document; + const targetDoc = this.props.View.props.Document; + const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag"); + + // if the link is to a Hypothes.is annotation + if (DocumentLinksButton.AnnotationId) { + const sourceUrl = StrCast(sourceDoc.data.url); // the URL of the annotation's source web page + console.log("sourceAnnotationId, url", DocumentLinksButton.AnnotationId, sourceUrl); + Doc.GetProto(linkDoc as Doc).annotationUrl = Hypothesis.makeAnnotationUrl(DocumentLinksButton.AnnotationId, sourceUrl); // redirect web doc to this URL when following link + Hypothesis.dispatchLinkRequest(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc.Id), DocumentLinksButton.AnnotationId); // update and link placeholder annotation + } + LinkManager.currentLink = linkDoc; runInAction(() => { LinkCreatedBox.popupX = e.screenX; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c67267860..41308b3a4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -793,7 +793,7 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "pretend we made an annotation", event: () => { - document.dispatchEvent(new CustomEvent("fakeLinkStarted", { + document.dispatchEvent(new CustomEvent("fakeAnnotationCreated", { detail: "fakefakefakeid", bubbles: true })); diff --git a/src/server/database.ts b/src/server/database.ts index 7fbab357b..767d38350 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -417,7 +417,7 @@ export namespace Database { } /** - * Writes the @param enrichedCredentials to the database, associated + * Writes the @param hypothesisApiKey to the database, associated * with @param userId for later retrieval and updating. */ export const Write = async (userId: string, hypothesisApiKey: string) => { -- cgit v1.2.3-70-g09d2 From 1e988c7c6d927630059645ebee05261619aba7b4 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Wed, 8 Jul 2020 23:13:11 -0700 Subject: added multiple links capability, linking bug fixes --- src/client/apis/hypothesis/HypothesisApiUtils.ts | 33 +++++++++++++++++++----- src/client/views/linking/LinkMenuItem.tsx | 5 ++-- src/client/views/nodes/DocumentLinksButton.tsx | 7 +++-- src/client/views/nodes/DocumentView.tsx | 17 ------------ 4 files changed, 34 insertions(+), 28 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts index bf9f4ea99..fe35f5831 100644 --- a/src/client/apis/hypothesis/HypothesisApiUtils.ts +++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts @@ -1,7 +1,20 @@ import { StrCast } from "../../../fields/Types"; export namespace Hypothesis { - export const getAnnotation = async (username: String, searchKeyWord: String) => { + export const fetchAnnotation = async (annotationId: string) => { + const response = await fetch(`https://api.hypothes.is/api/annotations/${annotationId}`); + if (response.ok) { + return response.json(); + } else { + throw new Error('DASH: Error in fetchAnnotation GET request'); + } + }; + + /** + * Searches for annotations made by @param username that + * contain @param searchKeyWord + */ + export const searchAnnotation = async (username: String, searchKeyWord: String) => { const base = 'https://api.hypothes.is/api/search'; const request = base + `?user=acct:${username}@hypothes.is&text=${searchKeyWord}`; console.log("DASH Querying " + request); @@ -9,21 +22,29 @@ export namespace Hypothesis { if (response.ok) { return response.json(); } else { - throw new Error('DASH: Error in GET request'); + throw new Error('DASH: Error in searchAnnotation GET request'); } }; + // Find the most recent placeholder annotation created, and return its ID export const getPlaceholderId = async (username: String, searchKeyWord: String) => { - const getResponse = await Hypothesis.getAnnotation(username, searchKeyWord); + const getResponse = await Hypothesis.searchAnnotation(username, searchKeyWord); const id = getResponse.rows.length > 0 ? getResponse.rows[0].id : undefined; return StrCast(id); }; // Send request to Hypothes.is client to modify a placeholder annotation into a hyperlink to Dash - export const dispatchLinkRequest = (title: string, url: string, annotationId: string) => { + export const dispatchLinkRequest = async (title: string, url: string, annotationId: string) => { + const apiKey = "6879-GHmtDG_P2kmWNKM3hcHptEUZX3VMOUePkamCaOrJbSw"; + + const oldAnnotation = await fetchAnnotation(annotationId); + const oldText = StrCast(oldAnnotation.text); + const newHyperlink = `[${title}\n](${url})`; + const newText = oldText === "placeholder" ? newHyperlink : oldText + '\n\n' + newHyperlink; + console.log("DASH dispatching linkRequest"); - document.dispatchEvent(new CustomEvent<{ url: string, title: string, id: string }>("linkRequest", { - detail: { url: url, title: title, id: annotationId }, + document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("linkRequest", { + detail: { newText: newText, id: annotationId, apiKey: apiKey }, bubbles: true })); }; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 9c21bca35..ad3c12122 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -152,8 +152,7 @@ export class LinkMenuItem extends React.Component { DocumentLinksButton.EditLink = undefined; LinkDocPreview.LinkInfo = undefined; - const redirectUrl = StrCast(this.props.linkDoc.annotationUrl, null); - redirectUrl && (Doc.GetProto(this.props.destinationDoc).data = new WebField(redirectUrl)); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation + this.props.linkDoc.linksToAnnotation && (Doc.GetProto(this.props.destinationDoc).data = new WebField(StrCast(this.props.linkDoc.annotationUrl))); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation if (this.props.linkDoc.follow) { if (this.props.linkDoc.follow === "Default") { @@ -196,7 +195,7 @@ export class LinkMenuItem extends React.Component {

- {StrCast(this.props.destinationDoc.title)}

+ {(this.props.linkDoc.linksToAnnotation ? "Annotation in " : "") + StrCast(this.props.destinationDoc.title)}

{this.props.linkDoc.description !== "" ?

{StrCast(this.props.linkDoc.description)}

: null}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 839f83f78..32c344304 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -18,6 +18,7 @@ import { StrCast } from "../../../fields/Types"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { LinkManager } from "../../util/LinkManager"; import { Hypothesis } from "../../apis/hypothesis/HypothesisApiUtils"; +import { Id } from "../../../fields/FieldSymbols"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -128,8 +129,9 @@ export class DocumentLinksButton extends React.Component(Docu const cm = ContextMenu.Instance; if (!cm) return; - cm.addItem({ - description: "make hypothesis link", event: async () => { - const docUrl = Utils.prepend("/doc/" + this.props.Document[Id]); - const docTitle = StrCast(this.layoutDoc.title); - const getResponse = await Hypothesis.getAnnotation("melissaz", "placeholder"); - if (getResponse && getResponse.rows.length > 0) { - const annotationId = getResponse.rows[0].id; - document.dispatchEvent(new CustomEvent<{ url: string, title: string, id: string }>("linkRequest", { - detail: { url: docUrl, title: docTitle, id: annotationId }, - bubbles: true - })); - } else { - console.log("no placeholder annotation found"); - } - }, icon: "eye" - }); - cm.addItem({ description: "pretend we made an annotation", event: () => { document.dispatchEvent(new CustomEvent("fakeAnnotationCreated", { -- cgit v1.2.3-70-g09d2 From 610ddaf1425018b4b00eb1fae83930f20777bad7 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Tue, 14 Jul 2020 22:22:47 -0700 Subject: deleting a link in a document removes its corresponding hyperlink in annotation (with some regex bugs) --- src/client/apis/hypothesis/HypothesisApiUtils.ts | 51 ++++++++++++++++-------- src/client/views/linking/LinkMenuItem.tsx | 6 ++- src/client/views/nodes/DocumentLinksButton.tsx | 10 ++--- 3 files changed, 44 insertions(+), 23 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts index 83881cd89..2bffdb530 100644 --- a/src/client/apis/hypothesis/HypothesisApiUtils.ts +++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts @@ -43,27 +43,44 @@ export namespace Hypothesis { } }; - // Find the most recent placeholder annotation created, and return its ID - export const getPlaceholderId = async (searchKeyWord: string) => { - const getResponse = await Hypothesis.searchAnnotation(searchKeyWord); - const id = getResponse.rows.length > 0 ? getResponse.rows[0].id : undefined; - const uri = getResponse.rows.length > 0 ? getResponse.rows[0].uri : undefined; - return id ? { id, uri } : undefined; + export const editAnnotation = async (annotationId: string, newText: string) => { + console.log("DASH dispatching editRequest"); + const credentials = await getCredentials(); + document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editRequest", { + detail: { newText: newText, id: annotationId, apiKey: credentials.apiKey }, + bubbles: true + })); }; - // Send request to Hypothes.is client to modify a placeholder annotation into a hyperlink to Dash - export const dispatchLinkRequest = async (title: string, url: string, annotationId: string) => { - const credentials = await getCredentials(); + /** + * Edit an annotation with ID @param annotationId to add a hyperlink to a Dash document, which needs to be + * written in the format [@param title](@param url) + */ + export const makeLink = async (title: string, url: string, annotationId: string) => { const oldAnnotation = await fetchAnnotation(annotationId); const oldText = StrCast(oldAnnotation.text); const newHyperlink = `[${title}\n](${url})`; - const newText = oldText === "placeholder" ? newHyperlink : oldText + '\n\n' + newHyperlink; + const newText = oldText === "placeholder" ? newHyperlink : oldText + '\n\n' + newHyperlink; // if this is not the first link in the annotation, add link on new line + await editAnnotation(annotationId, newText); + }; - console.log("DASH dispatching linkRequest"); - document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("linkRequest", { - detail: { newText: newText, id: annotationId, apiKey: credentials.apiKey }, - bubbles: true - })); + /** + * Edit an annotation with ID @param annotationId to delete a hyperlink to the Dash document with URL @param linkUrl + */ + export const deleteLink = async (annotationId: string, linkUrl: string) => { + const annotation = await fetchAnnotation(annotationId); + const regex = new RegExp(`(\[[^[]*)\(${linkUrl.replace('/', '\/')}\)`); + const target = regex.exec(annotation.text); // use regex to extract the link to be deleted, which is written in [title](hyperlink) format + target && console.log(target); + // target && editAnnotation(annotationId, annotation.text.replace(target[0], '')); + }; + + // Finds the most recent placeholder annotation created and returns its ID + export const getPlaceholderId = async (searchKeyWord: string) => { + const getResponse = await Hypothesis.searchAnnotation(searchKeyWord); + const id = getResponse.rows.length > 0 ? getResponse.rows[0].id : undefined; + const uri = getResponse.rows.length > 0 ? getResponse.rows[0].uri : undefined; + return id ? { id, uri } : undefined; }; // Construct an URL which will scroll the web page to a specific annotation's position @@ -74,7 +91,7 @@ export namespace Hypothesis { // Extract username from Hypothe.is's userId format export const extractUsername = (userid: string) => { - const exp: RegExp = /(?<=\:)(.*?)(?=\@)/; - return exp.exec(userid)![0]; + const regex = new RegExp('(?<=\:)(.*?)(?=\@)/'); + return regex.exec(userid)![0]; }; } \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index ad3c12122..509de2745 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -11,11 +11,13 @@ import { ContextMenu } from '../ContextMenu'; import './LinkMenuItem.scss'; import React = require("react"); import { DocumentManager } from '../../util/DocumentManager'; -import { setupMoveUpEvents, emptyFunction } from '../../../Utils'; +import { setupMoveUpEvents, emptyFunction, Utils } from '../../../Utils'; import { DocumentView } from '../nodes/DocumentView'; import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import { WebField } from '../../../fields/URLField'; +import { Hypothesis } from '../../apis/hypothesis/HypothesisApiUtils'; +import { Id } from '../../../fields/FieldSymbols'; library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt); @@ -169,6 +171,8 @@ export class LinkMenuItem extends React.Component { @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); LinkManager.Instance.deleteLink(this.props.linkDoc); //this.props.showLinks(); LinkDocPreview.LinkInfo = undefined; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index ab97e8531..6d439e379 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -40,7 +40,7 @@ export class DocumentLinksButton extends React.Component { if (e.origin === "http://localhost:1050" && e.data.message === "annotation created") { - console.log("DASH RECEIVED MESSAGE:", e.data.message); + console.log("DASH received message: annotation created"); const response = await Hypothesis.getPlaceholderId("placeholder"); // delete once eventListening between client & Dash works const source = SelectionManager.SelectedDocuments()[0]; response && runInAction(() => { @@ -110,7 +110,6 @@ export class DocumentLinksButton extends React.Component) => { // Doc.UnBrushDoc(this.props.View.Document); // }); @@ -124,8 +123,9 @@ export class DocumentLinksButton extends React.Component) => { // Doc.UnBrushDoc(this.props.View.Document); // }); @@ -165,8 +164,9 @@ export class DocumentLinksButton extends React.Component Date: Thu, 23 Jul 2020 13:20:12 -0700 Subject: adjustments to link following/editing behavior --- src/client/apis/hypothesis/HypothesisApiUtils.ts | 22 ++++++++++++++++- src/client/views/linking/LinkMenuItem.tsx | 4 +++- src/client/views/nodes/DocumentLinksButton.tsx | 30 +++++++++++++++--------- 3 files changed, 43 insertions(+), 13 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts index 2bffdb530..4c9fef45c 100644 --- a/src/client/apis/hypothesis/HypothesisApiUtils.ts +++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts @@ -1,5 +1,9 @@ -import { StrCast } from "../../../fields/Types"; +import { StrCast, Cast } from "../../../fields/Types"; import HypothesisAuthenticationManager from "../HypothesisAuthenticationManager"; +import { SearchUtil } from "../../util/SearchUtil"; +import { action } from "mobx"; +import { Doc } from "../../../fields/Doc"; +import { DocumentType } from "../../documents/DocumentTypes"; export namespace Hypothesis { @@ -94,4 +98,20 @@ export namespace Hypothesis { const regex = new RegExp('(?<=\:)(.*?)(?=\@)/'); return regex.exec(userid)![0]; }; + + // Return corres + export const getWebDocs = async (uri: string) => { + const results: Doc[] = []; + await SearchUtil.Search(uri, 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.type === DocumentType.WEB && doc.data); + + console.log("docs", docs); + console.log("FILTEREDDOCS: ", filteredDocs); + filteredDocs.forEach(doc => { + results.push(doc); + }); + })); + return results; + }; } \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 509de2745..30571a165 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -154,7 +154,7 @@ export class LinkMenuItem extends React.Component { DocumentLinksButton.EditLink = undefined; LinkDocPreview.LinkInfo = undefined; - this.props.linkDoc.linksToAnnotation && (Doc.GetProto(this.props.destinationDoc).data = new WebField(StrCast(this.props.linkDoc.annotationUrl))); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation + // this.props.linkDoc.linksToAnnotation && (Doc.GetProto(this.props.destinationDoc).data = new WebField(StrCast(this.props.linkDoc.annotationUrl))); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation if (this.props.linkDoc.follow) { if (this.props.linkDoc.follow === "Default") { @@ -167,6 +167,8 @@ export class LinkMenuItem extends React.Component { } else { DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false); } + + this.props.linkDoc.linksToAnnotation && setTimeout(() => window.open(StrCast(this.props.linkDoc.annotationUrl), '_blank'), 1000); // open external page if hypothes.is annotation } @action diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index d753edbeb..4c60d03fd 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -60,10 +60,18 @@ export class DocumentLinksButton extends React.Component { // event used by hypothes.is plugin to tell Dash which annotation to link from + linkAnnotation = async (e: any) => { // event sent by hypothes.is plugin to tell Dash which annotation we're linking from const annotationId = e.detail.id; - const sourceUrl = e.detail.uri; - console.log(annotationId, sourceUrl); + const annotationUri = e.detail.uri; + console.log(annotationId, annotationUri); + // const source = await Hypothesis.getWebDoc(annotationUri); + + const source = SelectionManager.SelectedDocuments()[0]; // TO BE FIXED, currently link just starts from whichever doc is selected + runInAction(() => { + DocumentLinksButton.AnnotationId = annotationId; + DocumentLinksButton.AnnotationUri = annotationUri; + DocumentLinksButton.StartLink = source; + }); } @action @@ -133,14 +141,14 @@ export class DocumentLinksButton extends React.Component { -- cgit v1.2.3-70-g09d2 From 7157310f1b4d7a9f409c46f35c10c8db4a66b0af Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Tue, 28 Jul 2020 19:00:02 -0700 Subject: scroll to annotations within dash, handle annotation editing in client --- src/client/apis/hypothesis/HypothesisApiUtils.ts | 22 +++++++++++----------- src/client/views/MainView.tsx | 19 +++---------------- src/client/views/linking/LinkMenuItem.tsx | 4 ++-- 3 files changed, 16 insertions(+), 29 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts index 1158dcc11..e2fb91af9 100644 --- a/src/client/apis/hypothesis/HypothesisApiUtils.ts +++ b/src/client/apis/hypothesis/HypothesisApiUtils.ts @@ -5,6 +5,7 @@ import { action } from "mobx"; import { Doc } from "../../../fields/Doc"; import { DocumentType } from "../../documents/DocumentTypes"; import { WebField } from "../../../fields/URLField"; +import { DocumentManager } from "../../util/DocumentManager"; export namespace Hypothesis { @@ -49,9 +50,9 @@ export namespace Hypothesis { }; export const editAnnotation = async (annotationId: string, newText: string) => { - console.log("DASH dispatching editRequest"); + console.log("DASH dispatching editAnnotation"); const credentials = await getCredentials(); - document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editRequest", { + document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editAnnotation", { detail: { newText: newText, id: annotationId, apiKey: credentials.apiKey }, bubbles: true })); @@ -76,14 +77,6 @@ export namespace Hypothesis { editAnnotation(annotationId, out); }; - // Finds the most recent placeholder annotation created and returns its ID - export const getPlaceholderId = async (searchKeyWord: string) => { - const getResponse = await Hypothesis.searchAnnotation(searchKeyWord); - const id = getResponse.rows.length > 0 ? getResponse.rows[0].id : undefined; - const uri = getResponse.rows.length > 0 ? getResponse.rows[0].uri : undefined; - return id ? { id, uri } : undefined; - }; - // Construct an URL which will scroll the web page to a specific annotation's position export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => { return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version @@ -111,6 +104,13 @@ export namespace Hypothesis { })); // TODO: open & return new Web doc with given uri if no matching Web docs are found - return results.length ? results[0] : undefined; + return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined; + }; + + export const scrollToAnnotation = (annotationId: string) => { + document.dispatchEvent(new CustomEvent("scrollToAnnotation", { + detail: annotationId, + bubbles: true + })); }; } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 605b81506..64538d015 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -115,27 +115,14 @@ export class MainView extends React.Component { } }); }); - window.addEventListener("message", async (e: any) => { // listen for a new Hypothes.is annotation from an iframe inside Dash - // start link from new Hypothes.is annotation - // TODO: pass in placeholderId directly from client, move - if (e.origin === "http://localhost:1050" && e.data.message === "annotation created") { - console.log("DASH received message: annotation created"); - const response = await Hypothesis.getPlaceholderId("placeholder"); - const source = SelectionManager.SelectedDocuments()[0]; - response && runInAction(() => { - DocumentLinksButton.AnnotationId = response.id; - DocumentLinksButton.AnnotationUri = response.uri; - DocumentLinksButton.StartLink = source; - }); - } - }); - document.addEventListener("linkAnnotationToDash", async (e: any) => { // listen for event from Hypothes.is plugin to to link an existing annotation to Dash + document.addEventListener("linkAnnotationToDash", async (e: any) => { // listen for event from Hypothes.is plugin to link an annotation to Dash const annotationId = e.detail.id; const annotationUri = e.detail.uri; const sourceDoc = await Hypothesis.getSourceWebDoc(annotationUri); console.log("sourceDoc: ", sourceDoc ? sourceDoc.title : "not found"); - const source = SelectionManager.SelectedDocuments()[0]; // TO BE FIXED, currently link just starts from whichever doc is selected + // TO BE FIXED, currently cannot start links from new webpages that don't exist in Dash + const source = sourceDoc || SelectionManager.SelectedDocuments()[0]; runInAction(() => { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index cae90cd0c..8f7a2481d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -154,14 +154,14 @@ export class LinkMenuItem extends React.Component { DocumentLinksButton.EditLink = undefined; LinkDocPreview.LinkInfo = undefined; - // this.props.linkDoc.linksToAnnotation && (Doc.GetProto(this.props.destinationDoc).data = new WebField(StrCast(this.props.linkDoc.annotationUrl))); // if destination is a Hypothes.is annotation, redirect website to the annotation's URL to scroll to the annotation if (this.props.linkDoc.followLinkLocation && this.props.linkDoc.followLinkLocation !== "Default") { this.props.addDocTab(this.props.destinationDoc, StrCast(this.props.linkDoc.followLinkLocation)); } else { DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false); } - this.props.linkDoc.linksToAnnotation && setTimeout(() => window.open(StrCast(this.props.linkDoc.annotationUrl), '_blank'), 1000); // open external page if hypothes.is annotation + // this.props.linkDoc.linksToAnnotation && setTimeout(() => window.open(StrCast(this.props.linkDoc.annotationUrl), '_blank'), 1000); // open external page if hypothes.is annotation + this.props.linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId)); } @undoBatch -- cgit v1.2.3-70-g09d2 From fabb86b78874a38619d821b315eaf9ce204e1afe Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Tue, 28 Jul 2020 21:27:38 -0700 Subject: "handle editing annotations in client, bypass authorization issues" --- .../apis/HypothesisAuthenticationManager.tsx | 2 +- src/client/apis/hypothesis/HypothesisApiUtils.ts | 116 --------------------- src/client/apis/hypothesis/HypothesisUtils.ts | 77 ++++++++++++++ src/client/documents/Documents.ts | 1 - src/client/views/MainView.tsx | 2 +- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 1 - 8 files changed, 81 insertions(+), 122 deletions(-) delete mode 100644 src/client/apis/hypothesis/HypothesisApiUtils.ts create mode 100644 src/client/apis/hypothesis/HypothesisUtils.ts (limited to 'src/client/views/linking') diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx index d4a81b3eb..653f21a7a 100644 --- a/src/client/apis/HypothesisAuthenticationManager.tsx +++ b/src/client/apis/HypothesisAuthenticationManager.tsx @@ -6,7 +6,7 @@ import { Opt } from "../../fields/Doc"; import { Networking } from "../Network"; import "./HypothesisAuthenticationManager.scss"; import { Scripting } from "../util/Scripting"; -import { Hypothesis } from "./hypothesis/HypothesisApiUtils"; +import { Hypothesis } from "./hypothesis/HypothesisUtils"; const prompt = "Paste authorization code here..."; diff --git a/src/client/apis/hypothesis/HypothesisApiUtils.ts b/src/client/apis/hypothesis/HypothesisApiUtils.ts deleted file mode 100644 index e2fb91af9..000000000 --- a/src/client/apis/hypothesis/HypothesisApiUtils.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { StrCast, Cast } from "../../../fields/Types"; -import HypothesisAuthenticationManager from "../HypothesisAuthenticationManager"; -import { SearchUtil } from "../../util/SearchUtil"; -import { action } from "mobx"; -import { Doc } from "../../../fields/Doc"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { WebField } from "../../../fields/URLField"; -import { DocumentManager } from "../../util/DocumentManager"; - -export namespace Hypothesis { - - const getCredentials = async () => HypothesisAuthenticationManager.Instance.fetchAccessToken(); - - export const fetchAnnotation = async (annotationId: string) => { - const response = await fetch(`https://api.hypothes.is/api/annotations/${annotationId}`); - if (response.ok) { - return response.json(); - } else { - throw new Error('DASH: Error in fetchAnnotation GET request'); - } - }; - - /** - * Searches for annotations authored by the current user that contain @param searchKeyWord - */ - export const searchAnnotation = async (searchKeyWord: string) => { - const credentials = await getCredentials(); - const base = 'https://api.hypothes.is/api/search'; - const request = base + `?user=acct:${credentials.username}@hypothes.is&text=${searchKeyWord}`; - console.log("DASH Querying " + request); - const response = await fetch(request); - if (response.ok) { - return response.json(); - } else { - throw new Error('DASH: Error in searchAnnotation GET request'); - } - }; - - export const fetchUser = async (apiKey: string) => { - const response = await fetch('https://api.hypothes.is/api/profile', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - }, - }); - if (response.ok) { - return response.json(); - } else { - throw new Error('DASH: Error in fetchUser GET request'); - } - }; - - export const editAnnotation = async (annotationId: string, newText: string) => { - console.log("DASH dispatching editAnnotation"); - const credentials = await getCredentials(); - document.dispatchEvent(new CustomEvent<{ newText: string, id: string, apiKey: string }>("editAnnotation", { - detail: { newText: newText, id: annotationId, apiKey: credentials.apiKey }, - bubbles: true - })); - }; - - /** - * Edit an annotation with ID @param annotationId to add a hyperlink to a Dash document, which needs to be - * written in the format [@param title](@param url) - */ - export const makeLink = async (title: string, url: string, annotationId: string) => { - const oldAnnotation = await fetchAnnotation(annotationId); - const oldText = StrCast(oldAnnotation.text); - const newHyperlink = `[${title}\n](${url})`; - const newText = oldText === "placeholder" ? newHyperlink : oldText + '\n\n' + newHyperlink; // if this is not the first link in the annotation, add link on new line - await editAnnotation(annotationId, newText); - }; - - export const deleteLink = async (annotationId: string, linkUrl: string) => { - const annotation = await fetchAnnotation(annotationId); - const regex = new RegExp(`\\[[^\\]]*\\]\\(${linkUrl}\\)`); // finds the link (written in [title](hyperlink) format) to be deleted - const out = annotation.text.replace(regex, ""); - editAnnotation(annotationId, out); - }; - - // Construct an URL which will scroll the web page to a specific annotation's position - export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => { - return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version - // return baseUrl + '#annotations:' + annotationId; - }; - - // Extract username from Hypothe.is's userId format - export const extractUsername = (userid: string) => { - const regex = new RegExp('(?<=\:)(.*?)(?=\@)/'); - return regex.exec(userid)![0]; - }; - - // Return corres - export const getSourceWebDoc = async (uri: string) => { - 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.type === DocumentType.WEB && doc.data - ); - filteredDocs.forEach(doc => { - console.log(Cast(doc.data, WebField)?.url.href); - if (uri === Cast(doc.data, WebField)?.url.href) results.push(doc); // TODO check history? imperfect matches? - }); - })); - - // TODO: open & return new Web doc with given uri if no matching Web docs are found - return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined; - }; - - export const scrollToAnnotation = (annotationId: string) => { - document.dispatchEvent(new CustomEvent("scrollToAnnotation", { - detail: annotationId, - bubbles: true - })); - }; -} \ 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..e15620a91 --- /dev/null +++ b/src/client/apis/hypothesis/HypothesisUtils.ts @@ -0,0 +1,77 @@ +import { StrCast, Cast } from "../../../fields/Types"; +import HypothesisAuthenticationManager from "../HypothesisAuthenticationManager"; +import { SearchUtil } from "../../util/SearchUtil"; +import { action } from "mobx"; +import { Doc } from "../../../fields/Doc"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { WebField } from "../../../fields/URLField"; +import { DocumentManager } from "../../util/DocumentManager"; + +export namespace Hypothesis { + export const fetchUser = async (apiKey: string) => { + const response = await fetch('https://api.hypothes.is/api/profile', { + headers: { + 'Authorization': `Bearer ${apiKey}`, + }, + }); + if (response.ok) { + return response.json(); + } else { + throw new Error('DASH: Error in fetchUser GET request'); + } + }; + + // Send Hypothes.is client request to edit an annotation to add a Dash hyperlink + export const makeLink = async (title: string, url: string, annotationId: string) => { + 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 + })); + }; + + // Construct an URL which will scroll the web page to a specific annotation's position + export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => { + return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version + // return baseUrl + '#annotations:' + annotationId; + }; + + // Extract username from Hypothe.is's userId format + export const extractUsername = (userid: string) => { + const regex = new RegExp('(?<=\:)(.*?)(?=\@)/'); + return regex.exec(userid)![0]; + }; + + // Return corres + export const getSourceWebDoc = async (uri: string) => { + 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.type === DocumentType.WEB && doc.data + ); + filteredDocs.forEach(doc => { + console.log(Cast(doc.data, WebField)?.url.href); + if (uri === Cast(doc.data, WebField)?.url.href) results.push(doc); // TODO check history? imperfect matches? + }); + })); + + // TODO: open & return new Web doc with given uri if no matching Web docs are found + return results.length ? DocumentManager.Instance.getFirstDocumentView(results[0]) : undefined; + }; + + export const scrollToAnnotation = (annotationId: string) => { + document.dispatchEvent(new CustomEvent("scrollToAnnotation", { + detail: annotationId, + bubbles: true + })); + }; +} \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f1d4ec46c..529a25bd9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -50,7 +50,6 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { Networking } from "../Network"; import { Upload } from "../../server/SharedMediaTypes"; -import { Hypothesis } from "../apis/hypothesis/HypothesisApiUtils"; const path = require('path'); export interface DocumentOptions { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 64538d015..5bf9ac2a3 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -66,7 +66,7 @@ import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane"; import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager'; import CollectionMenu from './collections/CollectionMenu'; -import { Hypothesis } from '../apis/hypothesis/HypothesisApiUtils'; +import { Hypothesis } from '../apis/hypothesis/HypothesisUtils'; import { SelectionManager } from '../util/SelectionManager'; @observer diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 8f7a2481d..475133010 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -15,7 +15,7 @@ import { setupMoveUpEvents, emptyFunction, Utils } from '../../../Utils'; import { DocumentView } from '../nodes/DocumentView'; import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; -import { Hypothesis } from '../../apis/hypothesis/HypothesisApiUtils'; +import { Hypothesis } from '../../apis/hypothesis/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 8b4dc2957..e68a85664 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -17,7 +17,7 @@ import { StrCast } from "../../../fields/Types"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { LinkManager } from "../../util/LinkManager"; -import { Hypothesis } from "../../apis/hypothesis/HypothesisApiUtils"; +import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils"; import { Id } from "../../../fields/FieldSymbols"; import { Tooltip } from "@material-ui/core"; const higflyout = require("@hig/flyout"); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 555e58bb0..998c6798e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -42,7 +42,6 @@ import React = require("react"); import { DocumentLinksButton } from './DocumentLinksButton'; import { MobileInterface } from '../../../mobile/MobileInterface'; import { LinkCreatedBox } from './LinkCreatedBox'; -import { Hypothesis } from '../../apis/hypothesis/HypothesisApiUtils'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; import { LinkManager } from '../../util/LinkManager'; -- cgit v1.2.3-70-g09d2 From 5b348d21a79a7ecd9ac30d808edaa03c3f26dfef Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Thu, 30 Jul 2020 10:37:16 -0700 Subject: added option to follow link to external page --- src/client/apis/hypothesis/HypothesisUtils.ts | 5 ++--- src/client/views/linking/LinkEditor.tsx | 6 ++++++ src/client/views/linking/LinkMenuItem.tsx | 13 +++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts index e245552b1..8eaad2905 100644 --- a/src/client/apis/hypothesis/HypothesisUtils.ts +++ b/src/client/apis/hypothesis/HypothesisUtils.ts @@ -38,10 +38,9 @@ export namespace Hypothesis { })); }; - // Construct an URL which will scroll the web page to a specific annotation's position + // Construct an URL which will automatically scroll the web page to a specific annotation's position export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => { - return `https://hyp.is/${annotationId}/${baseUrl}`; // embeds the generic version of Hypothes.is client, not the Dash version - // return baseUrl + '#annotations:' + annotationId; + return `${baseUrl}#annotations:${annotationId}`; }; // Extract username from Hypothe.is's userId format diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 04329182e..737e0ca28 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -395,6 +395,12 @@ export class LinkEditor extends React.Component { onPointerDown={() => this.changeFollowBehavior("inTab")}> Always open in new tab + {this.props.linkDoc.linksToAnnotation ? +
this.changeFollowBehavior("openExternal")}> + Always open in external page +
+ : null} ; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 475133010..079e130ea 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -153,15 +153,20 @@ export class LinkMenuItem extends React.Component { async followDefault() { DocumentLinksButton.EditLink = undefined; LinkDocPreview.LinkInfo = undefined; + const linkDoc = this.props.linkDoc; - if (this.props.linkDoc.followLinkLocation && this.props.linkDoc.followLinkLocation !== "Default") { - this.props.addDocTab(this.props.destinationDoc, StrCast(this.props.linkDoc.followLinkLocation)); + if (linkDoc.followLinkLocation === "openExternal" && this.props.destinationDoc.type === DocumentType.WEB) { + window.open(Hypothesis.makeAnnotationUrl(StrCast(this.props.linkDoc.annotationId), '_blank')); + return; + } + + if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "Default") { + this.props.addDocTab(this.props.destinationDoc, StrCast(linkDoc.followLinkLocation)); } else { DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false); } - // this.props.linkDoc.linksToAnnotation && setTimeout(() => window.open(StrCast(this.props.linkDoc.annotationUrl), '_blank'), 1000); // open external page if hypothes.is annotation - this.props.linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId)); + linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId)); } @undoBatch -- cgit v1.2.3-70-g09d2 From ba8549220295d03fb7eb7d7d31c90af72b30b1a6 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Thu, 30 Jul 2020 10:38:57 -0700 Subject: remove unnecessary authentication/database stuff --- .../apis/HypothesisAuthenticationManager.tsx | 164 --------------------- src/client/apis/hypothesis/HypothesisUtils.ts | 18 +-- src/client/util/CurrentUserUtils.ts | 1 - src/client/util/SettingsManager.tsx | 6 - src/client/views/GlobalKeyHandler.ts | 2 - src/client/views/linking/LinkMenuItem.tsx | 2 +- src/server/ApiManagers/HypothesisManager.ts | 40 ----- src/server/ApiManagers/UploadManager.ts | 1 - src/server/apis/google/GoogleApiServerUtils.ts | 1 - src/server/database.ts | 38 ----- src/server/index.ts | 2 - 11 files changed, 4 insertions(+), 271 deletions(-) delete mode 100644 src/client/apis/HypothesisAuthenticationManager.tsx delete mode 100644 src/server/ApiManagers/HypothesisManager.ts (limited to 'src/client/views/linking') diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx deleted file mode 100644 index 653f21a7a..000000000 --- a/src/client/apis/HypothesisAuthenticationManager.tsx +++ /dev/null @@ -1,164 +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"; -import { Hypothesis } from "./hypothesis/HypothesisUtils"; - -const prompt = "Paste authorization code here..."; - -@observer -export default class HypothesisAuthenticationManager extends React.Component<{}> { - public static Instance: HypothesisAuthenticationManager; - private authenticationLink: Opt = undefined; - @observable private openState = false; - @observable private authenticationCode: Opt = undefined; - @observable private showPasteTargetState = false; - @observable private success: Opt = undefined; - @observable private displayLauncher = true; - @observable private credentials: { username: string, apiKey: string } | undefined; - private disposer: Opt; - - 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 jsonResponse = await Networking.FetchFromServer("/readHypothesisAccessToken"); - const response = jsonResponse !== "" ? JSON.parse(jsonResponse) : undefined; - // if this is an authentication url, activate the UI to register the new access token - if (!response) { - this.isOpen = true; - this.authenticationLink = response; - return new Promise(async resolve => { - this.disposer?.(); - this.disposer = reaction( - () => this.authenticationCode, - async authenticationCode => { - const userProfile = authenticationCode && await Hypothesis.fetchUser(authenticationCode); - if (userProfile && userProfile.userid !== null) { - this.disposer?.(); - const hypothesisUsername = Hypothesis.extractUsername(userProfile.userid); // extract username from profile - Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode, hypothesisUsername }); - runInAction(() => { - this.success = true; - this.credentials = { username: hypothesisUsername, apiKey: authenticationCode! }; - }); - this.resetState(); - resolve(authenticationCode); - } - } - ); - }); - } - - if (displayIfFound) { - runInAction(() => { - this.success = true; - this.credentials = response; - }); - this.resetState(-1, -1); - this.isOpen = true; - } - return response; - } - - resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => { - if (!visibleForMS && !fadesOutInMS) { - runInAction(() => { - this.isOpen = false; - this.success = undefined; - this.displayLauncher = true; - this.credentials = undefined; - 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 = undefined; - }), fadesOutInMS); - }), visibleForMS); - } - }); - - constructor(props: {}) { - super(props); - HypothesisAuthenticationManager.Instance = this; - } - - private get renderPrompt() { - return ( -
- - {this.displayLauncher ? : (null)} - {this.showPasteTargetState ? this.authenticationCode = e.currentTarget.value)} - placeholder={prompt} - /> : (null)} - {this.credentials ? - <> - Welcome to Dash, {this.credentials.username} - -
{ - await Networking.FetchFromServer("/revokeHypothesisAccessToken"); - this.resetState(0, 0); - }} - >Disconnect Account
- : (null)} -
- ); - } - - private get dialogueBoxStyle() { - const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red"; - return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 }; - } - - render() { - return ( - 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 index 8eaad2905..a9d807976 100644 --- a/src/client/apis/hypothesis/HypothesisUtils.ts +++ b/src/client/apis/hypothesis/HypothesisUtils.ts @@ -1,5 +1,4 @@ import { StrCast, Cast } from "../../../fields/Types"; -import HypothesisAuthenticationManager from "../HypothesisAuthenticationManager"; import { SearchUtil } from "../../util/SearchUtil"; import { action } from "mobx"; import { Doc } from "../../../fields/Doc"; @@ -8,18 +7,6 @@ import { WebField } from "../../../fields/URLField"; import { DocumentManager } from "../../util/DocumentManager"; export namespace Hypothesis { - export const fetchUser = async (apiKey: string) => { - const response = await fetch('https://api.hypothes.is/api/profile', { - headers: { - 'Authorization': `Bearer ${apiKey}`, - }, - }); - if (response.ok) { - return response.json(); - } else { - throw new Error('DASH: Error in fetchUser GET request'); - } - }; // Send Hypothes.is client request to edit an annotation to add a Dash hyperlink export const makeLink = async (title: string, url: string, annotationId: string) => { @@ -40,6 +27,7 @@ export namespace Hypothesis { // Construct an URL which will automatically scroll the web page to a specific annotation's position export const makeAnnotationUrl = (annotationId: string, baseUrl: string) => { + console.log("baseUrl", baseUrl, annotationId); return `${baseUrl}#annotations:${annotationId}`; }; @@ -58,8 +46,8 @@ export namespace Hypothesis { doc.type === DocumentType.WEB && doc.data ); filteredDocs.forEach(doc => { - console.log(Cast(doc.data, WebField)?.url.href); - if (uri === Cast(doc.data, WebField)?.url.href) results.push(doc); // TODO check history? imperfect matches? + console.log(uri, Cast(doc.data, WebField)?.url.href, uri === Cast(doc.data, WebField)?.url.href); + (uri === Cast(doc.data, WebField)?.url.href) && results.push(doc); // TODO check history? imperfect matches? }); })); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ff33d35e0..1fe611b12 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -441,7 +441,6 @@ export class CurrentUserUtils { { toolTip: "Drag a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, { toolTip: "Connect a Google Account", title: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, - { toolTip: "Connect a Hypothesis Account", title: "Hypothesis Account", icon: "heading", click: 'HypothesisAuthenticationManager.Instance.fetchAccessToken(true)' }, ]; } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 90d59aa51..a9c2d5e15 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -12,7 +12,6 @@ import { CurrentUserUtils } from "./CurrentUserUtils"; import { Utils } from "../../Utils"; import { Doc } from "../../fields/Doc"; import GroupManager from "./GroupManager"; -import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager"; import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; import { togglePlaygroundMode } from "../../fields/util"; @@ -92,10 +91,6 @@ export default class SettingsManager extends React.Component<{}> { googleAuthorize = (event: any) => { GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true); } - @action - hypothesisAuthorize = (event: any) => { - HypothesisAuthenticationManager.Instance.fetchAccessToken(true); - } @action togglePlaygroundMode = () => { @@ -118,7 +113,6 @@ export default class SettingsManager extends React.Component<{}> { - - ; + const button = ; return !this.layoutDoc.toolTip ? button : {StrCast(this.layoutDoc.toolTip)}}> {button} -- 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/linking') 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: Wed, 5 Aug 2020 01:17:28 -0400 Subject: lint --- src/client/util/CurrentUserUtils.ts | 1 - src/client/views/EditableView.tsx | 4 +- src/client/views/MainView.tsx | 9 +- .../views/collections/CollectionSchemaCells.tsx | 11 +- src/client/views/collections/CollectionSubView.tsx | 4 +- src/client/views/linking/LinkMenuItem.tsx | 1 - src/client/views/nodes/DocumentLinksButton.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- .../views/nodes/formattedText/RichTextMenu.tsx | 3 +- src/client/views/search/FieldFilters.scss | 12 - src/client/views/search/FieldFilters.tsx | 41 -- src/client/views/search/FilterBox.scss | 178 --------- src/client/views/search/FilterBox.tsx | 431 --------------------- src/server/websocket.ts | 8 +- 14 files changed, 25 insertions(+), 683 deletions(-) delete mode 100644 src/client/views/search/FieldFilters.scss delete mode 100644 src/client/views/search/FieldFilters.tsx delete mode 100644 src/client/views/search/FilterBox.scss delete mode 100644 src/client/views/search/FilterBox.tsx (limited to 'src/client/views/linking') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ea701ec33..53d187aa0 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -771,7 +771,6 @@ export class CurrentUserUtils { // setup the list of sidebar mode buttons which determine what is displayed in the sidebar static async setupSidebarButtons(doc: Doc) { - this.setupSidebarContainer(doc); await CurrentUserUtils.setupToolsBtnPanel(doc); CurrentUserUtils.setupWorkspaces(doc); CurrentUserUtils.setupCatalog(doc); diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index a5628d5ee..ec3e754fb 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -197,11 +197,11 @@ export class EditableView extends React.Component { // contents = String(this.props.contents.valueOf()); - results.push({contents ? contents.slice(0, this.props.positions![0]) : this.props.placeholder?.valueOf()}); + results.push({contents ? contents.slice(0, this.props.positions[0]) : this.props.placeholder?.valueOf()}); positions.forEach((num, cur) => { results.push({contents ? contents.slice(num, num + length) : this.props.placeholder?.valueOf()}); let end = 0; - cur === positions.length - 1 ? end = contents!.length : end = positions[cur + 1]; + cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; results.push({contents ? contents.slice(num + length, end) : this.props.placeholder?.valueOf()}); } ); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 119fa3451..1b2dd1c44 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -82,8 +82,8 @@ export class MainView extends React.Component { @computed public get mainFreeform(): Opt { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } @computed public get searchDoc() { return Cast(this.userDoc["search-panel"], Doc) as Doc; } - - @observable public sidebarContent: any = this.userDoc?.["sidebar"]; + sidebar: string = "sidebar"; + @observable public sidebarContent: any = this.userDoc?.[this.sidebar]; @observable public panelContent: string = "none"; @observable public showProperties: boolean = false; public isPointerDown = false; @@ -193,8 +193,9 @@ export class MainView extends React.Component { } if (targets && targets.length && SearchBox.Instance._searchbarOpen) { let check = false; + const icon = "icon"; targets.forEach((thing) => { - if (thing.className.toString() === "collectionSchemaView-table" || (thing as any)?.dataset["icon"] === "filter" || thing.className.toString() === "beta" || thing.className.toString() === "collectionSchemaView-menuOptions-wrapper") { + if (thing.className.toString() === "collectionSchemaView-table" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "beta" || thing.className.toString() === "collectionSchemaView-menuOptions-wrapper") { check = true; } }); @@ -731,7 +732,7 @@ export class MainView extends React.Component { @computed get search() { return
{/*
{Doc.CurrentUserEmail}
*/} -
{ bing={() => { const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); if (cfield !== undefined) { - if (cfield.Text !== undefined) { - return (cfield.Text); + console.log(typeof (cfield)); + // if (typeof(cfield)===RichTextField) + const a = cfield as RichTextField; + if (a.Text !== undefined) { + return (a.Text); } else if (StrCast(cfield)) { return StrCast(cfield); @@ -884,7 +888,8 @@ export class CollectionSchemaButtons extends CollectionSchemaCell { // SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); // }; const doc = this.props.rowProps.original; - let buttons =
-
-
-
-
-
Filter by type of node
-
-
-
-
-
-
Filter by Basic Keys
-
-
-
-
-
- - -
-
- ) : - undefined} - - ); - } -} \ No newline at end of file diff --git a/src/server/websocket.ts b/src/server/websocket.ts index cdce41a3a..2c8ec090a 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -286,16 +286,16 @@ export namespace WebSocket { dynfield = true; const val = docfield[key]; key = key.substring(7); - if (key==="_height"){ - Object.values(suffixMap).forEach(suf => {update[key] = { set: null };}); + if (key === "_height") { + Object.values(suffixMap).forEach(suf => { update[key] = { set: null }; }); } else { - Object.values(suffixMap).forEach(suf => {update[key + getSuffix(suf)] = { set: null };}); + Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; }); } const term = ToSearchTerm(val); if (term !== undefined) { const { suffix, value } = term; - if (key==="_height"){ + if (key === "_height") { update[key] = { set: value }; } update[key + suffix] = { set: value }; -- cgit v1.2.3-70-g09d2 From 1632540870c4a46be31a2ae949714296e8403383 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Thu, 6 Aug 2020 13:04:43 -0700 Subject: fixed LinkMenu position so that list of links stay with doc when zooming --- src/client/views/MainView.tsx | 2 +- src/client/views/linking/LinkMenu.tsx | 23 +++++++++++++++-------- src/client/views/nodes/DocumentLinksButton.tsx | 4 ---- 3 files changed, 16 insertions(+), 13 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 007d531c8..d03cb1bb2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -790,7 +790,7 @@ export class MainView extends React.Component {
{LinkDescriptionPopup.descriptionPopup ? : null} - {DocumentLinksButton.EditLink ? : (null)} + {DocumentLinksButton.EditLink ? : (null)} {LinkDocPreview.LinkInfo ? : (null)} diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 7b5fb0127..8023928a1 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -19,7 +19,6 @@ interface Props { docView: DocumentView; changeFlyout: () => void; addDocTab: (document: Doc, where: string) => boolean; - location: number[]; } @observer @@ -85,17 +84,25 @@ export class LinkMenu extends React.Component { return linkItems; } + @computed + get position() { + const docView = this.props.docView; + const transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse(); + const [sptX, sptY] = transform.transformPoint(0, 0); + const [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight()); + return { x: sptX, y: sptY, r: bptX, b: bptY }; + } + render() { + console.log("computed", this.position.x, this.position.b); const sourceDoc = this.props.docView.props.Document; const groups: Map = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc); return
- {!this._editingLink ?
- {this.renderAllGroups(groups)} -
:
+ {!this._editingLink ? +
+ {this.renderAllGroups(groups)} +
: +
this._editingLink = undefined)} />
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index cb79e1522..c2f27c85a 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -74,7 +74,6 @@ export class DocumentLinksButton extends React.Component Doc.BrushDoc(this.props.View.Document)); } else if (!this.props.InMenu) { DocumentLinksButton.EditLink = this.props.View; - DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY]; } } @@ -147,8 +145,6 @@ export class DocumentLinksButton extends React.Component Date: Thu, 6 Aug 2020 13:07:48 -0700 Subject: put LinkMenu above document toolbar so that it doesn't get blocked --- src/client/views/linking/LinkMenu.scss | 1 + src/client/views/linking/LinkMenu.tsx | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss index 98e4171f0..4dc25031d 100644 --- a/src/client/views/linking/LinkMenu.scss +++ b/src/client/views/linking/LinkMenu.scss @@ -6,6 +6,7 @@ position: absolute; top: 0; left: 0; + z-index: 999; .linkMenu-list { diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 8023928a1..8ecde959f 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -4,14 +4,13 @@ import { DocumentView } from "../nodes/DocumentView"; import { LinkEditor } from "./LinkEditor"; import './LinkMenu.scss'; import React = require("react"); -import { Doc, Opt } from "../../../fields/Doc"; +import { Doc } from "../../../fields/Doc"; import { LinkManager } from "../../util/LinkManager"; import { LinkMenuGroup } from "./LinkMenuGroup"; import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { library } from "@fortawesome/fontawesome-svg-core"; import { DocumentLinksButton } from "../nodes/DocumentLinksButton"; import { LinkDocPreview } from "../nodes/LinkDocPreview"; -import { isUndefined } from "util"; library.add(faTrash); -- cgit v1.2.3-70-g09d2 From 6263a4539576ce92f97a02b9e7bde2804e01a6df Mon Sep 17 00:00:00 2001 From: Melissa Zhang 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/linking') 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/linking') 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 From 778c189edc73cdc6f672eff5166c23fd932740e3 Mon Sep 17 00:00:00 2001 From: anika-ahluwalia Date: Sun, 9 Aug 2020 19:23:28 -0500 Subject: minor changes --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/linking/LinkEditor.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ef4b7b9d2..2b8e949b1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1257,7 +1257,7 @@ export class CollectionFreeFormView extends CollectionSubView this.showTimeline = !this.showTimeline), icon: faEye }); this.props.ContainingCollectionView && - optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); + optionItems.push({ description: "Undo Collection", event: this.promoteCollection, icon: "table" }); optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" }); optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 5832a2181..0470cf08b 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -382,7 +382,7 @@ export class LinkEditor extends React.Component {
this.changeFollowBehavior("onRight")}> - Always open in right tab + Always open in new pane on right
this.changeFollowBehavior("inTab")}> -- cgit v1.2.3-70-g09d2 From dc0ffc050b5b11e345245927f3d8a813937e588d Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 9 Aug 2020 22:48:26 -0400 Subject: starting to fix link following behavior to audio regions. --- src/client/util/DocumentManager.ts | 2 +- src/client/views/linking/LinkEditor.tsx | 6 +- src/client/views/nodes/AudioBox.scss | 5 + src/client/views/nodes/AudioBox.tsx | 256 ++++++++++++++++---------------- 4 files changed, 140 insertions(+), 129 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 523dbfca0..bd57e7f48 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -152,7 +152,7 @@ export class DocumentManager { const first = getFirstDocView(annotatedDoc); if (first) { annotatedDoc = first.props.Document; - docView?.props.focus(annotatedDoc, false); + first.props.focus(annotatedDoc, false); } } if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 5832a2181..3ef391a5d 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -355,11 +355,11 @@ export class LinkEditor extends React.Component { this.openDropdown = !this.openDropdown; } - @undoBatch @action - changeFollowBehavior = (follow: string) => { + @undoBatch + changeFollowBehavior = action((follow: string) => { this.openDropdown = false; Doc.GetProto(this.props.linkDoc).followLinkLocation = follow; - } + }) @computed get followingDropdown() { diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 0d787d9af..c80e3af24 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -8,6 +8,11 @@ position: relative; cursor: default; + .audiobox-inner { + width:100%; + height: 100%; + } + .audiobox-buttons { display: flex; width: 100%; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 709422f35..0ea624edf 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -128,37 +128,41 @@ export class AudioBox extends ViewBoxAnnotatableComponent SelectionManager.SelectedDocuments(), selected => { - const sel = selected.length ? selected[0].props.Document : undefined; - let link; - if (sel) { - // for determining if the link is created after recording (since it will use linkTime rather than creation date) - DocListCast(this.dataDoc.links).map(l => { - let { la1, la2, linkTime } = this.getLinkData(l); - if (la1 === sel || la2 === sel) { // if the selected document is linked to this audio - let endTime; - if (la2.audioStart) linkTime = NumCast(la2.audioStart); - if (la1.audioStart) linkTime = NumCast(la1.audioStart); - - if (la1.audioEnd) endTime = NumCast(la1.audioEnd); - if (la2.audioEnd) endTime = NumCast(la2.audioEnd); - - if (linkTime) { - link = true; - this.layoutDoc.playOnSelect && this.recordingStart && sel && !Doc.AreProtosEqual(sel, this.props.Document) && (endTime ? this.playFrom(linkTime, endTime) : this.playFrom(linkTime)); - } - } - }); - } - - // for links created during recording - if (!link) { - this.layoutDoc.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime()); - this.layoutDoc.playOnSelect && this.recordingStart && !sel && this.pause(); + if (this.layoutDoc.playOnSelect) { + const sel = selected.length ? selected[0].props.Document : undefined; + const link = sel && DocListCast(this.dataDoc.links).forEach(l => (l.anchor1 === sel || l.anchor2 === sel) && this.playLink(l), false); + // for links created during recording + if (!link) { + this.layoutDoc.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime()); + this.layoutDoc.playOnSelect && this.recordingStart && !sel && this.pause(); + } } }); this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime)); } + playLink = (doc: Doc) => { + let link = false; + !Doc.AreProtosEqual(doc, this.props.Document) && DocListCast(this.props.Document.links).forEach(l => { + if (l.anchor1 === doc || l.anchor2 === doc) { + const { la1, la2, linkTime } = this.getLinkData(l); + let startTime = linkTime; + if (la2.audioStart) startTime = NumCast(la2.audioStart); + if (la1.audioStart) startTime = NumCast(la1.audioStart); + + let endTime; + if (la1.audioEnd) endTime = NumCast(la1.audioEnd); + if (la2.audioEnd) endTime = NumCast(la2.audioEnd); + + if (startTime) { + link = true; + this.recordingStart && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime)); + } + } + }); + return link; + } + // for updating the timecode timecodeChanged = () => { const htmlEle = this._ele; @@ -511,6 +515,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent ScriptField)) => { return this.playLink(mark)} pointerEvents={true} NativeHeight={returnZero} NativeWidth={returnZero} @@ -524,112 +529,113 @@ export class AudioBox extends ViewBoxAnnotatableComponent; }; - return
- {!this.path ? -
-
- -
- {this.audioState === "recording" ? -
e.stopPropagation()}> -
- -
-
- -
-
{formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}
+ return
+
+ {!this.path ? +
+
+
- : - } -
: -
-
-
-
-
{ e.stopPropagation(); e.preventDefault(); }} - onPointerDown={e => { - if (e.button === 0 && !e.ctrlKey) { - const rect = (e.target as any).getBoundingClientRect(); - - if (e.target !== this._audioRef.current) { - const wasPaused = this.audioState === "paused"; - this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration; - wasPaused && this.pause(); +
: +
+
+
+
+
{ e.stopPropagation(); e.preventDefault(); }} + onPointerDown={e => { + if (e.button === 0 && !e.ctrlKey) { + const rect = (e.target as any).getBoundingClientRect(); + + if (e.target !== this._audioRef.current) { + const wasPaused = this.audioState === "paused"; + this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration; + wasPaused && this.pause(); + } + + this.onPointerDownTimeline(e); + } + }}> +
+ {this.waveform} +
+ {DocListCast(this.dataDoc[this.annotationKey]).map((m, i) => + (!m.isLabel) ? + (this.layoutDoc.hideMarkers) ? (null) : +
{ this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation(); }} > +
this.onPointerDown(e, m, true)}>
+ {markerDoc(m, this.rangeScript)} +
this.onPointerDown(e, m, false)}>
+
+ : + (this.layoutDoc.hideLabels) ? (null) : +
+ {markerDoc(m, this.labelScript)} +
+ )} + {DocListCast(this.dataDoc.links).map((l, i) => { + const { la1, la2, linkTime } = this.getLinkData(l); + let startTime = linkTime; + if (la2.audioStart && !la2.audioEnd) { + startTime = NumCast(la2.audioStart); } - this.onPointerDownTimeline(e); - } - }}> -
- {this.waveform} + return !linkTime ? (null) : +
e.stopPropagation()}> + +
Doc.linkFollowHighlight(la1)} + onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(startTime); e.stopPropagation(); e.preventDefault(); } }} /> +
; + })} + {this._visible ? this.selectionContainer : null} + +
{ e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} /> + {this.audio} +
+
+ {formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))} +
+
+ {formatTime(Math.round(this.audioDuration))}
- {DocListCast(this.dataDoc[this.annotationKey]).map((m, i) => - (!m.isLabel) ? - (this.layoutDoc.hideMarkers) ? (null) : -
{ this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation(); }} > -
this.onPointerDown(e, m, true)}>
- {markerDoc(m, this.rangeScript)} -
this.onPointerDown(e, m, false)}>
-
- : - (this.layoutDoc.hideLabels) ? (null) : -
- {markerDoc(m, this.labelScript)} -
- )} - {DocListCast(this.dataDoc.links).map((l, i) => { - let { la1, la2, linkTime } = this.getLinkData(l); - if (la2.audioStart && !la2.audioEnd) { - linkTime = NumCast(la2.audioStart); - } - - return !linkTime ? (null) : -
e.stopPropagation()}> - -
Doc.linkFollowHighlight(la1)} - onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); e.preventDefault(); } }} /> -
; - })} - {this._visible ? this.selectionContainer : null} - -
{ e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / this.audioDuration * 100}%`, pointerEvents: "none" }} /> - {this.audio} -
-
- {formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))} -
-
- {formatTime(Math.round(this.audioDuration))}
-
- } + }
; } } -- cgit v1.2.3-70-g09d2 From 1d2851b237fd41f8a8726244b095986699f513a5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 9 Aug 2020 23:09:49 -0400 Subject: fixed playing audio on link follow when audio opens on right or in tab --- src/client/views/linking/LinkMenuItem.tsx | 9 ++++++++- src/client/views/nodes/AudioBox.tsx | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index b95fccf2a..21c666a4d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -162,7 +162,14 @@ export class LinkMenuItem extends React.Component { } if (linkDoc.followLinkLocation && linkDoc.followLinkLocation !== "Default") { - this.props.addDocTab(this.props.destinationDoc, StrCast(linkDoc.followLinkLocation)); + const annotationOn = this.props.destinationDoc.annotationOn as Doc; + this.props.addDocTab(annotationOn instanceof Doc ? annotationOn : this.props.destinationDoc, StrCast(linkDoc.followLinkLocation)); + if (annotationOn) { + setTimeout(() => { + const dv = DocumentManager.Instance.getFirstDocumentView(this.props.destinationDoc); + dv?.props.focus(this.props.destinationDoc, false); + }); + } } else { DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false); } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 0ea624edf..d689aeadc 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -195,7 +195,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.playFrom(seekTimeInSeconds, endTime), 500); + } else if (this._ele && AudioBox.Enabled) { if (seekTimeInSeconds < 0) { if (seekTimeInSeconds > -1) { setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000); -- cgit v1.2.3-70-g09d2