UNPKG

@atlaskit/editor-plugin-unsupported-content

Version:

Unsupported Content plugin for @atlaskit/editor-core

170 lines (163 loc) • 8.88 kB
"use strict"; 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; } }]); }();