@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
324 lines (323 loc) • 13.6 kB
JavaScript
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;
};