@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
370 lines (355 loc) • 14.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.insertMediaInlineNode = exports.insertMediaGroupNode = exports.getPosInList = exports.getFailToInsertAnalytics = exports.canInsertMediaInline = void 0;
var _analytics = require("@atlaskit/editor-common/analytics");
var _mediaInline = require("@atlaskit/editor-common/media-inline");
var _selection = require("@atlaskit/editor-common/selection");
var _utils = require("@atlaskit/editor-common/utils");
var _model = require("@atlaskit/editor-prosemirror/model");
var _transform = require("@atlaskit/editor-prosemirror/transform");
var _utils2 = require("@atlaskit/editor-prosemirror/utils");
var _isType = require("./is-type");
var _mediaCommon = require("./media-common");
var _mediaInline2 = require("./media-inline");
var canInsertMediaInline = exports.canInsertMediaInline = function canInsertMediaInline(state) {
var node = state.schema.nodes.mediaInline.create({});
return (0, _utils2.canInsert)(state.selection.$to, _model.Fragment.from(node));
};
var getInsertMediaGroupAnalytics = function getInsertMediaGroupAnalytics(mediaState, inputMethod, insertMediaVia) {
var media = '';
if (mediaState.length === 1) {
media = mediaState[0].fileMimeType || 'unknown';
} else if (mediaState.length > 1) {
media = 'multiple';
}
return {
action: _analytics.ACTION.INSERTED,
actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
actionSubjectId: _analytics.ACTION_SUBJECT_ID.MEDIA,
attributes: {
type: _analytics.ACTION_SUBJECT_ID.MEDIA_GROUP,
inputMethod: inputMethod,
fileExtension: media,
insertMediaVia: insertMediaVia
},
eventType: _analytics.EVENT_TYPE.TRACK
};
};
var getInsertMediaInlineAnalytics = function getInsertMediaInlineAnalytics(mediaState, inputMethod, insertMediaVia) {
var media = mediaState.fileMimeType || 'unknown';
return {
action: _analytics.ACTION.INSERTED,
actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
actionSubjectId: _analytics.ACTION_SUBJECT_ID.MEDIA,
attributes: {
type: _analytics.ACTION_SUBJECT_ID.MEDIA_INLINE,
inputMethod: inputMethod,
fileExtension: media,
insertMediaVia: insertMediaVia
},
eventType: _analytics.EVENT_TYPE.TRACK
};
};
var getFailToInsertAnalytics = exports.getFailToInsertAnalytics = function getFailToInsertAnalytics(mediaState, actionSubjectId, inputMethod, insertMediaVia, reason) {
var media = mediaState.fileMimeType || 'unknown';
return {
action: _analytics.ACTION.FAILED_TO_INSERT,
actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
actionSubjectId: actionSubjectId,
attributes: {
inputMethod: inputMethod,
fileExtension: media,
insertMediaVia: insertMediaVia,
reason: reason
},
eventType: _analytics.EVENT_TYPE.OPERATIONAL
};
};
/**
* Check if current editor selections is a media group or not.
* @param state Editor state
*/
function isSelectionMediaGroup(state) {
var schema = state.schema;
var selectionParent = state.selection.$anchor.node();
return selectionParent && selectionParent.type === schema.nodes.mediaGroup;
}
/**
* Insert a paragraph after if reach the end of doc
* and there is no media group in the front or selection is a non media block node
* @param node Node at insertion point
* @param state Editor state
*/
function shouldAppendParagraph(state, node) {
var media = state.schema.nodes.media;
var wasMediaNode = node && node.type === media;
return ((0, _utils.insideTableCell)(state) || (0, _utils.isInListItem)(state) || (0, _utils.isInLayoutColumn)(state) || (0, _selection.atTheEndOfDoc)(state) && (!(0, _mediaCommon.posOfPrecedingMediaGroup)(state) || (0, _mediaCommon.isSelectionNonMediaBlockNode)(state))) && !wasMediaNode;
}
/**
* Check if node of type has been inserted successfully
*/
var hasInsertedNodeOfType = function hasInsertedNodeOfType(tr, nodeType) {
var _tr$doc$nodeAt;
var insertPos = -1;
tr.steps.forEach(function (step) {
if (step instanceof _transform.ReplaceStep) {
step.slice.content.forEach(function (node) {
if (node.type.name === nodeType) {
insertPos = step.from;
}
});
}
});
if (insertPos === -1 || ((_tr$doc$nodeAt = tr.doc.nodeAt(insertPos)) === null || _tr$doc$nodeAt === void 0 ? void 0 : _tr$doc$nodeAt.type.name) !== nodeType) {
return false;
}
return true;
};
/**
* Create a new media inline to insert the new media.
* @param view Editor view
* @param mediaState Media file to be added to the editor
* @param allowInlineImages Configuration for allowing adding of inline images
* @param collection Collection for the media to be added
*/
var insertMediaInlineNode = exports.insertMediaInlineNode = function insertMediaInlineNode(editorAnalyticsAPI) {
return function (view, mediaState, collection, allowInlineImages, inputMethod, insertMediaVia) {
var state = view.state,
dispatch = view.dispatch;
var schema = state.schema,
tr = state.tr;
var mediaInline = schema.nodes.mediaInline;
// Do nothing if no media found
if (!mediaInline || !mediaState || collection === undefined) {
return false;
}
var id = mediaState.id,
dimensions = mediaState.dimensions,
_mediaState$scaleFact = mediaState.scaleFactor,
scaleFactor = _mediaState$scaleFact === void 0 ? 1 : _mediaState$scaleFact,
fileName = mediaState.fileName;
var mediaInlineAttrs = {
id: id,
collection: collection
};
if (allowInlineImages && (0, _isType.isImage)(mediaState.fileMimeType)) {
var _ref = dimensions || {
width: undefined,
height: undefined
},
width = _ref.width,
height = _ref.height;
var scaledWidth = width ? Math.round(width / scaleFactor) : _mediaInline.DEFAULT_IMAGE_WIDTH;
var scaledHeight = height ? Math.round(height / scaleFactor) : _mediaInline.DEFAULT_IMAGE_HEIGHT;
mediaInlineAttrs.width = scaledWidth;
mediaInlineAttrs.height = scaledHeight;
mediaInlineAttrs.type = 'image';
mediaInlineAttrs.alt = fileName;
}
var mediaInlineNode = mediaInline.create(mediaInlineAttrs);
var space = state.schema.text(' ');
var pos = state.selection.$to.pos;
// If the selection is inside an empty list item or panel set pos inside paragraph
if ((0, _mediaInline2.isInSupportedInlineImageParent)(state) && (0, _mediaCommon.isInsidePotentialEmptyParagraph)(state)) {
pos = pos + 1;
}
var content = _model.Fragment.from([mediaInlineNode, space]);
// Delete the selection if a selection is made
var deleteRange = findDeleteRange(state);
var payload;
try {
if (!deleteRange) {
tr.insert(pos, content);
} else {
tr.insert(pos, content).deleteRange(deleteRange.start, deleteRange.end);
}
if (hasInsertedNodeOfType(tr, 'mediaInline')) {
payload = getInsertMediaInlineAnalytics(mediaState, inputMethod, insertMediaVia);
} else {
payload = getFailToInsertAnalytics(mediaState, _analytics.ACTION_SUBJECT_ID.MEDIA_INLINE, inputMethod, insertMediaVia);
}
} catch (error) {
payload = getFailToInsertAnalytics(mediaState, _analytics.ACTION_SUBJECT_ID.MEDIA_INLINE, inputMethod, insertMediaVia,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error.toString());
}
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(payload)(tr);
dispatch(tr);
return true;
};
};
/**
* Insert a media into an existing media group
* or create a new media group to insert the new media.
* @param view Editor view
* @param mediaStates Media files to be added to the editor
* @param collection Collection for the media to be added
*/
var insertMediaGroupNode = exports.insertMediaGroupNode = function insertMediaGroupNode(editorAnalyticsAPI) {
return function (view, mediaStates, collection, inputMethod, insertMediaVia) {
var state = view.state,
dispatch = view.dispatch;
var tr = state.tr,
schema = state.schema;
var _schema$nodes = schema.nodes,
media = _schema$nodes.media,
paragraph = _schema$nodes.paragraph;
// Do nothing if no media found
if (!media || !mediaStates.length) {
return;
}
var mediaNodes = createMediaFileNodes(mediaStates, collection, media);
var mediaInsertPos = findMediaInsertPos(state);
var resolvedInsertPos = tr.doc.resolve(mediaInsertPos);
var parent = resolvedInsertPos.parent;
var nodeAtInsertionPoint = tr.doc.nodeAt(mediaInsertPos);
var shouldSplit = !isSelectionMediaGroup(state) && (0, _utils.isSupportedInParent)(state, _model.Fragment.from(state.schema.nodes.mediaGroup.createChecked({}, mediaNodes)));
var withParagraph = shouldAppendParagraph(state, nodeAtInsertionPoint);
var content = parent.type === schema.nodes.mediaGroup ? mediaNodes // If parent is a mediaGroup do not wrap items again.
: [schema.nodes.mediaGroup.createChecked({}, mediaNodes)];
if (shouldSplit) {
content = withParagraph ? content.concat(paragraph.create()) : content;
// delete the selection or empty paragraph
var deleteRange = findDeleteRange(state);
if (!deleteRange) {
tr.insert(mediaInsertPos, content);
} else if (mediaInsertPos <= deleteRange.start) {
tr.deleteRange(deleteRange.start, deleteRange.end).insert(mediaInsertPos, content);
} else {
tr.insert(mediaInsertPos, content).deleteRange(deleteRange.start, deleteRange.end);
}
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(getInsertMediaGroupAnalytics(mediaStates, inputMethod, insertMediaVia))(tr);
dispatch(tr);
setSelectionAfterMediaInsertion(view);
return;
}
// Don't append new paragraph when adding media to a existing mediaGroup
if (withParagraph && parent.type !== schema.nodes.mediaGroup) {
content.push(paragraph.create());
}
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 || editorAnalyticsAPI.attachAnalyticsEvent(getInsertMediaGroupAnalytics(mediaStates, inputMethod, insertMediaVia))(tr);
dispatch((0, _utils2.safeInsert)(_model.Fragment.fromArray(content), mediaInsertPos)(tr));
};
};
var createMediaFileNodes = function createMediaFileNodes(mediaStates, collection, media) {
var nodes = mediaStates.map(function (mediaState) {
var id = mediaState.id;
var node = media.create({
id: id,
type: 'file',
collection: collection
});
(0, _mediaCommon.copyOptionalAttrsFromMediaState)(mediaState, node);
return node;
});
return nodes;
};
/**
* Find root list node if exist from current selection
* @param state Editor state
*/
var findRootListNode = function findRootListNode(state) {
var _state$schema$nodes = state.schema.nodes,
bulletList = _state$schema$nodes.bulletList,
orderedList = _state$schema$nodes.orderedList;
return (0, _utils.findFarthestParentNode)(function (node) {
return node.type === bulletList || node.type === orderedList;
})(state.selection.$from);
};
/**
* Return position of media to be inserted, if it is inside a list
* @param content Content to be inserted
* @param state Editor State
*/
var getPosInList = exports.getPosInList = function getPosInList(state) {
var _state$schema$nodes2 = state.schema.nodes,
mediaGroup = _state$schema$nodes2.mediaGroup,
listItem = _state$schema$nodes2.listItem;
// 1. Check if I am inside a list.
if ((0, _utils2.hasParentNode)(function (node) {
return node.type === listItem;
})(state.selection)) {
// 2. Get end position of root list
var rootListNode = findRootListNode(state);
if (rootListNode) {
var pos = rootListNode.pos + rootListNode.node.nodeSize;
// 3. Fint the first location inside the media group
var nextNode = state.doc.nodeAt(pos);
if (nextNode && nextNode.type === mediaGroup) {
return pos + 1;
}
return pos;
}
}
return;
};
/**
* Find insertion point,
* If it is in a List it will return position to the next block,
* If there are any media group close it will return this position
* Otherwise, It will return the respective block given selection.
* @param content Content to be inserted
* @param state Editor state
*/
var findMediaInsertPos = function findMediaInsertPos(state) {
var _state$selection = state.selection,
$from = _state$selection.$from,
$to = _state$selection.$to;
// Check if selection is inside a list.
var posInList = getPosInList(state);
if (posInList) {
// If I have a position in lists, I should return, otherwise I am not inside a list
return posInList;
}
var nearbyMediaGroupPos = (0, _mediaCommon.posOfMediaGroupNearby)(state);
if (nearbyMediaGroupPos && (!(0, _mediaCommon.isSelectionNonMediaBlockNode)(state) || $from.pos < nearbyMediaGroupPos && $to.pos < nearbyMediaGroupPos)) {
return nearbyMediaGroupPos;
}
if ((0, _mediaCommon.isSelectionNonMediaBlockNode)(state)) {
return $to.pos;
}
if ((0, _selection.atTheEndOfBlock)(state)) {
return $to.pos + 1;
}
if ((0, _selection.atTheBeginningOfBlock)(state)) {
return $from.pos - 1;
}
return $to.pos;
};
var findDeleteRange = function findDeleteRange(state) {
var _state$selection2 = state.selection,
$from = _state$selection2.$from,
$to = _state$selection2.$to;
if ((0, _mediaCommon.posOfParentMediaGroup)(state)) {
return;
}
if (!(0, _mediaCommon.isInsidePotentialEmptyParagraph)(state) || (0, _mediaCommon.posOfMediaGroupNearby)(state)) {
return range($from.pos, $to.pos);
}
return range((0, _selection.startPositionOfParent)($from) - 1, (0, _selection.endPositionOfParent)($to));
};
var range = function range(start) {
var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : start;
return {
start: start,
end: end
};
};
var setSelectionAfterMediaInsertion = function setSelectionAfterMediaInsertion(view) {
var state = view.state;
var doc = state.doc;
var mediaPos = (0, _mediaCommon.posOfMediaGroupNearby)(state);
if (!mediaPos) {
return;
}
var $mediaPos = doc.resolve(mediaPos);
var endOfMediaGroup = (0, _selection.endPositionOfParent)($mediaPos);
if (endOfMediaGroup + 1 >= doc.nodeSize - 1) {
// if nothing after the media group, fallback to select the newest uploaded media item
(0, _utils.setNodeSelection)(view, mediaPos);
} else {
(0, _utils.setTextSelection)(view, endOfMediaGroup + 1);
}
};