UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

324 lines (323 loc) 13.6 kB
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { withAnalytics } from '@atlaskit/editor-common/editor-analytics'; import { currentMediaNodeWithPos } from '@atlaskit/editor-common/media-single'; import { Fragment } from '@atlaskit/editor-prosemirror/model'; import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { findParentNodeClosestToPos, isNodeSelection, removeSelectedNode, safeInsert } from '@atlaskit/editor-prosemirror/utils'; import { findChangeFromLocation, getChangeMediaAnalytics, getMediaInputResizeAnalyticsEvent } from '../../pm-plugins/utils/analytics'; import { currentMediaInlineNodeWithPos } from '../../pm-plugins/utils/current-media-node'; import { isSelectionMediaSingleNode } from '../../pm-plugins/utils/media-common'; import { changeFromMediaInlineToMediaSingleNode } from '../../pm-plugins/utils/media-single'; import { getSelectedMediaSingle, removeMediaGroupNode } from './utils'; export const DEFAULT_BORDER_COLOR = '#091e4224'; export const DEFAULT_BORDER_SIZE = 2; export const getNodeType = state => { const { mediaSingle, mediaInline } = state.schema.nodes; return isSelectionMediaSingleNode(state) ? mediaSingle.name : mediaInline.name; }; export const changeInlineToMediaCard = (editorAnalyticsAPI, forceFocusSelector) => (state, dispatch) => { var _findParentNodeCloses, _parent$content$first, _parent$content$lastC, _parent$content$lastC2, _parent$content$lastC3; const { media, mediaInline, mediaGroup, paragraph, heading } = state.schema.nodes; const selectedNode = state.selection instanceof NodeSelection && state.selection.node.type === mediaInline && state.selection.node; if (!selectedNode) { return false; } const { id, type, collection } = selectedNode.attrs; const mediaNode = media.createChecked({ id, type, collection }); const group = mediaGroup.createChecked({}, mediaNode); const parent = (_findParentNodeCloses = findParentNodeClosestToPos(state.selection.$from, node => { return node.type === paragraph || node.type === heading; })) === null || _findParentNodeCloses === void 0 ? void 0 : _findParentNodeCloses.node; let tr = state.tr; let insertPos; if (!!parent && parent.content.size === 2 && ((_parent$content$first = parent.content.firstChild) === null || _parent$content$first === void 0 ? void 0 : _parent$content$first.type.name) === 'mediaInline' && ((_parent$content$lastC = parent.content.lastChild) === null || _parent$content$lastC === void 0 ? void 0 : _parent$content$lastC.type.name) === 'text' && ((_parent$content$lastC2 = parent.content.lastChild) === null || _parent$content$lastC2 === void 0 ? void 0 : (_parent$content$lastC3 = _parent$content$lastC2.text) === null || _parent$content$lastC3 === void 0 ? void 0 : _parent$content$lastC3.trim()) === '') { /// Empty paragraph or empty heading /// Drop the corresponding card on the current line insertPos = state.tr.doc.resolve(state.selection.from).start() - 1; if (insertPos < 0) { return false; } tr = tr.delete(insertPos, insertPos + parent.nodeSize); tr = safeInsert(group, insertPos, false)(tr); } else { /// Non-empty paragraph, non-empty heading, or other nodes (e.g., action, list) /// Drop the corresponding card underneath the current line insertPos = state.tr.doc.resolve(state.selection.from).end(); tr = removeSelectedNode(tr); tr = safeInsert(group, insertPos, false)(tr); } if (dispatch) { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: ACTION.CHANGED_TYPE, actionSubject: ACTION_SUBJECT.MEDIA, eventType: EVENT_TYPE.TRACK, attributes: { newType: ACTION_SUBJECT_ID.MEDIA_GROUP, previousType: ACTION_SUBJECT_ID.MEDIA_INLINE } })(tr); const $endOfNode = tr.doc.resolve(insertPos + 1); const newSelection = new NodeSelection($endOfNode); tr.setSelection(newSelection); forceFocusSelector === null || forceFocusSelector === void 0 ? void 0 : forceFocusSelector(`.thumbnail-appearance`)(tr); dispatch(tr); } return true; }; export const changeMediaCardToInline = (editorAnalyticsAPI, forceFocusSelector) => (state, dispatch) => { const { media, mediaInline, paragraph } = state.schema.nodes; const selectedNode = state.selection instanceof NodeSelection && state.selection.node; // @ts-ignore - [unblock prosemirror bump] redundant check comparing boolean to media if (!selectedNode || !selectedNode.type === media) { return false; } const mediaInlineNode = mediaInline.create({ id: selectedNode.attrs.id, collection: selectedNode.attrs.collection }); const space = state.schema.text(' '); const content = Fragment.from([mediaInlineNode, space]); const node = paragraph.createChecked({}, content); const nodePos = state.tr.doc.resolve(state.selection.from).start(); let tr = removeMediaGroupNode(state); // Minus 1 to insert the node before the media group tr = safeInsert(node, nodePos - 1, false)(tr); if (dispatch) { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: ACTION.CHANGED_TYPE, actionSubject: ACTION_SUBJECT.MEDIA, eventType: EVENT_TYPE.TRACK, attributes: { newType: ACTION_SUBJECT_ID.MEDIA_INLINE, previousType: ACTION_SUBJECT_ID.MEDIA_GROUP } })(tr); const newSelection = NodeSelection.create(tr.doc, nodePos); tr.setSelection(newSelection); forceFocusSelector === null || forceFocusSelector === void 0 ? void 0 : forceFocusSelector(`.inline-appearance`)(tr); dispatch(tr); } return true; }; export const changeMediaInlineToMediaSingle = (editorAnalyticsAPI, widthPluginState, allowPixelResizing) => (state, dispatch, view) => { const { mediaInline } = state.schema.nodes; const selectedNode = state.selection instanceof NodeSelection && state.selection.node.type === mediaInline && state.selection.node; if (!selectedNode) { return false; } if (view) { return changeFromMediaInlineToMediaSingleNode(view, selectedNode, widthPluginState, editorAnalyticsAPI, allowPixelResizing); } return true; }; export const removeInlineCardWithAnalytics = editorAnalyticsAPI => { return withAnalytics(editorAnalyticsAPI, { action: ACTION.DELETED, actionSubject: ACTION_SUBJECT.MEDIA_INLINE, attributes: { inputMethod: INPUT_METHOD.FLOATING_TB }, eventType: EVENT_TYPE.TRACK })(removeInlineCard); }; export const removeInlineCard = (state, dispatch) => { if (isNodeSelection(state.selection)) { if (dispatch) { dispatch(removeSelectedNode(state.tr)); } return true; } return false; }; export const toggleBorderMark = editorAnalyticsAPI => (state, dispatch) => { const nodeWithPos = currentMediaNodeWithPos(state) || currentMediaInlineNodeWithPos(state); if (!nodeWithPos) { return false; } const { node, pos } = nodeWithPos; const borderMark = node.marks.find(m => m.type.name === 'border'); const marks = node.marks.filter(m => m.type.name !== 'border').concat(borderMark ? [] : state.schema.marks.border.create({ color: DEFAULT_BORDER_COLOR, size: DEFAULT_BORDER_SIZE })); const tr = state.tr.setNodeMarkup(pos, node.type, node.attrs, marks); tr.setMeta('scrollIntoView', false); if (state.selection instanceof NodeSelection) { if (state.selection.$anchor.pos === state.selection.from) { tr.setSelection(NodeSelection.create(tr.doc, state.selection.from)); } } if (dispatch) { const type = getNodeType(state); if (borderMark !== null && borderMark !== void 0 && borderMark.attrs) { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: ACTION.DELETED, actionSubject: ACTION_SUBJECT.MEDIA, actionSubjectId: ACTION_SUBJECT_ID.BORDER, eventType: EVENT_TYPE.TRACK, attributes: { type, previousColor: borderMark.attrs.color, previousSize: borderMark.attrs.size, mediaType: node.attrs.type } })(tr); } else { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: ACTION.ADDED, actionSubject: ACTION_SUBJECT.MEDIA, actionSubjectId: ACTION_SUBJECT_ID.BORDER, eventType: EVENT_TYPE.TRACK, attributes: { type, color: DEFAULT_BORDER_COLOR, size: DEFAULT_BORDER_SIZE, mediaType: node.attrs.type } })(tr); } dispatch(tr); } return true; }; export const setBorderMark = editorAnalyticsAPI => attrs => (state, dispatch) => { var _node$marks$find, _ref, _attrs$color, _ref2, _attrs$size; const nodeWithPos = currentMediaNodeWithPos(state) || currentMediaInlineNodeWithPos(state); if (!nodeWithPos) { return false; } const { node, pos } = nodeWithPos; const borderMark = (_node$marks$find = node.marks.find(m => m.type.name === 'border')) === null || _node$marks$find === void 0 ? void 0 : _node$marks$find.attrs; const color = (_ref = (_attrs$color = attrs.color) !== null && _attrs$color !== void 0 ? _attrs$color : borderMark === null || borderMark === void 0 ? void 0 : borderMark.color) !== null && _ref !== void 0 ? _ref : DEFAULT_BORDER_COLOR; const size = (_ref2 = (_attrs$size = attrs.size) !== null && _attrs$size !== void 0 ? _attrs$size : borderMark === null || borderMark === void 0 ? void 0 : borderMark.size) !== null && _ref2 !== void 0 ? _ref2 : DEFAULT_BORDER_SIZE; const marks = node.marks.filter(m => m.type.name !== 'border').concat(state.schema.marks.border.create({ color, size })); const tr = state.tr.setNodeMarkup(pos, node.type, node.attrs, marks); tr.setMeta('scrollIntoView', false); if (state.selection instanceof NodeSelection) { if (state.selection.$anchor.pos === state.selection.from) { tr.setSelection(NodeSelection.create(tr.doc, state.selection.from)); } } if (dispatch) { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({ action: ACTION.UPDATED, actionSubject: ACTION_SUBJECT.MEDIA, actionSubjectId: ACTION_SUBJECT_ID.BORDER, eventType: EVENT_TYPE.TRACK, attributes: { type: getNodeType(state), mediaType: node.attrs.type, previousColor: borderMark === null || borderMark === void 0 ? void 0 : borderMark.color, previousSize: borderMark === null || borderMark === void 0 ? void 0 : borderMark.size, newColor: color, newSize: size } })(tr); dispatch(tr); } return true; }; export const updateMediaSingleWidthTr = (editorAnalyticsAPI, state, width, validation, inputMethod, layout) => { const selectedMediaSingleNode = getSelectedMediaSingle(state); if (!selectedMediaSingleNode) { return null; } const tr = state.tr.setNodeMarkup(selectedMediaSingleNode.pos, undefined, { ...selectedMediaSingleNode.node.attrs, width, widthType: 'pixel', layout }); tr.setMeta('scrollIntoView', false); tr.setSelection(NodeSelection.create(tr.doc, selectedMediaSingleNode.pos)); const $pos = state.doc.resolve(selectedMediaSingleNode.pos); const parentNodeType = $pos ? $pos.parent.type.name : undefined; const payload = getMediaInputResizeAnalyticsEvent('mediaSingle', { width, layout, validation, inputMethod, parentNode: parentNodeType }); if (payload) { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(payload)(tr); } return tr; }; export const updateMediaSingleWidth = editorAnalyticsAPI => (width, validation, inputMethod, layout) => (state, dispatch) => { const tr = updateMediaSingleWidthTr(editorAnalyticsAPI, state, width, validation, inputMethod, layout); if (!tr) { return false; } if (dispatch) { dispatch(tr); } return true; }; export const changeMediaSingleToMediaInline = editorAnalyticsAPI => (state, dispatch) => { const selectedNodeWithPos = getSelectedMediaSingle(state); const { mediaInline, paragraph } = state.schema.nodes; if (!selectedNodeWithPos) { return false; } const mediaSingleNode = selectedNodeWithPos.node; const mediaNode = mediaSingleNode.firstChild; if (!mediaNode) { return false; } const mediaInlineNode = mediaInline.create({ ...mediaNode.attrs, type: 'image' }, null, mediaNode.marks); const space = state.schema.text(' '); const content = Fragment.from([mediaInlineNode, space]); const node = paragraph.createChecked({}, content); const { from } = state.selection; let tr = state.tr; tr = removeSelectedNode(tr); tr = safeInsert(node, from, false)(tr); // 3 accounts for paragraph 1 + mediaInline size 1 + space 1 tr.setSelection(TextSelection.create(tr.doc, from + 3)); if (dispatch) { editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(getChangeMediaAnalytics(ACTION_SUBJECT_ID.MEDIA_SINGLE, ACTION_SUBJECT_ID.MEDIA_INLINE, findChangeFromLocation(state.selection)))(tr); dispatch(tr); } return true; };