UNPKG

@atlaskit/editor-plugin-synced-block

Version:

SyncedBlock plugin for @atlaskit/editor-core

214 lines (209 loc) • 11.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.bodiedSyncBlockNodeView = exports.SourceSyncBlockLabel = exports.BodiedSyncBlock = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _analytics = require("@atlaskit/editor-common/analytics"); var _errorBoundary = require("@atlaskit/editor-common/error-boundary"); var _syncBlock = require("@atlaskit/editor-common/sync-block"); var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity"); var _model = require("@atlaskit/editor-prosemirror/model"); var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled"); var _getUnpublishedSourceType = require("../ui/getUnpublishedSourceType"); var _SyncBlockLabel = require("../ui/SyncBlockLabel"); var _isEmptySourceSyncBlock = require("./isEmptySourceSyncBlock"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } var toDOM = function toDOM(node) { return ['div', { class: "".concat(_syncBlock.BodiedSyncBlockSharedCssClassName.prefix, " bodiedSyncBlockView-content-wrap"), localid: node.attrs.localId, resourceid: node.attrs.resourceId }, ['div', { class: _syncBlock.BodiedSyncBlockSharedCssClassName.content, contenteditable: 'true' }, 0]]; }; var SourceSyncBlockLabel = exports.SourceSyncBlockLabel = function SourceSyncBlockLabel(_ref) { var localId = _ref.localId, resourceId = _ref.resourceId, sourceManager = _ref.sourceManager; var isSamePageSyncEnabled = (0, _isExperimentEnabled.isExperimentEnabled)('editor-synced-block-same-page-sync'); var _useState = (0, _react.useState)(function () { var _sourceManager$getLoc; return isSamePageSyncEnabled && (sourceManager === null || sourceManager === void 0 || (_sourceManager$getLoc = sourceManager.getLocalSourceSnapshot(resourceId)) === null || _sourceManager$getLoc === void 0 ? void 0 : _sourceManager$getLoc.status) === 'unpublished'; }), _useState2 = (0, _slicedToArray2.default)(_useState, 2), isUnpublished = _useState2[0], setIsUnpublished = _useState2[1]; var _useState3 = (0, _react.useState)(), _useState4 = (0, _slicedToArray2.default)(_useState3, 2), sourceInfo = _useState4[0], setSourceInfo = _useState4[1]; (0, _react.useEffect)(function () { if (!isSamePageSyncEnabled) { return; } var isMounted = true; void (sourceManager === null || sourceManager === void 0 ? void 0 : sourceManager.getSyncBlockSourceInfo(localId).then(function (nextSourceInfo) { if (isMounted) { setSourceInfo(nextSourceInfo); } })); var unsubscribe = sourceManager === null || sourceManager === void 0 ? void 0 : sourceManager.subscribeToLocalSource(resourceId, function (snapshot) { return setIsUnpublished((snapshot === null || snapshot === void 0 ? void 0 : snapshot.status) === 'unpublished'); }); return function () { isMounted = false; unsubscribe === null || unsubscribe === void 0 || unsubscribe(); }; }, [isSamePageSyncEnabled, localId, resourceId, sourceManager]); return /*#__PURE__*/_react.default.createElement(_SyncBlockLabel.SyncBlockLabel, { isSource: true, localId: localId, unpublishedInfo: isSamePageSyncEnabled && isUnpublished ? { sourceType: (0, _getUnpublishedSourceType.getUnpublishedSourceType)({ sourceAri: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.sourceAri, sourceProduct: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.productType }), variant: 'source' } : undefined }); }; var BodiedSyncBlock = exports.BodiedSyncBlock = /*#__PURE__*/function () { function BodiedSyncBlock(node, view, getPos, api, nodeViewPortalProviderAPI, sourceManager) { var _this = this; (0, _classCallCheck2.default)(this, BodiedSyncBlock); this.node = node; this.view = view; this.getPos = getPos; this.api = api; this.nodeViewPortalProviderAPI = nodeViewPortalProviderAPI; // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- NodeView serialization must target active runtime document var _DOMSerializer$render = _model.DOMSerializer.renderSpec(document, toDOM(this.node)), dom = _DOMSerializer$render.dom, contentDOM = _DOMSerializer$render.contentDOM; // eslint-disable-next-line @atlaskit/editor/no-as-casting this.dom = dom; // eslint-disable-next-line @atlaskit/editor/no-as-casting this.contentDOM = contentDOM; // Render the label into a separate container so portal implementations that // write directly into the target do not clobber contentDOM. This also covers // SSR, where the portal's renderToStaticMarkup + innerHTML would clobber it. // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage -- NodeView DOM must be created against active runtime document var labelContainer = document.createElement('div'); this.dom.appendChild(labelContainer); this.labelKey = crypto.randomUUID(); this.nodeViewPortalProviderAPI.render(function () { var _this$api; return /*#__PURE__*/_react.default.createElement(_errorBoundary.ErrorBoundary, { component: _analytics.ACTION_SUBJECT.SYNCED_BLOCK, componentId: _analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_LABEL, dispatchAnalyticsEvent: (_this$api = _this.api) === null || _this$api === void 0 || (_this$api = _this$api.analytics) === null || _this$api === void 0 ? void 0 : _this$api.actions.fireAnalyticsEvent, fallbackComponent: null }, /*#__PURE__*/_react.default.createElement(SourceSyncBlockLabel, { localId: node.attrs.localId, resourceId: node.attrs.resourceId, sourceManager: sourceManager })); }, labelContainer, this.labelKey); this.updateContentEditable({}); this.updateEmptyClass(); this.handleConnectivityModeChange(); this.handleViewModeChange(); // Cache is populated in state.init() and updated in appendTransaction, // so no additional updateSyncBlockData call is needed here. } return (0, _createClass2.default)(BodiedSyncBlock, [{ key: "updateContentEditable", value: function updateContentEditable(_ref2) { var _this$api2, _this$api3; var nextConnectivityMode = _ref2.nextConnectivityMode, nextViewMode = _ref2.nextViewMode; var connectivityMode = nextConnectivityMode !== null && nextConnectivityMode !== void 0 ? nextConnectivityMode : (_this$api2 = this.api) === null || _this$api2 === void 0 || (_this$api2 = _this$api2.connectivity) === null || _this$api2 === void 0 || (_this$api2 = _this$api2.sharedState) === null || _this$api2 === void 0 || (_this$api2 = _this$api2.currentState()) === null || _this$api2 === void 0 ? void 0 : _this$api2.mode; var viewMode = nextViewMode !== null && nextViewMode !== void 0 ? nextViewMode : (_this$api3 = this.api) === null || _this$api3 === void 0 || (_this$api3 = _this$api3.editorViewMode) === null || _this$api3 === void 0 || (_this$api3 = _this$api3.sharedState) === null || _this$api3 === void 0 || (_this$api3 = _this$api3.currentState()) === null || _this$api3 === void 0 ? void 0 : _this$api3.mode; var isOnline = !(0, _editorPluginConnectivity.isOfflineMode)(connectivityMode); var isEditMode = viewMode !== 'view'; var shouldBeEditable = isOnline && isEditMode; this.contentDOM.setAttribute('contenteditable', shouldBeEditable ? 'true' : 'false'); } }, { key: "handleConnectivityModeChange", value: function handleConnectivityModeChange() { var _this$api4, _this2 = this; if ((_this$api4 = this.api) !== null && _this$api4 !== void 0 && _this$api4.connectivity) { this.cleanupConnectivityModeListener = this.api.connectivity.sharedState.onChange(function (_ref3) { var nextSharedState = _ref3.nextSharedState; _this2.updateContentEditable({ nextConnectivityMode: nextSharedState.mode }); }); } } }, { key: "handleViewModeChange", value: function handleViewModeChange() { var _this$api5, _this3 = this; if ((_this$api5 = this.api) !== null && _this$api5 !== void 0 && _this$api5.editorViewMode) { this.cleanupViewModeListener = this.api.editorViewMode.sharedState.onChange(function (_ref4) { var nextSharedState = _ref4.nextSharedState; _this3.updateContentEditable({ nextViewMode: nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.mode }); }); } } /** * Marks the block as empty so the source placeholder can be shown purely from document state. * See `isEmptySourceSyncBlock` for why the DOM is not used to decide this. */ }, { key: "updateEmptyClass", value: function updateEmptyClass() { this.dom.classList.toggle(_syncBlock.BodiedSyncBlockSharedCssClassName.empty, (0, _isEmptySourceSyncBlock.isEmptySourceSyncBlock)(this.node)); } }, { key: "update", value: function update(node) { if (this.node.type !== node.type) { return false; } // Cache updates are handled in appendTransaction where we can // filter out non-user changes (remote collab, table auto-scale, etc.) this.node = node; this.updateEmptyClass(); return true; } }, { key: "ignoreMutation", value: function ignoreMutation(mutation) { if (mutation.type === 'selection') { return false; } return true; } }, { key: "destroy", value: function destroy() { var _this$cleanupConnecti, _this$cleanupViewMode; (_this$cleanupConnecti = this.cleanupConnectivityModeListener) === null || _this$cleanupConnecti === void 0 || _this$cleanupConnecti.call(this); (_this$cleanupViewMode = this.cleanupViewModeListener) === null || _this$cleanupViewMode === void 0 || _this$cleanupViewMode.call(this); this.nodeViewPortalProviderAPI.remove(this.labelKey); } }]); }(); var bodiedSyncBlockNodeView = exports.bodiedSyncBlockNodeView = function bodiedSyncBlockNodeView(props) { var api = props.api, nodeViewPortalProviderAPI = props.pmPluginFactoryParams.nodeViewPortalProviderAPI, syncBlockStore = props.syncBlockStore; return function (node, view, getPos) { return new BodiedSyncBlock(node, view, getPos, api, nodeViewPortalProviderAPI, syncBlockStore === null || syncBlockStore === void 0 ? void 0 : syncBlockStore.sourceManager); }; };