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 { DocServer } from '../../DocServer'; import { Docs, DocUtils } from "../../documents/Documents"; import { LinkManager } from '../../util/LinkManager'; import { Transform } from "../../util/Transform"; import { undoBatch } from '../../util/UndoManager'; import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; import './LinkDocPreview.scss'; import React = require("react"); import { DocumentType } from '../../documents/DocumentTypes'; interface LinkDocPreviewProps { linkDoc?: Doc; linkSrc?: Doc; docProps: DocumentViewSharedProps; location: number[]; hrefs?: string[]; showHeader?: boolean; } @observer export class LinkDocPreview extends React.Component { @action public static Clear() { LinkDocPreview.LinkInfo = undefined; } @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); } _infoRef = React.createRef(); @observable public static LinkInfo: Opt; @observable _targetDoc: Opt; @observable _linkDoc: Opt; @observable _linkSrc: Opt; @observable _toolTipText = ""; @observable _hrefInd = 0; @action init() { var linkTarget = this.props.linkDoc; this._linkSrc = this.props.linkSrc; this._linkDoc = this.props.linkDoc; const anchor1 = this._linkDoc?.anchor1 as Doc; const anchor2 = this._linkDoc?.anchor2 as Doc; 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)); } else { this._targetDoc = linkTarget; } 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); } componentWillUnmount() { LinkDocPreview.SetLinkInfo(undefined); document.removeEventListener("pointerdown", this.onPointerDown); } 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 } @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)))); } else { 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]; 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))); } nextHref = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1))); } followLink = (e: React.PointerEvent) => { if (this._linkDoc && this._linkSrc) { LinkDocPreview.Clear(); LinkManager.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], _width: 200, _height: 400, useCors: true }), "add:right"); } } 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, 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, NumCast(this._targetDoc?.[HeightSym](), 225)); } @computed get previewHeader() { return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
{StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}

{StrCast(this._linkDoc.description)}

Next Link
} placement="top">
Delete Link
} placement="top">
; } @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) :
{!this.props.showHeader ? (null) : this.previewHeader}
{this._toolTipText ? this._toolTipText : { const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!); targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor); }} Document={this._targetDoc!} moveDocument={returnFalse} rootSelected={returnFalse} styleProvider={this.props.docProps?.styleProvider} layerProvider={this.props.docProps?.layerProvider} docViewPath={returnEmptyDoclist} ScreenToLocalTransform={Transform.Identity} isDocumentActive={returnFalse} isContentActive={emptyFunction} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} dontRegisterView={true} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} renderDepth={-1} PanelWidth={this.width} PanelHeight={this.height} 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} />}
; } render() { const borders = 16; // 8px border on each side return
{this.docPreview}
; } }