@atlaskit/editor-plugin-synced-block
Version:
SyncedBlock plugin for @atlaskit/editor-core
259 lines (258 loc) • 12.9 kB
JavaScript
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);
};
};