import { TextSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; import * as React from 'react'; import { IReactionDisposer, computed, reaction } from 'mobx'; import { Doc } from '../../../../fields/Doc'; import { DocServer } from '../../../DocServer'; import { NumCast } from '../../../../fields/Types'; interface IDashDocCommentViewInternal { docId: string; view: any; getPos: any; setHeight: (height: number) => void; } export class DashDocCommentViewInternal extends React.Component { _reactionDisposer: IReactionDisposer | undefined; constructor(props: any) { super(props); this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this); this.onPointerEnterCollapsed = this.onPointerEnterCollapsed.bind(this); this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this); this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this); } componentDidMount(): void { this._reactionDisposer?.(); this._dashDoc.then(doc => { if (doc instanceof Doc) { this._reactionDisposer = reaction( () => NumCast((doc as Doc)._height), hgt => this.props.setHeight(hgt), { fireImmediately: true } ); } }); } componentWillUnmount(): void { this._reactionDisposer?.(); } @computed get _dashDoc() { return DocServer.GetRefField(this.props.docId); } onPointerLeaveCollapsed = (e: any) => { this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); e.preventDefault(); e.stopPropagation(); }; onPointerEnterCollapsed = (e: any) => { this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); e.preventDefault(); e.stopPropagation(); }; onPointerUpCollapsed = (e: any) => { const target = this.targetNode(); if (target) { const expand = target.hidden; const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: !target.node.attrs.hidden }); this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs setTimeout(() => { expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (err) { /* empty */ } }, 0); } e.stopPropagation(); }; onPointerDownCollapsed = (e: any) => { e.stopPropagation(); }; targetNode = () => { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor const { state } = this.props.view; for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) { const m = state.doc.nodeAt(i); if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docId === this.props.docId) { return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean }; } } const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: this.props.docId, float: 'right' }); this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc)); setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (err) { /* empty */ } }, 0); return undefined; }; render() { return ( ); } } // creates an inline comment in a note when '>>' is typed. // the comment sits on the right side of the note and vertically aligns with its anchor in the text. // the comment can be toggled on/off with the '<-' text anchor. export class DashDocCommentView { dom: HTMLDivElement; // container for label and value root: any; node: any; constructor(node: any, view: any, getPos: any) { this.node = node; this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; this.dom.style.height = node.attrs.height; this.dom.style.fontWeight = 'bold'; this.dom.style.position = 'relative'; this.dom.style.display = 'inline-block'; this.dom.onkeypress = function (e: any) { e.stopPropagation(); }; this.dom.onkeydown = function (e: any) { e.stopPropagation(); }; this.dom.onkeyup = function (e: any) { e.stopPropagation(); }; this.dom.onmousedown = function (e: any) { e.stopPropagation(); }; this.root = ReactDOM.createRoot(this.dom); this.root.render(); (this as any).dom = this.dom; } setHeight = (hgt: number) => { !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => { doc instanceof Doc && (this.dom.style.height = hgt + ''); }); }; destroy() { this.root.unmount(); } deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } selectNode() { this.dom.classList.add('ProseMirror-selectednode'); } }