aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMelissa Zhang <mzhang19096@gmail.com>2020-08-04 16:53:36 -0700
committerMelissa Zhang <mzhang19096@gmail.com>2020-08-04 16:53:36 -0700
commitbd6b5a81480f7f56a65c13954e080281279b6627 (patch)
tree531b168ea67f982c4ec65e39159a8a6e32ad7fd4 /src
parente9737412cb3d60ed76c31c9571e9516518cddd77 (diff)
"added invisibleDoc and simulated clicks to load annotations"
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts4
-rw-r--r--src/client/apis/hypothesis/HypothesisUtils.ts133
-rw-r--r--src/client/views/MainView.tsx79
-rw-r--r--src/client/views/collections/CollectionSubView.tsx26
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx15
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx14
6 files changed, 181 insertions, 90 deletions
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<DocumentView> = 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 {
</div>;
}
+ @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts
+ return !DocumentLinksButton.invisibleWebDoc ? null :
+ <div style={{ position: 'absolute', left: 50, top: 50, display: 'block', width: '500px', height: '1000px' }} ref={DocumentLinksButton.invisibleWebRef}>
+ <WebBox
+ fieldKey={"data"}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ Document={DocumentLinksButton.invisibleWebDoc}
+ LibraryPath={emptyPath}
+ dropAction={"move"}
+ isSelected={returnFalse}
+ select={returnFalse}
+ rootSelected={returnFalse}
+ renderDepth={0}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={returnFalse}
+ active={returnFalse}
+ whenActiveChanged={returnFalse}
+ focus={returnFalse}
+ PanelWidth={() => 500}
+ PanelHeight={() => 800}
+ NativeHeight={() => 500}
+ NativeWidth={() => 800}
+ ContentScaling={returnOne}
+ docFilters={returnEmptyFilter}
+ />
+ </div>;
+ }
+
render() {
return (<div className={"mainView-container" + (this.darkScheme ? "-dark" : "")} ref={this._mainViewRef}>
@@ -753,8 +786,52 @@ export class MainView extends React.Component {
<OverlayView />
<TimelineMenu />
{this.snapLines}
+ <div ref={this.makeWebRef} style={{ position: 'absolute', left: -1000, top: -1000, display: 'block', width: '200px', height: '800px' }} />
</div >);
}
+
+ makeWebRef = (ele: HTMLDivElement) => {
+ reaction(() => DocumentLinksButton.invisibleWebDoc,
+ invisibleDoc => {
+ ReactDOM.unmountComponentAtNode(ele);
+ invisibleDoc && ReactDOM.render(<span title="Drag as document" className="invisible-webbox" >
+ <div style={{ position: 'absolute', left: -1000, top: -1000, display: 'block', width: '200px', height: '800px' }} ref={DocumentLinksButton.invisibleWebRef}>
+ <WebBox
+ fieldKey={"data"}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ Document={invisibleDoc}
+ LibraryPath={emptyPath}
+ dropAction={"move"}
+ isSelected={returnFalse}
+ select={returnFalse}
+ rootSelected={returnFalse}
+ renderDepth={0}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={returnFalse}
+ active={returnFalse}
+ whenActiveChanged={returnFalse}
+ focus={returnFalse}
+ PanelWidth={() => 500}
+ PanelHeight={() => 800}
+ NativeHeight={() => 500}
+ NativeWidth={() => 800}
+ ContentScaling={returnOne}
+ docFilters={returnEmptyFilter}
+ />
+ </div>;
+ </span>, 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<T, X>(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<LinkMenuItemProps> {
}
@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<LinkMenuItemProps> {
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<LinkMenuItemProps> {
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<DocumentLinksButtonProp
@observable public static AnnotationId: string | undefined;
@observable public static AnnotationUri: string | undefined;
+ @observable public static invisibleWebDoc: Opt<Doc>;
+ public static invisibleWebRef = React.createRef<HTMLDivElement>();
+
@action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
if (this.props.InMenu && this.props.StartLink) {
@@ -114,12 +116,12 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
const targetDoc = this.props.View.props.Document;
const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : "long drag");
- // TODO: Not currently possible to drag to complete links to annotations
+ // currently possible to drag to complete links to annotations
if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
- Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
+ Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId, sourceDoc); // update and link placeholder annotation
}
LinkManager.currentLink = linkDoc;
@@ -163,7 +165,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
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); // edit annotation to add a Dash hyperlink to the linked doc
+ 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
}
if (linkDoc) {