@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
180 lines (177 loc) • 7.46 kB
JavaScript
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;