UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

180 lines (177 loc) 7.46 kB
import { NodeSelection } from '@atlaskit/editor-prosemirror/state'; import { hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { DEFAULT_EMBED_CARD_WIDTH } from '@atlaskit/editor-shared-styles'; import EditorAlignImageCenter from '@atlaskit/icon/glyph/editor/align-image-center'; import EditorAlignImageLeft from '@atlaskit/icon/glyph/editor/align-image-left'; import EditorAlignImageRight from '@atlaskit/icon/glyph/editor/align-image-right'; import FullWidthIcon from '@atlaskit/icon/glyph/editor/media-full-width'; import WideIcon from '@atlaskit/icon/glyph/editor/media-wide'; import WrapLeftIcon from '@atlaskit/icon/glyph/editor/media-wrap-left'; import WrapRightIcon from '@atlaskit/icon/glyph/editor/media-wrap-right'; import { getBooleanFF } from '@atlaskit/platform-feature-flags'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '../../analytics'; import { insideTable } from '../../core-utils'; import commonMessages, { mediaAndEmbedToolbarMessages as toolbarMessages } from '../../messages'; import { alignAttributes, isInLayoutColumn, nonWrappedLayouts } from '../../utils'; // Workaround as we don't want to import this package into `editor-common` // We'll get type errors if this gets out of sync with `editor-plugin-width`. export const alignmentIcons = [{ id: 'editor.media.alignLeft', value: 'align-start', icon: EditorAlignImageLeft }, { id: 'editor.media.alignCenter', value: 'center', icon: EditorAlignImageCenter }, { id: 'editor.media.alignRight', value: 'align-end', icon: EditorAlignImageRight }]; export const wrappingIcons = [{ id: 'editor.media.wrapLeft', value: 'wrap-left', icon: WrapLeftIcon }, { id: 'editor.media.wrapRight', value: 'wrap-right', icon: WrapRightIcon }]; const breakoutIcons = [{ value: 'wide', icon: WideIcon }, { value: 'full-width', icon: FullWidthIcon }]; export const layoutToMessages = { 'wrap-left': toolbarMessages.wrapLeft, center: commonMessages.alignImageCenter, 'wrap-right': toolbarMessages.wrapRight, wide: commonMessages.layoutWide, 'full-width': commonMessages.layoutFullWidth, 'align-end': commonMessages.alignImageRight, 'align-start': commonMessages.alignImageLeft }; const getNodeWidth = (node, schema) => { const { embedCard } = schema.nodes; if (node.type === embedCard) { return node.attrs.originalWidth || DEFAULT_EMBED_CARD_WIDTH; } return node.firstChild && node.firstChild.attrs.width || node.attrs.width; }; const makeAlign = (layout, nodeType, widthPluginDependencyApi, analyticsApi) => { return (state, dispatch) => { const { node } = state.selection; const { layout: previousLayoutType } = node.attrs; const { mediaSingle } = state.schema.nodes; if (!dispatch) { return false; } const widthPluginState = widthPluginDependencyApi === null || widthPluginDependencyApi === void 0 ? void 0 : widthPluginDependencyApi.sharedState.currentState(); if (!node || node.type !== nodeType || !widthPluginState) { return false; } const nodeWidth = getNodeWidth(node, state.schema); const newAttrs = getBooleanFF('platform.editor.media.extended-resize-experience') ? // with extended experience, change alignment does not change media single width { ...node.attrs, layout } : alignAttributes(layout, node.attrs, undefined, nodeWidth, widthPluginState.lineLength); const tr = state.tr.setNodeMarkup(state.selection.from, undefined, newAttrs); tr.setMeta('scrollIntoView', false); // when image captions are enabled, the wrong node gets selected after // setNodeMarkup is called tr.setSelection(NodeSelection.create(tr.doc, state.selection.from)); const { doc: { type: { schema: { nodes: { paragraph } } } } } = tr; // see https://product-fabric.atlassian.net/browse/ED-15518 insert a new paragraph when an embedded card is wrapped left or right if (layout.startsWith('wrap') && paragraph && !tr.doc.nodeAt(state.selection.to) && (insideTable(state) || isInLayoutColumn(state))) { const emptyParaghraph = paragraph.createAndFill(); if (emptyParaghraph) { tr.insert(state.selection.to, emptyParaghraph); } } analyticsApi === null || analyticsApi === void 0 ? void 0 : analyticsApi.attachAnalyticsEvent({ eventType: EVENT_TYPE.TRACK, action: ACTION.SELECTED, actionSubject: ACTION_SUBJECT[node.type === mediaSingle ? 'MEDIA_SINGLE' : 'EMBEDS'], actionSubjectId: ACTION_SUBJECT_ID.RICH_MEDIA_LAYOUT, attributes: { previousLayoutType, currentLayoutType: layout } })(tr); dispatch(tr); return true; }; }; const getToolbarLayout = layout => { if (getBooleanFF('platform.editor.media.extended-resize-experience') && nonWrappedLayouts.includes(layout)) { return 'center'; } return layout; }; const mapIconsToToolbarItem = (icons, layout, intl, nodeType, widthPluginDependencyApi, analyticsApi, isChangingLayoutDisabled) => icons.map(toolbarItem => { const { id, value } = toolbarItem; return { id: id, type: 'button', icon: toolbarItem.icon, title: intl.formatMessage(layoutToMessages[value]), selected: getToolbarLayout(layout) === value, onClick: makeAlign(value, nodeType, widthPluginDependencyApi, analyticsApi), ...(isChangingLayoutDisabled && { disabled: value !== 'center' }) }; }); const shouldHideLayoutToolbar = (selection, { nodes }, allowResizingInTables) => { return hasParentNodeOfType([nodes.bodiedExtension, nodes.extensionFrame, nodes.listItem, nodes.expand, nodes.nestedExpand, ...(allowResizingInTables ? [] : [nodes.table])].filter(Boolean))(selection); }; const buildLayoutButtons = (state, intl, nodeType, widthPluginDependencyApi, analyticsApi, allowResizing, allowResizingInTables, allowWrapping = true, allowAlignment = true, isChangingLayoutDisabled) => { const { selection } = state; if (!(selection instanceof NodeSelection) || !selection.node || !nodeType || shouldHideLayoutToolbar(selection, state.schema, allowResizingInTables)) { return []; } const { layout } = selection.node.attrs; const alignmentToolbarItems = allowAlignment ? mapIconsToToolbarItem(alignmentIcons, layout, intl, nodeType, widthPluginDependencyApi, analyticsApi, isChangingLayoutDisabled) : []; const wrappingToolbarItems = allowWrapping ? mapIconsToToolbarItem(wrappingIcons, layout, intl, nodeType, widthPluginDependencyApi, analyticsApi, isChangingLayoutDisabled) : []; const breakOutToolbarItems = !allowResizing ? mapIconsToToolbarItem(breakoutIcons, layout, intl, nodeType, widthPluginDependencyApi, analyticsApi) : []; const items = [...alignmentToolbarItems, ...getSeparatorBetweenAlignmentAndWrapping(allowAlignment, allowWrapping), ...wrappingToolbarItems, ...getSeparatorBeforeBreakoutItems(allowAlignment, allowWrapping, allowResizing), ...breakOutToolbarItems]; return items; }; const getSeparatorBetweenAlignmentAndWrapping = (allowAlignment, allowWrapping) => allowAlignment && allowWrapping ? [{ type: 'separator' }] : []; const getSeparatorBeforeBreakoutItems = (allowAlignment, allowWrapping, allowResizing) => !allowResizing && (allowAlignment || allowWrapping) ? [{ type: 'separator' }] : []; export default buildLayoutButtons;