import { Node } from "prosemirror-model"; import { AllSelection, Selection, TextSelection } from "prosemirror-state"; import { EditorView, NodeView } from "prosemirror-view"; import React from "react"; import { BehaviorSubject, Observable, Subscription } from "rxjs"; import { map } from "rxjs/operators"; import { CssClassName } from "../constants"; import { EditorSchema } from "../schema"; import { CellSelection } from "../table/CellSelection"; import { el } from "../util/dom"; import { PortalProviderApi } from "../util/PortalProvider"; import { AttachmentService } from "./AttachmentService"; import * as operations from "./operations"; import { AttachmentPreview } from "./react/AttachmentPreview"; import { AttachmentAttrs } from "./schema"; import { decode } from "./util/attrs"; interface State { active: boolean; editable: boolean; node: Node; selected: boolean; } export class AttachmentView implements NodeView { public readonly dom = el("div", CssClassName.ATTACHMENT); private readonly editableSubscription: Subscription; private readonly state: BehaviorSubject; private readonly selectionSubscription: Subscription; private readonly renderSubscription: Subscription; private readonly portalProviderApi: PortalProviderApi; constructor( node: Node, view: EditorView, getPos: () => number, portalProviderApi: PortalProviderApi, attachmentService: AttachmentService, selection$: Observable | null>, editable$: Observable ) { this.portalProviderApi = portalProviderApi; this.state = new BehaviorSubject({ active: false, editable: false, node, selected: false }); this.selectionSubscription = selection$ .pipe( map(selection => { if (selection instanceof TextSelection || selection instanceof CellSelection) { const pos = getPos(); return pos >= selection.from && pos <= selection.to; } else if (selection instanceof AllSelection) { return true; } else { return false; } }) ) .subscribe(inSelection => this.setState({ selected: inSelection })); this.editableSubscription = editable$.subscribe(newEditable => { if (this.state.value.editable !== newEditable) { this.setState({ editable: newEditable }); } }); this.renderSubscription = this.state.subscribe(({ editable, node, active, selected }) => { const attachment = decode(node.attrs as AttachmentAttrs); const style = active ? "active" : selected ? "selected" : undefined; this.portalProviderApi.render( { const tr = view.state.tr; operations.toggleHidePreview(tr, getPos()); view.dispatch(tr); }} onRemove={() => { const tr = view.state.tr; operations.remove(tr, getPos()); view.dispatch(tr); }} onShowPreview={() => { const tr = view.state.tr; operations.toggleHidePreview(tr, getPos()); view.dispatch(tr); }} style={style} />, this.dom ); }); } public update(node: Node) { if (node.type !== this.state.value.node.type) { return false; } this.setState({ node }); return true; } public selectNode() { this.setState({ active: true }); } public deselectNode() { this.setState({ active: false }); } public destroy() { this.portalProviderApi.destroy(this.dom); this.selectionSubscription.unsubscribe(); this.editableSubscription.unsubscribe(); this.renderSubscription.unsubscribe(); } private setState(patch: Partial): void { this.state.next({ ...this.state.value, ...patch }); } }