UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

259 lines (258 loc) 12.9 kB
import React from 'react'; import { ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics'; import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary'; import ReactNodeView from '@atlaskit/editor-common/react-node-view'; import { BodiedSyncBlockSharedCssClassName } from '@atlaskit/editor-common/sync-block'; import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity'; import { DOMSerializer } from '@atlaskit/editor-prosemirror/model'; import { fg } from '@atlaskit/platform-feature-flags'; import { BodiedSyncBlockWrapper } from '../ui/BodiedSyncBlockWrapper'; import { SyncBlockLabel } from '../ui/SyncBlockLabel'; const toDOMOld = () => ['div', { class: BodiedSyncBlockSharedCssClassName.content, contenteditable: true }, 0]; class BodiedSyncBlockOld extends ReactNodeView { constructor(props) { super(props.node, props.view, props.getPos, props.portalProviderAPI, props.eventDispatcher, props); this.api = props.api; this.syncBlockStore = props.syncBlockStore; this.handleConnectivityModeChange(); this.handleViewModeChange(); } updateContentEditable({ contentDOM, nextConnectivityMode, nextViewMode }) { var _this$api, _this$api$connectivit, _this$api$connectivit2, _this$api$connectivit3, _this$api2, _this$api2$editorView, _this$api2$editorView2, _this$api2$editorView3; const connectivityMode = nextConnectivityMode !== null && nextConnectivityMode !== void 0 ? nextConnectivityMode : (_this$api = this.api) === null || _this$api === void 0 ? void 0 : (_this$api$connectivit = _this$api.connectivity) === null || _this$api$connectivit === void 0 ? void 0 : (_this$api$connectivit2 = _this$api$connectivit.sharedState) === null || _this$api$connectivit2 === void 0 ? void 0 : (_this$api$connectivit3 = _this$api$connectivit2.currentState()) === null || _this$api$connectivit3 === void 0 ? void 0 : _this$api$connectivit3.mode; const viewMode = nextViewMode !== null && nextViewMode !== void 0 ? nextViewMode : (_this$api2 = this.api) === null || _this$api2 === void 0 ? void 0 : (_this$api2$editorView = _this$api2.editorViewMode) === null || _this$api2$editorView === void 0 ? void 0 : (_this$api2$editorView2 = _this$api2$editorView.sharedState) === null || _this$api2$editorView2 === void 0 ? void 0 : (_this$api2$editorView3 = _this$api2$editorView2.currentState()) === null || _this$api2$editorView3 === void 0 ? void 0 : _this$api2$editorView3.mode; const isOnline = !isOfflineMode(connectivityMode); const isEditMode = viewMode !== 'view'; const shouldBeEditable = isOnline && isEditMode; contentDOM === null || contentDOM === void 0 ? void 0 : contentDOM.setAttribute('contenteditable', shouldBeEditable ? 'true' : 'false'); } handleConnectivityModeChange() { var _this$api3; if ((_this$api3 = this.api) !== null && _this$api3 !== void 0 && _this$api3.connectivity) { this.cleanupConnectivityModeListener = this.api.connectivity.sharedState.onChange(({ nextSharedState }) => { this.updateContentEditable({ contentDOM: this.contentDOM, nextConnectivityMode: nextSharedState.mode }); }); } } handleViewModeChange() { var _this$api4; if ((_this$api4 = this.api) !== null && _this$api4 !== void 0 && _this$api4.editorViewMode) { this.cleanupViewModeListener = this.api.editorViewMode.sharedState.onChange(({ nextSharedState }) => { this.updateContentEditable({ contentDOM: this.contentDOM, nextViewMode: nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode }); }); } } createDomRef() { const domRef = document.createElement('div'); domRef.classList.add(BodiedSyncBlockSharedCssClassName.prefix); return domRef; } render(_props, forwardRef) { var _this$api$syncedBlock, _this$api5, _this$api5$syncedBloc, _this$api5$syncedBloc2, _this$api6, _this$api6$analytics; // Use passed syncBlockStore for SSR where sharedState.currentState() is delayed const syncBlockStore = (_this$api$syncedBlock = (_this$api5 = this.api) === null || _this$api5 === void 0 ? void 0 : (_this$api5$syncedBloc = _this$api5.syncedBlock.sharedState) === null || _this$api5$syncedBloc === void 0 ? void 0 : (_this$api5$syncedBloc2 = _this$api5$syncedBloc.currentState()) === null || _this$api5$syncedBloc2 === void 0 ? void 0 : _this$api5$syncedBloc2.syncBlockStore) !== null && _this$api$syncedBlock !== void 0 ? _this$api$syncedBlock : this.syncBlockStore; if (!syncBlockStore) { return null; } return /*#__PURE__*/React.createElement(ErrorBoundary, { component: ACTION_SUBJECT.SYNCED_BLOCK, dispatchAnalyticsEvent: (_this$api6 = this.api) === null || _this$api6 === void 0 ? void 0 : (_this$api6$analytics = _this$api6.analytics) === null || _this$api6$analytics === void 0 ? void 0 : _this$api6$analytics.actions.fireAnalyticsEvent, fallbackComponent: null }, /*#__PURE__*/React.createElement(BodiedSyncBlockWrapper, { ref: forwardRef, syncBlockStore: syncBlockStore, node: this.node })); } getContentDOM() { const { dom, contentDOM } = DOMSerializer.renderSpec(document, toDOMOld()); // In SSR, the first check won't work, so fallback to nodeType check if (dom instanceof HTMLElement || dom.nodeType === 1) { this.updateContentEditable({ contentDOM }); // eslint-disable-next-line @atlaskit/editor/no-as-casting return { dom: dom, contentDOM }; } return undefined; } destroy() { if (this.cleanupConnectivityModeListener) { this.cleanupConnectivityModeListener(); } if (this.cleanupViewModeListener) { this.cleanupViewModeListener(); } } } export const bodiedSyncBlockNodeViewOld = ({ pluginOptions, pmPluginFactoryParams, api, syncBlockStore }) => (node, view, getPos) => { const { portalProviderAPI, eventDispatcher } = pmPluginFactoryParams; return new BodiedSyncBlockOld({ api, pluginOptions, node, view, getPos, portalProviderAPI, eventDispatcher, syncBlockStore }).init(); }; const toDOM = node => ['div', { class: `${BodiedSyncBlockSharedCssClassName.prefix} bodiedSyncBlockView-content-wrap`, localid: node.attrs.localId, resourceid: node.attrs.resourceId }, ['div', { class: BodiedSyncBlockSharedCssClassName.content, contenteditable: 'true' }, 0]]; export class BodiedSyncBlock { constructor(node, view, getPos, api, nodeViewPortalProviderAPI, syncBlockStore) { this.node = node; this.view = view; this.getPos = getPos; this.api = api; this.syncBlockStore = syncBlockStore; this.nodeViewPortalProviderAPI = nodeViewPortalProviderAPI; const { dom, contentDOM } = DOMSerializer.renderSpec(document, toDOM(this.node)); // eslint-disable-next-line @atlaskit/editor/no-as-casting this.dom = dom; // eslint-disable-next-line @atlaskit/editor/no-as-casting this.contentDOM = contentDOM; this.labelKey = crypto.randomUUID(); this.nodeViewPortalProviderAPI.render(() => { var _this$api7, _this$api7$analytics; return /*#__PURE__*/React.createElement(ErrorBoundary, { component: ACTION_SUBJECT.SYNCED_BLOCK, componentId: ACTION_SUBJECT_ID.SYNCED_BLOCK_LABEL, dispatchAnalyticsEvent: (_this$api7 = this.api) === null || _this$api7 === void 0 ? void 0 : (_this$api7$analytics = _this$api7.analytics) === null || _this$api7$analytics === void 0 ? void 0 : _this$api7$analytics.actions.fireAnalyticsEvent, fallbackComponent: null }, /*#__PURE__*/React.createElement(SyncBlockLabel, { isSource: true, localId: node.attrs.localId })); }, this.dom, this.labelKey); this.updateContentEditable({}); this.handleConnectivityModeChange(); this.handleViewModeChange(); // update sync block data on initial creation // When fg is ON, cache is populated in state.init() and updated in appendTransaction if (!fg('platform_synced_block_update_refactor')) { var _this$syncedBlockStor; (_this$syncedBlockStor = this.syncedBlockStore) === null || _this$syncedBlockStor === void 0 ? void 0 : _this$syncedBlockStor.sourceManager.updateSyncBlockData(node, false); } } updateContentEditable({ nextConnectivityMode, nextViewMode }) { var _this$api8, _this$api8$connectivi, _this$api8$connectivi2, _this$api8$connectivi3, _this$api9, _this$api9$editorView, _this$api9$editorView2, _this$api9$editorView3; const connectivityMode = nextConnectivityMode !== null && nextConnectivityMode !== void 0 ? nextConnectivityMode : (_this$api8 = this.api) === null || _this$api8 === void 0 ? void 0 : (_this$api8$connectivi = _this$api8.connectivity) === null || _this$api8$connectivi === void 0 ? void 0 : (_this$api8$connectivi2 = _this$api8$connectivi.sharedState) === null || _this$api8$connectivi2 === void 0 ? void 0 : (_this$api8$connectivi3 = _this$api8$connectivi2.currentState()) === null || _this$api8$connectivi3 === void 0 ? void 0 : _this$api8$connectivi3.mode; const viewMode = nextViewMode !== null && nextViewMode !== void 0 ? nextViewMode : (_this$api9 = this.api) === null || _this$api9 === void 0 ? void 0 : (_this$api9$editorView = _this$api9.editorViewMode) === null || _this$api9$editorView === void 0 ? void 0 : (_this$api9$editorView2 = _this$api9$editorView.sharedState) === null || _this$api9$editorView2 === void 0 ? void 0 : (_this$api9$editorView3 = _this$api9$editorView2.currentState()) === null || _this$api9$editorView3 === void 0 ? void 0 : _this$api9$editorView3.mode; const isOnline = !isOfflineMode(connectivityMode); const isEditMode = viewMode !== 'view'; const shouldBeEditable = isOnline && isEditMode; this.contentDOM.setAttribute('contenteditable', shouldBeEditable ? 'true' : 'false'); } handleConnectivityModeChange() { var _this$api0; if ((_this$api0 = this.api) !== null && _this$api0 !== void 0 && _this$api0.connectivity) { this.cleanupConnectivityModeListener = this.api.connectivity.sharedState.onChange(({ nextSharedState }) => { this.updateContentEditable({ nextConnectivityMode: nextSharedState.mode }); }); } } handleViewModeChange() { var _this$api1; if ((_this$api1 = this.api) !== null && _this$api1 !== void 0 && _this$api1.editorViewMode) { this.cleanupViewModeListener = this.api.editorViewMode.sharedState.onChange(({ nextSharedState }) => { this.updateContentEditable({ nextViewMode: nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode }); }); } } get syncedBlockStore() { var _this$api$syncedBlock2, _this$api10, _this$api10$syncedBlo, _this$api10$syncedBlo2; return (_this$api$syncedBlock2 = (_this$api10 = this.api) === null || _this$api10 === void 0 ? void 0 : (_this$api10$syncedBlo = _this$api10.syncedBlock.sharedState) === null || _this$api10$syncedBlo === void 0 ? void 0 : (_this$api10$syncedBlo2 = _this$api10$syncedBlo.currentState()) === null || _this$api10$syncedBlo2 === void 0 ? void 0 : _this$api10$syncedBlo2.syncBlockStore) !== null && _this$api$syncedBlock2 !== void 0 ? _this$api$syncedBlock2 : this.syncBlockStore; } update(node) { if (this.node.type !== node.type) { return false; } if (node !== this.node) { // When fg is ON, cache updates are handled in appendTransaction where we can // filter out non-user changes (remote collab, table auto-scale, etc.) if (!fg('platform_synced_block_update_refactor')) { var _this$syncedBlockStor2; (_this$syncedBlockStor2 = this.syncedBlockStore) === null || _this$syncedBlockStor2 === void 0 ? void 0 : _this$syncedBlockStor2.sourceManager.updateSyncBlockData(node, false); } } this.node = node; return true; } ignoreMutation(mutation) { if (mutation.type === 'selection') { return false; } return true; } destroy() { var _this$cleanupConnecti, _this$cleanupViewMode; (_this$cleanupConnecti = this.cleanupConnectivityModeListener) === null || _this$cleanupConnecti === void 0 ? void 0 : _this$cleanupConnecti.call(this); (_this$cleanupViewMode = this.cleanupViewModeListener) === null || _this$cleanupViewMode === void 0 ? void 0 : _this$cleanupViewMode.call(this); this.nodeViewPortalProviderAPI.remove(this.labelKey); } } export const bodiedSyncBlockNodeView = props => { const { api, syncBlockStore, pmPluginFactoryParams: { nodeViewPortalProviderAPI } } = props; return (node, view, getPos) => { return new BodiedSyncBlock(node, view, getPos, api, nodeViewPortalProviderAPI, syncBlockStore); }; };