diff options
author | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-02-11 16:43:46 -0500 |
---|---|---|
committer | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-02-11 16:43:46 -0500 |
commit | 546540013de0a7cb647f30f1fcb513ce52048b72 (patch) | |
tree | 12b78ea0e29fba23b8557864540984daf9680942 /src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | |
parent | 77b7c3927c454a829d7dbb2748ad322b146265a7 (diff) | |
parent | 890337b525ea460f9986562c047135bc5ca203a6 (diff) |
merging
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | 327 |
1 files changed, 55 insertions, 272 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 5371bd10a..89df5e246 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -1,43 +1,17 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, observable } from "mobx"; import { Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState, Plugin } from "prosemirror-state"; +import { EditorState } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import * as ReactDOM from 'react-dom'; -import wiki from "wikijs"; -import { Doc, DocCastAsync, DocListCast, Opt } from "../../../../fields/Doc"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, Utils } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { LinkManager } from "../../../util/LinkManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { DocumentLinksButton } from "../DocumentLinksButton"; -import { DocumentView } from "../DocumentView"; +import { Doc } from "../../../../fields/Doc"; import { LinkDocPreview } from "../LinkDocPreview"; import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; import { schema } from "./schema_rts"; -import React = require("react"); -export let formattedTextBoxCommentPlugin = new Plugin({ - view(editorView) { return new FormattedTextBoxComment(editorView); } -}); -export function findOtherUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); -} -export function findUserMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.attrs.userid); -} -export function findLinkMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.type === schema.marks.linkAnchor); -} +export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } +export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } +export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); } export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let before = 0; - let nbef = rpos.nodeBefore; + let before = 0, nbef = rpos.nodeBefore; while (nbef && finder(nbef.marks)) { before += nbef.nodeSize; rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); @@ -46,8 +20,7 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma return before; } export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let after = 0; - let naft = rpos.nodeAfter; + let after = 0, naft = rpos.nodeAfter; while (naft && finder(naft.marks)) { after += naft.nodeSize; rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); @@ -59,283 +32,93 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark // this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target. // this will also display metadata information about text when the view is configured to display things like other people who authored text. // - export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; - static tooltipInput: HTMLInputElement; - static start: number; - static end: number; - static mark: Mark; + static startUserMarkRegion: number; + static endUserMarkRegion: number; + static userMark: Mark; static textBox: FormattedTextBox | undefined; - static linkDoc: Doc | undefined; - - static _deleteRef: Opt<HTMLDivElement | null>; - static _followRef: Opt<HTMLDivElement | null>; - static _nextRef: Opt<HTMLDivElement | null>; - - static _lastState?: EditorState; - static _lastView?: EditorView; - - @observable static _hrefInd = 0; - static _hrefs: string[] | undefined = []; constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { - const root = document.getElementById("root"); - FormattedTextBoxComment.tooltipInput = document.createElement("input"); - FormattedTextBoxComment.tooltipInput.type = "checkbox"; - FormattedTextBoxComment.tooltip = document.createElement("div"); - FormattedTextBoxComment.tooltipText = document.createElement("div"); - //FormattedTextBoxComment.tooltipText.style.width = "100%"; - FormattedTextBoxComment.tooltipText.style.height = "100%"; - FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; - FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; - FormattedTextBoxComment.tooltip.style.maxWidth = "400px"; - FormattedTextBoxComment.tooltip.style.maxHeight = "235px"; - //FormattedTextBoxComment.tooltip.style.width = "100%"; - FormattedTextBoxComment.tooltip.style.height = "100%"; - FormattedTextBoxComment.tooltip.style.overflow = "hidden"; - FormattedTextBoxComment.tooltip.style.display = "none"; - // FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); - FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => { - const keep = e.target && (e.target as any).type === "checkbox" ? true : false; - const textBox = FormattedTextBoxComment.textBox; - const linkDoc = FormattedTextBoxComment.linkDoc; - if (linkDoc && !keep && textBox) { - if (linkDoc.author) { - if (FormattedTextBoxComment._deleteRef?.contains(e.target as any)) { - this.deleteLink(); - } else if (FormattedTextBoxComment._nextRef?.contains(e.target as any)) { - FormattedTextBoxComment.showPreview(FormattedTextBoxComment._lastView!, FormattedTextBoxComment._lastState, FormattedTextBoxComment._hrefs?.[(++FormattedTextBoxComment._hrefInd) % FormattedTextBoxComment._hrefs?.length]); - } else { - FormattedTextBoxComment.linkDoc = undefined; - if (linkDoc.type !== DocumentType.LINK) { - textBox.props.addDocTab(linkDoc, e.ctrlKey ? "add" : "add:right"); - } else { - const target = LinkManager.getOppositeAnchor(linkDoc, textBox.dataDoc); - target && LinkManager.FollowLink(linkDoc, textBox.dataDoc, textBox.props, e.altKey); - } - } - } - } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, _width: 200, _height: 400, useCors: true }), "add:right"); - } - keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( - FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); + const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div"); + const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div"); + tooltip.className = "FormattedTextBox-tooltip"; + tooltipText.className = "FormattedTextBox-tooltipText"; + tooltip.style.display = "none"; + tooltip.appendChild(tooltipText); + tooltip.onpointerdown = (e: PointerEvent) => { + const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment; + false && startUserMarkRegion !== undefined && textBox?.adoptAnnotation(startUserMarkRegion, endUserMarkRegion, userMark); e.stopPropagation(); e.preventDefault(); }; - root?.appendChild(FormattedTextBoxComment.tooltip); + document.getElementById("root")?.appendChild(tooltip); } } - - @undoBatch - deleteLink = action(() => { - FormattedTextBoxComment.linkDoc ? LinkManager.Instance.deleteLink(FormattedTextBoxComment.linkDoc) : null; - LinkDocPreview.LinkInfo = undefined; - DocumentLinksButton.EditLink = undefined; - FormattedTextBoxComment.Hide(); - }); - public static Hide() { FormattedTextBoxComment.textBox = undefined; - FormattedTextBoxComment.linkDoc = undefined; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText); - } catch (e) { } + FormattedTextBoxComment.tooltip.style.display = "none"; } - public static SetState(textBox: any, start: number, end: number, mark: Mark) { + public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) { FormattedTextBoxComment.textBox = textBox; - FormattedTextBoxComment.start = start; - FormattedTextBoxComment.end = end; - FormattedTextBoxComment.mark = mark; - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); + FormattedTextBoxComment.startUserMarkRegion = start; + FormattedTextBoxComment.endUserMarkRegion = end; + FormattedTextBoxComment.userMark = mark; + FormattedTextBoxComment.tooltip.style.display = ""; } - static showCommentbox(set: string, view: EditorView, nbef: number) { + static showCommentbox(view: EditorView, nbef: number) { const state = view.state; - if (set !== "none") { - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - const left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; - } - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); + // These are in screen coordinates + const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left) + const left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + FormattedTextBoxComment.tooltip.style.display = ""; } - static update(view: EditorView, lastState?: EditorState, forceUrl: string = "") { - // Don't do anything if the document/selection didn't change - if (!forceUrl && lastState?.doc.eq(view.state.doc) && lastState?.selection.eq(view.state.selection)) { - return; + static update(view: EditorView, lastState?: EditorState, hrefs: string = "") { + if (FormattedTextBoxComment.textBox && (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { + FormattedTextBoxComment.setupPreview(view, FormattedTextBoxComment.textBox, hrefs ? hrefs.trim().split(" ") : undefined); } - FormattedTextBoxComment._lastState = lastState; - FormattedTextBoxComment._lastView = view; - FormattedTextBoxComment._hrefs = forceUrl ? forceUrl.trim().split(" ") : undefined; - FormattedTextBoxComment._hrefInd = 0; - FormattedTextBoxComment.linkDoc = undefined; - FormattedTextBoxComment.showPreview(view, lastState, FormattedTextBoxComment._hrefs?.[FormattedTextBoxComment._hrefInd]); } - static showPreview(view: EditorView, lastState?: EditorState, forceUrl: string = "") { + static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) { const state = view.state; - const textBox = FormattedTextBoxComment.textBox; - if (!textBox || !textBox.props) { - return; - } - let set = "none"; - let nbef = 0; - FormattedTextBoxComment.tooltipInput.style.display = "none"; - FormattedTextBoxComment.tooltip.style.width = ""; - FormattedTextBoxComment.tooltip.style.height = ""; - (FormattedTextBoxComment.tooltipText as any).href = ""; - FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; - FormattedTextBoxComment.tooltipText.style.overflow = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + const nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const noselection = view.state.selection.$from === view.state.selection.$to; + const noselection = state.selection.$from === state.selection.$to; let child: any = null; state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); const mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); + FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } if (mark && child && ((nbef && naft) || !noselection)) { FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString(); - set = ""; - FormattedTextBoxComment.tooltipInput.style.display = ""; - } + FormattedTextBoxComment.showCommentbox(view, nbef); + } else FormattedTextBoxComment.Hide(); } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. - if (set === "none" && state.selection.$from) { - nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + if (state.selection.$from && hrefs) { + const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; - let child: any = null; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); - child = child || (nbef && state.selection.$from.nodeBefore); - const mark = child ? findLinkMark(child.marks) : undefined; - const href = forceUrl || (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href; - if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) { - try { - ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - FormattedTextBoxComment.tooltip.removeChild(FormattedTextBoxComment.tooltipText); - } catch (e) { } - FormattedTextBoxComment.tooltipText = document.createElement("div"); - FormattedTextBoxComment.tooltipText.className = "FormattedTextBoxComment-toolTipText"; - FormattedTextBoxComment.tooltipText.style.width = "100%"; - FormattedTextBoxComment.tooltipText.style.height = "100%"; - FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; - FormattedTextBoxComment.tooltipText.style.cursor = "pointer"; - FormattedTextBoxComment.tooltipText.textContent = "URL: " + href; - (FormattedTextBoxComment.tooltipText as any).href = href; - FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); - - if (href.startsWith("https://en.wikipedia.org/wiki/")) { - wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); - } else { - FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; - FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; - } - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const docTarget = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - FormattedTextBoxComment.tooltipText.textContent = "target not found..."; - (FormattedTextBoxComment.tooltipText as any).href = ""; - docTarget && DocServer.GetRefField(docTarget).then(async linkDoc => { - if (linkDoc instanceof Doc) { - (FormattedTextBoxComment.tooltipText as any).href = href; - FormattedTextBoxComment.linkDoc = linkDoc; - const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); - const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; - if (anchor !== target && anchor && target) { - target._scrollPreviewY = NumCast(anchor?.y); - } - if (target?.author) { - FormattedTextBoxComment.showCommentbox("", view, nbef); - - const title = StrCast(target.title).length > 16 ? StrCast(target.title).substr(0, 16) + "..." : target.title; - - const docPreview = <div className="FormattedTextBoxComment"> - <div className="FormattedTextBoxComment-info"> - <div className="FormattedTextBoxComment-title"> - {title} - {FormattedTextBoxComment.linkDoc.description === "" ? (null) : - <p className="FormattedTextBoxComment-description"> {StrCast(FormattedTextBoxComment.linkDoc.description)}</p>} - </div> - <div className="wrapper" style={{ float: "right" }}> - {(FormattedTextBoxComment._hrefs?.length || 0) <= 1 ? (null) : <Tooltip title={<><div className="dash-tooltip">Next Link</div></>} placement="top"> - <div className="FormattedTextBoxComment-button" ref={(r) => this._nextRef = r}> - <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="chevron-right" color="white" size="sm" /> - </div> - </Tooltip>} - - <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top"> - <div className="FormattedTextBoxComment-button" ref={(r) => this._deleteRef = r}> - <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white" size="sm" /> - </div> - </Tooltip> - - <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top"> - <div className="FormattedTextBoxComment-button" ref={(r) => this._followRef = r}> - <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white" size="sm" /> - </div> - </Tooltip> - </div> - </div> - <div className="FormattedTextBoxComment-preview-wrapper"> - <DocumentView - Document={target} - moveDocument={returnFalse} - rootSelected={returnFalse} - ScreenToLocalTransform={Transform.Identity} - parentActive={returnFalse} - addDocument={returnFalse} - removeDocument={returnFalse} - addDocTab={returnFalse} - pinToPres={returnFalse} - dontRegisterView={true} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - renderDepth={-1} - PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))} - focus={emptyFunction} - whenActiveChanged={returnFalse} - bringToFront={returnFalse} - NativeWidth={Doc.NativeWidth(target) ? (() => Doc.NativeWidth(target)) : undefined} - NativeHeight={Doc.NativeHeight(target) ? (() => Doc.NativeHeight(target)) : undefined} - /> - </div> - </div>; - - FormattedTextBoxComment.showCommentbox("", view, nbef); - - ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText); - - //FormattedTextBoxComment.tooltip.style.width = "100%"; - FormattedTextBoxComment.tooltip.style.height = "100%"; - } - } - }); - } - set = ""; - } + nbef && naft && LinkDocPreview.SetLinkInfo({ + docProps: textBox.props, + linkSrc: textBox.rootDoc, + location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)), + hrefs, + showHeader: true + }); } - FormattedTextBoxComment.showCommentbox(set, view, nbef); } destroy() { } |