@atlaskit/editor-plugin-unsupported-content
Version:
Unsupported Content plugin for @atlaskit/editor-core
170 lines (163 loc) • 8.88 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UnsupportedBlockNodeView = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _bindEventListener = require("bind-event-listener");
var _enums = require("@atlaskit/editor-common/analytics/types/enums");
var _unsupportedContent = require("@atlaskit/editor-common/messages/unsupportedContent");
var _unsupportedContent2 = require("@atlaskit/editor-common/styles/unsupported-content");
var _trackUnsupportedContent = require("@atlaskit/editor-common/utils/track-unsupported-content");
var _vanillaTooltip = require("@atlaskit/editor-common/vanilla-tooltip");
var _model = require("@atlaskit/editor-prosemirror/model");
var _getUnsupportedContent = require("./get-unsupported-content");
var _getUnsupportedContentIconSpec = require("./get-unsupported-content-icon-spec");
/**
* CSS class names for the vanilla unsupported block node view elements.
* Corresponding styles are defined in EditorContentContainer (gated behind
* the platform_editor_vanilla_node_views_phase1 experiment).
*
* ⚠️ Renaming any of these requires the same change in BOTH copies of the styles:
* - `editor-core/src/ui/EditorContentContainer/styles/unsupportedStyles.ts` (emotion)
* - `editor-core/src/ui/EditorContentContainer/EditorContentContainer-compiled.tsx` (cssMap)
*
* These cannot be imported by either stylesheet: `cssMap` keys must be statically defined, so
* the compiled copy hardcodes the strings — as it already does for `UnsupportedSharedCssClassName`.
* A rename that misses a stylesheet fails loudly in the unsupported-content VR tests, which
* assert the styled output rather than the class names.
*/
var VanillaUnsupportedCssClassName = {
BLOCK_INNER: 'unsupported-block-vanilla-inner',
ICON: 'unsupported-vanilla-icon'
};
var TOOLTIP_CLASS_NAME = 'unsupported-content-tooltip';
/** Distinct from the inline node view's prefix, so the counter fallback below cannot collide. */
var TOOLTIP_ID_PREFIX = 'unsupported-block-content-tooltip';
/** Classes applied to the tooltip element: the shared default look, plus our own hook. */
var TOOLTIP_CLASS_NAMES = "".concat(_vanillaTooltip.VANILLA_TOOLTIP_DEFAULT_CLASS, " ").concat(TOOLTIP_CLASS_NAME);
/**
* `crypto.randomUUID()` is undefined outside a secure context. The counter is not the best
* fallback, but given how widely available the crypto API is, it should be fine.
*
* Ideally `VanillaTooltip` would take an optional id and generate its own when one is not
* supplied, rather than leaving this to every caller.
*/
var nextTooltipId = function () {
var count = 0;
return function () {
return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? "".concat(TOOLTIP_ID_PREFIX, "-").concat(crypto.randomUUID()) : "".concat(TOOLTIP_ID_PREFIX, "-").concat(count += 1);
};
}();
/**
* Spec for an unsupported block node.
*
* The outer wrapper carries the shared CSS class so selection and cursor styles apply.
* The inner element's `unsupported` class is retained for parity with the React
* implementation, which products and tests may still select on.
*/
function getBlockSpec(label) {
return ['div', {
class: _unsupportedContent2.UnsupportedSharedCssClassName.BLOCK_CONTAINER
}, ['div', {
class: "unsupported ".concat(VanillaUnsupportedCssClassName.BLOCK_INNER)
}, ['span', {}, label], (0, _getUnsupportedContentIconSpec.getUnsupportedContentIconSpec)(VanillaUnsupportedCssClassName.ICON)]];
}
/**
* Creates the DOM structure for an unsupported content node view.
*
* Matches the React version's two-level structure:
* outer wrapper (gets the shared CSS class — unsupportedBlockView-content-wrap)
* └── inner div (gets visual styles from EditorContentContainer CSS)
* ├── text label (span)
* └── icon (span, tooltip trigger)
*
* Visual styles (background, border, flex) are applied via EditorContentContainer CSS on
* `.unsupportedBlockView-content-wrap > div`, matching how the existing selection/cursor
* styles already work.
*/
function createUnsupportedDOM(node, intl) {
var _domElement$querySele;
var label = (0, _getUnsupportedContent.getUnsupportedContent)(_unsupportedContent.unsupportedContentMessages.unsupportedBlockContent, "".concat(_unsupportedContent.unsupportedContentMessages.unsupportedBlockContent.defaultMessage, ":"), node, intl);
var _DOMSerializer$render = _model.DOMSerializer.renderSpec(document, getBlockSpec(label)),
dom = _DOMSerializer$render.dom;
var domElement = dom instanceof HTMLElement ? dom : undefined;
var icon = (_domElement$querySele = domElement === null || domElement === void 0 ? void 0 : domElement.querySelector(".".concat(VanillaUnsupportedCssClassName.ICON))) !== null && _domElement$querySele !== void 0 ? _domElement$querySele : null;
return {
dom: dom,
icon: icon
};
}
var UnsupportedBlockNodeView = exports.UnsupportedBlockNodeView = /*#__PURE__*/function () {
function UnsupportedBlockNodeView(node, intl, dispatchAnalyticsEvent) {
var _this = this;
(0, _classCallCheck2.default)(this, UnsupportedBlockNodeView);
(0, _defineProperty2.default)(this, "tooltip", null);
(0, _defineProperty2.default)(this, "unbindMouseenter", null);
this.node = node;
var _createUnsupportedDOM = createUnsupportedDOM(node, intl),
dom = _createUnsupportedDOM.dom,
icon = _createUnsupportedDOM.icon;
this.dom = dom;
if (!icon) {
return;
}
// Lazy-init: create the VanillaTooltip on first mouseenter.
// Inline styles are required because the Popover API promotes the tooltip to the
// browser's top layer, where ancestor CSS selectors cannot reach it.
var initTooltip = function initTooltip(event) {
var _this$unbindMouseente;
// Unbind before re-dispatching below, so this listener is not re-entered by the
// synthetic event. Matches `EmojiNodeView`.
(_this$unbindMouseente = _this.unbindMouseenter) === null || _this$unbindMouseente === void 0 || _this$unbindMouseente.call(_this);
_this.unbindMouseenter = null;
var tooltipText = intl.formatMessage(_unsupportedContent.unsupportedContentMessages.unsupportedContentTooltip);
_this.tooltip = new _vanillaTooltip.VanillaTooltip(icon, tooltipText, nextTooltipId(), TOOLTIP_CLASS_NAMES, 300,
// Styling comes from the classes above, defined in the EditorContentContainer stylesheets.
undefined,
// Fired once the tooltip is actually visible, rather than on hover intent, so the
// analytics match the React implementation's `<Tooltip onShow>`.
dispatchAnalyticsEvent ? function () {
var _this$node;
return (0, _trackUnsupportedContent.trackUnsupportedContentTooltipDisplayedFor)(dispatchAnalyticsEvent, _enums.ACTION_SUBJECT_ID.ON_UNSUPPORTED_BLOCK, (_this$node = _this.node) === null || _this$node === void 0 || (_this$node = _this$node.attrs) === null || _this$node === void 0 || (_this$node = _this$node.originalValue) === null || _this$node === void 0 ? void 0 : _this$node.type);
} : undefined);
// VanillaTooltip binds its own `mouseenter` listener inside its constructor.
// Listeners added while an event is being dispatched are not invoked for that
// same event, so without re-dispatching, the tooltip would stay hidden until
// the user hovered away and back. Matches `EmojiNodeView`, which lazy-inits
// the same way.
icon.dispatchEvent(new Event(event.type));
};
this.unbindMouseenter = (0, _bindEventListener.bind)(icon, {
type: 'mouseenter',
listener: initTooltip,
options: {
once: true
}
});
}
return (0, _createClass2.default)(UnsupportedBlockNodeView, [{
key: "update",
value: function update(node) {
if (node.type !== this.node.type) {
return false;
}
// originalValue attrs are immutable — label never changes.
// Track node reference so analytics can read the latest attrs.
this.node = node;
return true;
}
}, {
key: "destroy",
value: function destroy() {
var _this$tooltip, _this$unbindMouseente2;
(_this$tooltip = this.tooltip) === null || _this$tooltip === void 0 || _this$tooltip.destroy();
this.tooltip = null;
(_this$unbindMouseente2 = this.unbindMouseenter) === null || _this$unbindMouseente2 === void 0 || _this$unbindMouseente2.call(this);
this.unbindMouseenter = null;
}
}]);
}();