@atlaskit/editor-plugin-synced-block
Version:
SyncedBlock plugin for @atlaskit/editor-core
101 lines (97 loc) • 6.63 kB
JavaScript
"use strict";
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SyncBlockRendererWrapper = void 0;
var _react = _interopRequireWildcard(require("react"));
var _bindEventListener = require("bind-event-listener");
var _syncBlock = require("@atlaskit/editor-common/sync-block");
var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _SyncBlockLabel = require("./SyncBlockLabel");
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 SyncBlockRendererWrapperDataId = 'sync-block-plugin-renderer-wrapper';
var SyncBlockRendererWrapperComponent = function SyncBlockRendererWrapperComponent(_ref) {
var _api$analytics, _syncBlockFetchResult, _syncBlockFetchResult2, _syncBlockFetchResult3;
var syncedBlockRenderer = _ref.syncedBlockRenderer,
syncBlockStore = _ref.syncBlockStore,
node = _ref.node,
resourceId = _ref.resourceId,
localId = _ref.localId,
api = _ref.api;
var syncBlockFetchResult = (0, _editorSyncedBlockProvider.useFetchSyncBlockData)(syncBlockStore, resourceId, localId, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 ? void 0 : _api$analytics.fireAnalyticsEvent);
var title = (0, _editorSyncedBlockProvider.useFetchSyncBlockTitle)(syncBlockStore, node);
var contentUpdatedAt = syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult === void 0 || (_syncBlockFetchResult = _syncBlockFetchResult.data) === null || _syncBlockFetchResult === void 0 ? void 0 : _syncBlockFetchResult.contentUpdatedAt;
var isUnpublishedBlock = ((_syncBlockFetchResult2 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult2 === void 0 || (_syncBlockFetchResult2 = _syncBlockFetchResult2.data) === null || _syncBlockFetchResult2 === void 0 ? void 0 : _syncBlockFetchResult2.status) === 'unpublished';
var isUnsyncedBlock = isUnpublishedBlock || (syncBlockFetchResult === null || syncBlockFetchResult === void 0 || (_syncBlockFetchResult3 = syncBlockFetchResult.syncBlockInstance) === null || _syncBlockFetchResult3 === void 0 || (_syncBlockFetchResult3 = _syncBlockFetchResult3.error) === null || _syncBlockFetchResult3 === void 0 ? void 0 : _syncBlockFetchResult3.type) === _editorSyncedBlockProvider.SyncBlockError.NotFound;
// Evaluated unconditionally so the experiment exposure is tracked correctly
// (recorded on every render, not lazily on first click).
var isSyncBlockActivationEnabled = (0, _expValEquals.expValEquals)('platform_editor_sync_block_activation', 'isEnabled', true);
// Prevent editing in the contentEditable renderer wrapper. We set
// contentEditable="true" to enable text selection (creating an editable
// island inside ProseMirror's contentEditable="false" nodeview wrapper),
// but users must not be able to type into or modify the renderer content.
var preventInput = (0, _react.useCallback)(function (e) {
e.preventDefault();
}, []);
// Browsers suppress link navigation on a plain click inside a
// contentEditable region (they place the caret instead), which breaks links
// rendered in the content — e.g. the "when the page is published" link in
// the unpublished error card. Re-trigger navigation for _blank anchors
// ourselves. A native listener (rather than an onClick on the div) is used
// so we delegate link clicks without making the container itself interactive.
var rendererRef = (0, _react.useRef)(null);
(0, _react.useEffect)(function () {
var containerEl = rendererRef.current;
if (!containerEl || !isSyncBlockActivationEnabled) {
return;
}
var unbind = (0, _bindEventListener.bind)(containerEl, {
type: 'click',
listener: function listener(e) {
// Let the browser handle modified/non-primary clicks (open-in-new-tab,
// etc.) natively so we don't open a second tab on top of it.
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
return;
}
var target = e.target instanceof Element ? e.target : null;
var anchor = target === null || target === void 0 ? void 0 : target.closest('a[href]');
if (!(anchor instanceof HTMLAnchorElement) || e.defaultPrevented || anchor.target !== '_blank') {
return;
}
window.open(anchor.href, '_blank', 'noopener,noreferrer');
}
});
return unbind;
}, [isSyncBlockActivationEnabled]);
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("div", {
ref: rendererRef,
"data-testid": SyncBlockRendererWrapperDataId
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
,
className: _syncBlock.SyncBlockSharedCssClassName.renderer,
contentEditable: true,
suppressContentEditableWarning: true
// Prevent the contentEditable div from being keyboard-focusable.
// It is only used to enable text selection, not as an input target.
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
,
tabIndex: -1,
onBeforeInput: preventInput,
onPaste: preventInput
// eslint-disable-next-line @atlaskit/design-system/no-direct-use-of-web-platform-drag-and-drop
,
onDrop: preventInput
}, syncedBlockRenderer({
syncBlockFetchResult: syncBlockFetchResult,
api: api
})), /*#__PURE__*/_react.default.createElement(_SyncBlockLabel.SyncBlockLabel, {
isSource: false,
title: title,
contentUpdatedAt: contentUpdatedAt,
localId: localId,
isUnsyncedBlock: isUnsyncedBlock
}));
};
var SyncBlockRendererWrapper = exports.SyncBlockRendererWrapper = /*#__PURE__*/_react.default.memo(SyncBlockRendererWrapperComponent);