aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/LinkDocPreview.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/LinkDocPreview.tsx')
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx237
1 files changed, 147 insertions, 90 deletions
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 6c7e174f7..a12564839 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -1,21 +1,22 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, observable } from 'mobx';
-import { observer } from "mobx-react";
-import wiki from "wikijs";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../fields/Doc";
-import { NumCast, StrCast, Cast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils";
+import { observer } from 'mobx-react';
+import wiki from 'wikijs';
+import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
import { DocServer } from '../../DocServer';
-import { Docs, DocUtils } from "../../documents/Documents";
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
+import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
-import { Transform } from "../../util/Transform";
+import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
-import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
+import { DocumentView, DocumentViewSharedProps } from './DocumentView';
import './LinkDocPreview.scss';
-import React = require("react");
-import { DocumentType } from '../../documents/DocumentTypes';
-import { DragManager } from '../../util/DragManager';
+import React = require('react');
interface LinkDocPreviewProps {
linkDoc?: Doc;
@@ -27,15 +28,20 @@ interface LinkDocPreviewProps {
}
@observer
export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
- @action public static Clear() { LinkDocPreview.LinkInfo = undefined; }
- @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); }
+ @action public static Clear() {
+ LinkDocPreview.LinkInfo = undefined;
+ }
+ @action public static SetLinkInfo(info?: LinkDocPreviewProps) {
+ LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info);
+ }
_infoRef = React.createRef<HTMLDivElement>();
+ _linkDocRef = React.createRef<HTMLDivElement>();
@observable public static LinkInfo: Opt<LinkDocPreviewProps>;
@observable _targetDoc: Opt<Doc>;
@observable _linkDoc: Opt<Doc>;
@observable _linkSrc: Opt<Doc>;
- @observable _toolTipText = "";
+ @observable _toolTipText = '';
@observable _hrefInd = 0;
@action init() {
@@ -47,113 +53,132 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
if (anchor1 && anchor2) {
linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
}
- if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { // want to show annotation context document if annotation is not text
- linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno));
+ if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) {
+ // want to show annotation context document if annotation is not text
+ linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._targetDoc = anno)));
} else {
this._targetDoc = linkTarget;
}
- this._toolTipText = "";
+ this._toolTipText = '';
}
componentDidUpdate(props: any) {
if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init();
}
componentDidMount() {
this.init();
- document.addEventListener("pointerdown", this.onPointerDown);
+ document.addEventListener('pointerdown', this.onPointerDown, true);
}
componentWillUnmount() {
LinkDocPreview.SetLinkInfo(undefined);
- document.removeEventListener("pointerdown", this.onPointerDown);
+ document.removeEventListener('pointerdown', this.onPointerDown, true);
}
onPointerDown = (e: PointerEvent) => {
- !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
- }
+ !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview
+ };
@computed get href() {
if (this.props.hrefs?.length) {
const href = this.props.hrefs[this._hrefInd];
- if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview
- if (href.startsWith("https://en.wikipedia.org/wiki/")) {
- wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500))));
+ if (href.indexOf(Doc.localServerPath()) !== 0) {
+ // link to a web page URL -- try to show a preview
+ if (href.startsWith('https://en.wikipedia.org/wiki/')) {
+ wiki()
+ .page(href.replace('https://en.wikipedia.org/wiki/', ''))
+ .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500)))));
} else {
- setTimeout(action(() => this._toolTipText = "url => " + href));
+ setTimeout(action(() => (this._toolTipText = 'url => ' + href)));
}
- } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
- const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0];
- anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
- if (anchor instanceof Doc && DocListCast(anchor.links).length) {
- this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
- const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
- if (automaticLink) { // automatic links specify the target in the link info, not the source
- const linkTarget = anchor;
- this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
- this._targetDoc = linkTarget;
- } else {
- this._linkSrc = anchor;
- const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
- }
- this._toolTipText = "";
- }
- }));
+ } else {
+ // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated
+ const anchorDoc = href.replace(Doc.localServerPath(), '').split('?')[0];
+ anchorDoc &&
+ DocServer.GetRefField(anchorDoc).then(
+ action(anchor => {
+ if (anchor instanceof Doc && DocListCast(anchor.links).length) {
+ this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
+ const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords;
+ if (automaticLink) {
+ // automatic links specify the target in the link info, not the source
+ const linkTarget = anchor;
+ this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+ this._targetDoc = linkTarget;
+ } else {
+ this._linkSrc = anchor;
+ const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ }
+ this._toolTipText = '';
+ }
+ })
+ );
}
return href;
}
return undefined;
}
deleteLink = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc))
+ );
+ };
nextHref = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
- const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
- if (nextHrefInd !== this._hrefInd) {
- this._linkDoc = undefined;
- this._hrefInd = nextHrefInd;
- }
- }), true);
- }
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ action(() => {
+ const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ if (nextHrefInd !== this._hrefInd) {
+ this._linkDoc = undefined;
+ this._hrefInd = nextHrefInd;
+ }
+ }),
+ true
+ );
+ };
- followLink = (e: React.PointerEvent) => {
+ followLink = () => {
if (this._linkDoc && this._linkSrc) {
LinkDocPreview.Clear();
- LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
+ LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
} else if (this.props.hrefs?.length) {
- this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), "add:right");
+ this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right');
}
- }
+ };
+
+ followLinkPointerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.followLink);
+
width = () => {
if (!this._targetDoc) return 225;
if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) {
- return Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]() / this._targetDoc[HeightSym]();
+ return (Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]()) / this._targetDoc[HeightSym]();
}
return Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225));
- }
+ };
height = () => {
if (!this._targetDoc) return 225;
if (this._targetDoc[WidthSym]() > this._targetDoc?.[HeightSym]()) {
- return Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]() / this._targetDoc[WidthSym]();
+ return (Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]()) / this._targetDoc[WidthSym]();
}
return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225));
- }
+ };
@computed get previewHeader() {
- return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
- <div className="linkDocPreview-info" ref={this._infoRef}>
- <div className="linkDocPreview-title" style={{ pointerEvents: "all" }}
- onPointerDown={e => {
- DragManager.StartDocumentDrag([this._infoRef.current!],
- new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
- e.stopPropagation();
- LinkDocPreview.Clear();
- }}>
- {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
+ return !this._linkDoc || !this._targetDoc || !this._linkSrc ? null : (
+ <div className="linkDocPreview-info">
+ <div className="linkDocPreview-title" style={{ pointerEvents: 'all' }}>
+ {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + '...' : StrCast(this._targetDoc.title)}
<p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
</div>
- <div className="linkDocPreview-buttonBar" >
+ <div className="linkDocPreview-buttonBar">
<Tooltip title={<div className="dash-tooltip">Next Link</div>} placement="top">
- <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? "gray" : "green" }} onPointerDown={this.nextHref}>
+ <div className="linkDocPreview-button" style={{ background: (this.props.hrefs?.length || 0) <= 1 ? 'gray' : 'green' }} onPointerDown={this.nextHref}>
<FontAwesomeIcon className="linkDocPreview-fa-icon" icon="chevron-right" color="white" size="sm" />
</div>
</Tooltip>
@@ -164,20 +189,44 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
</div>
</Tooltip>
</div>
- </div>;
+ </div>
+ );
}
@computed get docPreview() {
const href = this.href; // needs to be here to trigger lookup of web pages and docs on server
- return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
+ return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
<div className="linkDocPreview-inner">
- {!this.props.showHeader ? (null) : this.previewHeader}
- <div className="linkDocPreview-preview-wrapper" style={{ maxHeight: this._toolTipText ? "100%" : undefined, overflow: "auto" }}>
- {this._toolTipText ? this._toolTipText :
- <DocumentView ref={(r) => {
- const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
- }}
+ {!this.props.showHeader ? null : this.previewHeader}
+ <div
+ className="linkDocPreview-preview-wrapper"
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ (e, down, delta) => {
+ if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) {
+ DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
+ LinkDocPreview.Clear();
+ return true;
+ }
+ return false;
+ },
+ emptyFunction,
+ this.followLink,
+ true
+ )
+ }
+ ref={this._infoRef}
+ style={{ maxHeight: this._toolTipText ? '100%' : undefined }}>
+ {this._toolTipText ? (
+ this._toolTipText
+ ) : (
+ <DocumentView
+ ref={r => {
+ const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
+ }}
Document={this._targetDoc!}
moveDocument={returnFalse}
rootSelected={returnFalse}
@@ -185,7 +234,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
docViewPath={returnEmptyDoclist}
ScreenToLocalTransform={Transform.Identity}
isDocumentActive={returnFalse}
- isContentActive={emptyFunction}
+ isContentActive={returnFalse}
addDocument={returnFalse}
removeDocument={returnFalse}
addDocTab={returnFalse}
@@ -200,22 +249,30 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
suppressSetHeight={true}
PanelWidth={this.width}
PanelHeight={this.height}
+ pointerEvents={returnNone}
focus={DocUtils.DefaultFocus}
whenChildContentsActiveChanged={returnFalse}
ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size.
bringToFront={returnFalse}
NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
- />}
+ />
+ )}
</div>
- </div>;
+ </div>
+ );
}
render() {
const borders = 16; // 8px border on each side
- return <div className="linkDocPreview" onPointerDown={this.followLink}
- style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
- {this.docPreview}
- </div>;
+ return (
+ <div
+ className="linkDocPreview"
+ ref={this._linkDocRef}
+ onPointerDown={this.followLinkPointerDown}
+ style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}>
+ {this.docPreview}
+ </div>
+ );
}
-} \ No newline at end of file
+}