@atlaskit/editor-plugin-panel
Version:
Panel plugin for @atlaskit/editor-core.
136 lines (131 loc) • 5.71 kB
JavaScript
import React from 'react';
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
import uuid from 'uuid/v4';
import { PanelType } from '@atlaskit/adf-schema';
import { Emoji } from '@atlaskit/editor-common/emoji';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { PanelErrorIcon, PanelInfoIcon, PanelNoteIcon, PanelSuccessIcon, PanelWarningIcon } from '@atlaskit/editor-common/icons';
import { PanelSharedCssClassName } from '@atlaskit/editor-common/panel';
import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
import { akEditorCustomIconSize } from '@atlaskit/editor-shared-styles/consts';
import LightbulbIcon from '@atlaskit/icon/core/lightbulb';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { panelAttrsToDom } from '../pm-plugins/utils/utils';
import { renderPanelIcon } from '../ui/renderPanelIcon';
/* eslint-disable @atlaskit/editor/no-re-export */
// Mapping export
export const panelIcons = {
info: PanelInfoIcon,
success: PanelSuccessIcon,
note: PanelNoteIcon,
tip: LightbulbIcon,
warning: PanelWarningIcon,
error: PanelErrorIcon,
custom: PanelInfoIcon
};
/* eslint-enable @atlaskit/editor/no-re-export */
const selector = states => {
return {
emojiState: states.emojiState
};
};
export const PanelIcon = props => {
const {
allowCustomPanel,
providerFactory,
pluginInjectionApi,
panelAttributes: {
panelType,
panelIcon,
panelIconId,
panelIconText
}
} = props;
const {
emojiState
} = useSharedPluginStateWithSelector(pluginInjectionApi, ['emoji'], selector);
const emojiProvider = emojiState === null || emojiState === void 0 ? void 0 : emojiState.emojiProvider;
if (allowCustomPanel && panelIcon && panelType === PanelType.CUSTOM) {
return /*#__PURE__*/React.createElement(Emoji, {
emojiProvider: emojiProvider,
providers: providerFactory,
shortName: panelIcon,
id: panelIconId,
fallback: panelIconText,
showTooltip: false,
allowTextFallback: false,
fitToHeight: akEditorCustomIconSize
});
}
const Icon = panelIcons[panelType];
return Icon ? /*#__PURE__*/React.createElement(Icon, {
label: `${panelType} panel`
}) : null;
};
class PanelNodeView {
constructor(node, view, getPos, pluginOptions, api, nodeViewPortalProviderAPI, providerFactory) {
this.nodeViewPortalProviderAPI = nodeViewPortalProviderAPI;
this.providerFactory = providerFactory;
this.pluginOptions = pluginOptions;
this.view = view;
this.node = node;
// eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
this.key = uuid();
const {
dom,
contentDOM
} = DOMSerializer.renderSpec(document, panelAttrsToDom(node.attrs, pluginOptions.allowCustomPanel || false));
this.getPos = getPos;
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
this.dom = dom;
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
this.contentDOM = contentDOM;
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
this.icon = this.dom.querySelector(`.${PanelSharedCssClassName.icon}`);
if (!this.icon) {
return;
}
// set contentEditable as false to be able to select the custom panels with keyboard
this.icon.contentEditable = 'false';
const panelAttrs = node.attrs;
// Determine if this is a standard panel type (info, note, success, warning, error)
const isStandardPanel = panelAttrs.panelType && [PanelType.INFO, PanelType.NOTE, PanelType.SUCCESS, PanelType.WARNING, PanelType.ERROR].includes(panelAttrs.panelType);
// For standard panels (info, note, success, warning, error), render icon directly as native DOM
// This avoids Portal rendering delays that cause flickering on SSR and page transitions
if (isStandardPanel && expValEquals('platform_editor_vc90_transition_panel_icon', 'isEnabled', true)) {
renderPanelIcon(panelAttrs.panelType, this.icon);
} else {
this.nodeViewPortalProviderAPI.render(() => /*#__PURE__*/React.createElement(PanelIcon, {
pluginInjectionApi: api,
allowCustomPanel: pluginOptions.allowCustomPanel,
panelAttributes: panelAttrs,
providerFactory: this.providerFactory
}), this.icon, this.key);
}
}
ignoreMutation(mutation) {
// ignore mutation if it caused by the icon.
if (!this.icon) {
return false;
}
const isIcon = mutation.target === this.icon || mutation.target.parentNode === this.icon;
// ignore mutation if it caused by the lazy load emoji inside icon.
const isInsideIcon = this.icon.contains(mutation.target);
return isIcon || isInsideIcon;
}
destroy() {
const panelAttrs = this.node.attrs;
// Determine if this is a standard panel type (info, note, success, warning, error)
const isStandardPanel = panelAttrs.panelType && [PanelType.INFO, PanelType.NOTE, PanelType.SUCCESS, PanelType.WARNING, PanelType.ERROR].includes(panelAttrs.panelType);
// Only remove Portal if it was used (for custom emoji panels)
if (!(isStandardPanel && expValEquals('platform_editor_vc90_transition_panel_icon', 'isEnabled', true))) {
this.nodeViewPortalProviderAPI.remove(this.key);
}
}
}
export const getPanelNodeView = (pluginOptions, api, nodeViewPortalProviderAPI, providerFactory) => (node, view, getPos) => {
return new PanelNodeView(node, view, getPos, pluginOptions, api, nodeViewPortalProviderAPI, providerFactory);
};