aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx160
-rw-r--r--src/client/apis/hypothesis/HypothesisUtils.ts78
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/util/CurrentUserUtils.ts3
-rw-r--r--src/client/util/SettingsManager.tsx6
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MainView.tsx16
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx2
-rw-r--r--src/client/views/linking/LinkEditor.tsx6
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx20
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx41
-rw-r--r--src/server/ApiManagers/HypothesisManager.ts44
-rw-r--r--src/server/ApiManagers/UploadManager.ts1
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts1
-rw-r--r--src/server/database.ts2
-rw-r--r--src/server/index.ts2
16 files changed, 155 insertions, 230 deletions
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
deleted file mode 100644
index c3e8d2fff..000000000
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { observable, action, reaction, runInAction, IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import MainViewModal from "../views/MainViewModal";
-import { Opt } from "../../fields/Doc";
-import { Networking } from "../Network";
-import "./HypothesisAuthenticationManager.scss";
-import { Scripting } from "../util/Scripting";
-
-const prompt = "Paste authorization code here...";
-
-@observer
-export default class HypothesisAuthenticationManager extends React.Component<{}> {
- public static Instance: HypothesisAuthenticationManager;
- private authenticationLink: Opt<string> = undefined;
- @observable private openState = false;
- @observable private authenticationCode: Opt<string> = undefined;
- @observable private showPasteTargetState = false;
- @observable private success: Opt<boolean> = undefined;
- @observable private displayLauncher = true;
- @observable private credentials: string;
- private disposer: Opt<IReactionDisposer>;
-
- private set isOpen(value: boolean) {
- runInAction(() => this.openState = value);
- }
-
- private set shouldShowPasteTarget(value: boolean) {
- runInAction(() => this.showPasteTargetState = value);
- }
-
- public cancel() {
- this.openState && this.resetState(0, 0);
- }
-
- public fetchAccessToken = async (displayIfFound = false) => {
- const response: any = await Networking.FetchFromServer("/readHypothesisAccessToken");
- // if this is an authentication url, activate the UI to register the new access token
- if (!response) { // new RegExp(AuthenticationUrl).test(response)) {
- this.isOpen = true;
- this.authenticationLink = response;
- return new Promise<string>(async resolve => {
- this.disposer?.();
- this.disposer = reaction(
- () => this.authenticationCode,
- async authenticationCode => {
- if (authenticationCode) {
- this.disposer?.();
- Networking.PostToServer("/writeHypothesisAccessToken", { authenticationCode });
- runInAction(() => {
- this.success = true;
- this.credentials = response;
- });
- this.resetState();
- resolve(authenticationCode);
- }
- }
- );
- });
- }
-
- if (displayIfFound) {
- runInAction(() => {
- this.success = true;
- this.credentials = response;
- });
- this.resetState(-1, -1);
- this.isOpen = true;
- }
- return response.access_token;
- }
-
- resetState = action((visibleForMS: number = 3000, fadesOutInMS: number = 500) => {
- if (!visibleForMS && !fadesOutInMS) {
- runInAction(() => {
- this.isOpen = false;
- this.success = undefined;
- this.displayLauncher = true;
- this.credentials = "";
- this.shouldShowPasteTarget = false;
- this.authenticationCode = undefined;
- });
- return;
- }
- this.authenticationCode = undefined;
- this.displayLauncher = false;
- this.shouldShowPasteTarget = false;
- if (visibleForMS > 0 && fadesOutInMS > 0) {
- setTimeout(action(() => {
- this.isOpen = false;
- setTimeout(action(() => {
- this.success = undefined;
- this.displayLauncher = true;
- this.credentials = "";
- }), fadesOutInMS);
- }), visibleForMS);
- }
- });
-
- constructor(props: {}) {
- super(props);
- HypothesisAuthenticationManager.Instance = this;
- }
-
- private get renderPrompt() {
- return (
- <div className={'authorize-container'}>
-
- {this.displayLauncher ? <button
- className={"dispatch"}
- onClick={() => {
- this.shouldShowPasteTarget = true;
- }}
- style={{ marginBottom: this.showPasteTargetState ? 15 : 0 }}
- >Authorize a Hypothesis account...</button> : (null)}
- {this.showPasteTargetState ? <input
- className={'paste-target'}
- onChange={action(e => this.authenticationCode = e.currentTarget.value)}
- placeholder={prompt}
- /> : (null)}
- {this.credentials ?
- <>
- <span
- className={'welcome'}
- >Welcome to Dash, {this.credentials}
- </span>
- <div
- className={'disconnect'}
- onClick={async () => {
- await Networking.FetchFromServer("/revokeHypothesisAccessToken");
- this.resetState(0, 0);
- }}
- >Disconnect Account</div>
- </> : (null)}
- </div>
- );
- }
-
- private get dialogueBoxStyle() {
- const borderColor = this.success === undefined ? "black" : this.success ? "green" : "red";
- return { borderColor, transition: "0.2s borderColor ease", zIndex: 1002 };
- }
-
- render() {
- return (
- <MainViewModal
- isDisplayed={this.openState}
- interactive={true}
- contents={this.renderPrompt}
- // overlayDisplayedOpacity={0.9}
- dialogueBoxStyle={this.dialogueBoxStyle}
- overlayStyle={{ zIndex: 1001 }}
- closeOnExternalClick={action(() => this.isOpen = false)}
- />
- );
- }
-
-}
-
-Scripting.addGlobal("HypothesisAuthenticationManager", HypothesisAuthenticationManager); \ No newline at end of file
diff --git a/src/client/apis/hypothesis/HypothesisUtils.ts b/src/client/apis/hypothesis/HypothesisUtils.ts
new file mode 100644
index 000000000..a9d807976
--- /dev/null
+++ b/src/client/apis/hypothesis/HypothesisUtils.ts
@@ -0,0 +1,78 @@
+import { StrCast, Cast } from "../../../fields/Types";
+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 {
+
+ // 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 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}`;
+ };
+
+ // 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(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?
+ });
+ }));
+
+ // 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) => {
+ 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
+ }));
+ }, 200);
+
+ 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/documents/Documents.ts b/src/client/documents/Documents.ts
index 984d43e32..045f7da76 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -920,7 +920,6 @@ export namespace DocUtils {
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/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 6d752832a..24ffa8b1b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -446,8 +446,9 @@ export class CurrentUserUtils {
// { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc },
// { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
- { 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: "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)' },
];
}
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<{}> {
<button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
<button onClick={this.togglePlaygroundMode}>{`${this.playgroundMode ? "Disable" : "Enable"} playground mode`}</button>
<button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
- <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
<button onClick={() => GroupManager.Instance.open()}>Manage groups</button>
<button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c9f95a538..b4a518326 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -7,7 +7,6 @@ import { List } from "../../fields/List";
import { ScriptField } from "../../fields/ScriptField";
import { Cast, PromiseValue } from "../../fields/Types";
import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
-import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
import { DocServer } from "../DocServer";
import { DocumentType } from "../documents/DocumentTypes";
import { DictationManager } from "../util/DictationManager";
@@ -106,7 +105,6 @@ export default class KeyManager {
doDeselect && SelectionManager.DeselectAll();
DictationManager.Controls.stop();
GoogleAuthenticationManager.Instance.cancel();
- HypothesisAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
GroupManager.Instance.close();
CollectionFreeFormViewChrome.Instance.clearKeep();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index bb73df75c..dd8433866 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -57,6 +57,8 @@ 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/HypothesisUtils';
+import { SelectionManager } from '../util/SelectionManager';
@observer
export class MainView extends React.Component {
@@ -104,6 +106,20 @@ export class MainView extends React.Component {
}
});
});
+ 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");
+
+ // 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;
+ DocumentLinksButton.StartLink = source;
+ });
+ });
}
componentWillUnMount() {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 3b31947f7..cd6e60de6 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -176,7 +176,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.StartLink.props.Document.title}
+ Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in" : ""} {DocumentLinksButton.StartLink.title}
</span>
<Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
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<LinkEditorProps> {
onPointerDown={() => this.changeFollowBehavior("inTab")}>
Always open in new tab
</div>
+ {this.props.linkDoc.linksToAnnotation ?
+ <div className="linkEditor-followingDropdown-option"
+ onPointerDown={() => this.changeFollowBehavior("openExternal")}>
+ Always open in external page
+ </div>
+ : null}
</div>
</div>
</div>;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index d1c839c3b..8503bcbeb 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -11,10 +11,12 @@ 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 { Hypothesis } from '../../apis/hypothesis/HypothesisUtils';
+import { Id } from '../../../fields/FieldSymbols';
import { Tooltip } from '@material-ui/core';
import { DocumentType } from '../../documents/DocumentTypes';
import { undoBatch } from '../../util/UndoManager';
@@ -151,17 +153,27 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
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(linkDoc.annotationId), StrCast(linkDoc.annotationUri)), '_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);
}
+
+ linkDoc.linksToAnnotation && Hypothesis.scrollToAnnotation(StrCast(this.props.linkDoc.annotationId));
}
@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);
LinkManager.Instance.deleteLink(this.props.linkDoc);
LinkDocPreview.LinkInfo = undefined;
DocumentLinksButton.EditLink = undefined;
@@ -232,7 +244,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /></div>
<p className="linkMenu-destination-title"
onPointerDown={this.followDefault}>
- {title}
+ {this.props.linkDoc.linksToAnnotation ? "Annotation in" : ""} {title}
</p>
</div>
{this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 3b91ac6b5..445ab6cd4 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -2,18 +2,22 @@ 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 } from "../../../fields/Doc";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { emptyFunction, setupMoveUpEvents, returnFalse, Utils } from "../../../Utils";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import './DocumentLinksButton.scss';
import { DocumentView } from "./DocumentView";
+import { StrCast } from "../../../fields/Types";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
+import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
+import { Id } from "../../../fields/FieldSymbols";
import { TaskCompletionBox } from "./TaskCompletedBox";
import React = require("react");
+import './DocumentLinksButton.scss';
+
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -31,6 +35,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
private _linkButton = React.createRef<HTMLDivElement>();
@observable public static StartLink: DocumentView | undefined;
+ @observable public static AnnotationId: string | undefined;
+ @observable public static AnnotationUri: string | undefined;
@action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
@@ -92,10 +98,22 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.AnnotationId = undefined;
} else {
-
if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
- const linkDoc = 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");
+
+ // TODO: Not currently possible to drag to complete links to annotations
+ if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
+ const sourceUrl = DocumentLinksButton.AnnotationUri;
+ Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
+ Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
+ Doc.GetProto(linkDoc as Doc).annotationUrl = Hypothesis.makeAnnotationUrl(DocumentLinksButton.AnnotationId, sourceUrl); // redirect web doc to this URL when following link
+ Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
+ }
+
LinkManager.currentLink = linkDoc;
runInAction(() => {
@@ -123,15 +141,26 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
finishLinkClick = (screenX: number, screenY: number) => {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.AnnotationId = undefined;
} else {
if (this.props.InMenu && !!!this.props.StartLink) {
if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
- const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, 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
runInAction(() => DocumentLinksButton.StartLink!._link = this.props.View._link = linkDoc);
setTimeout(action(() => DocumentLinksButton.StartLink!._link = this.props.View._link = undefined), 0);
LinkManager.currentLink = linkDoc;
+ // if the link is to a Hypothes.is annotation
+ if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
+ const sourceUrl = DocumentLinksButton.AnnotationUri; // the URL of the annotation's source web page
+ 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).annotationUrl = Hypothesis.makeAnnotationUrl(DocumentLinksButton.AnnotationId, sourceUrl); // redirect web doc to this URL when following link
+ Hypothesis.makeLink(StrCast(targetDoc.title), Utils.prepend("/doc/" + targetDoc[Id]), DocumentLinksButton.AnnotationId); // update and link placeholder annotation
+ }
+
runInAction(() => {
if (linkDoc) {
TaskCompletionBox.textDisplayed = "Link Created";
diff --git a/src/server/ApiManagers/HypothesisManager.ts b/src/server/ApiManagers/HypothesisManager.ts
deleted file mode 100644
index 33badbc42..000000000
--- a/src/server/ApiManagers/HypothesisManager.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied } from "../RouteManager";
-import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import { Database } from "../database";
-import { writeFile, readFile, readFileSync, existsSync } from "fs";
-import { serverPathToFile, Directory } from "./UploadManager";
-
-export default class HypothesisManager extends ApiManager {
-
- protected initialize(register: Registration): void {
-
- register({
- method: Method.GET,
- subscription: "/readHypothesisAccessToken",
- secureHandler: async ({ user, res }) => {
- if (existsSync(serverPathToFile(Directory.hypothesis, user.id))) {
- const read = readFileSync(serverPathToFile(Directory.hypothesis, user.id), "base64") || "";
- console.log("READ = " + read);
- res.send(read);
- } else res.send("");
- }
- });
-
- register({
- method: Method.POST,
- subscription: "/writeHypothesisAccessToken",
- secureHandler: async ({ user, req, res }) => {
- const write = req.body.authenticationCode;
- console.log("WRITE = " + write);
- res.send(await writeFile(serverPathToFile(Directory.hypothesis, user.id), write, "base64", () => { }));
- }
- });
-
- register({
- method: Method.GET,
- subscription: "/revokeHypothesisAccessToken",
- secureHandler: async ({ user, res }) => {
- await Database.Auxiliary.GoogleAccessToken.Revoke("dash-hyp-" + user.id);
- res.send();
- }
- });
-
- }
-} \ No newline at end of file
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 4455d11eb..bd242db5e 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -26,7 +26,6 @@ export enum Directory {
text = "text",
pdf_thumbnails = "pdf_thumbnails",
audio = "audio",
- hypothesis = "hypothesis"
}
export function serverPathToFile(directory: Directory, filename: string) {
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index b0157a85f..64bafe7fb 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -40,7 +40,6 @@ export namespace GoogleApiServerUtils {
export enum Service {
Documents = "Documents",
Slides = "Slides",
- Hypothesis = "Hypothesis"
}
/**
diff --git a/src/server/database.ts b/src/server/database.ts
index 2372cbcf2..64c5b7e4b 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -304,7 +304,7 @@ export namespace Database {
*/
export enum AuxiliaryCollections {
GooglePhotosUploadHistory = "uploadedFromGooglePhotos",
- GoogleAccess = "googleAuthentication"
+ GoogleAccess = "googleAuthentication",
}
/**
diff --git a/src/server/index.ts b/src/server/index.ts
index 9af4b00bc..5c313654c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -18,7 +18,6 @@ import PDFManager from "./ApiManagers/PDFManager";
import UploadManager from "./ApiManagers/UploadManager";
import { log_execution } from "./ActionUtilities";
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
-import HypothesisManager from "./ApiManagers/HypothesisManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
import { Logger } from "./ProcessFactory";
import { yellow } from "colors";
@@ -72,7 +71,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
new DeleteManager(),
new UtilManager(),
new GeneralGoogleManager(),
- new HypothesisManager(),
new GooglePhotosManager(),
];