@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
241 lines (240 loc) • 7.78 kB
JavaScript
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { createToggleBlockMarkOnRange, createToggleInlineMarkOnRange } from '@atlaskit/editor-common/commands';
import { normalizeUrl } from '@atlaskit/editor-common/utils';
import { createMediaLinkingCommand, getMediaLinkingState, mediaLinkingPluginKey } from '../linking';
import { MediaLinkingActionsTypes } from '../linking/actions';
import { getMediaPluginState } from '../main';
import { checkMediaType } from '../utils/check-media-type';
import { currentMediaInlineNode, currentMediaNode } from '../utils/current-media-node';
export const showLinkingToolbar = createMediaLinkingCommand(state => {
const mediaLinkingState = getMediaLinkingState(state);
if (mediaLinkingState && mediaLinkingState.mediaPos !== null) {
const node = state.doc.nodeAt(mediaLinkingState.mediaPos);
if (node) {
return {
type: MediaLinkingActionsTypes.showToolbar
};
}
}
return false;
});
export const showLinkingToolbarWithMediaTypeCheck = (editorState, dispatch, editorView) => {
if (dispatch && editorView) {
const mediaNode = currentMediaNode(editorState);
const mediaInlineNode = currentMediaInlineNode(editorState);
const nodeSelection = editorState.selection;
const currentSelectedNode = nodeSelection.node;
if (!mediaNode && !mediaInlineNode) {
return false;
}
const {
mediaClientConfig
} = getMediaPluginState(editorState);
if (!mediaClientConfig) {
return false;
}
if (mediaNode && currentSelectedNode !== mediaInlineNode) {
checkMediaType(mediaNode, mediaClientConfig).then(mediaType => {
if ((mediaType === 'external' || mediaType === 'image') &&
// We make sure the selection and the node hasn't changed.
currentMediaNode(editorView.state) === mediaNode) {
dispatch(editorView.state.tr.setMeta(mediaLinkingPluginKey, {
type: MediaLinkingActionsTypes.showToolbar
}));
}
});
}
if (mediaInlineNode) {
checkMediaType(mediaInlineNode, mediaClientConfig).then(mediaType => {
if ((mediaType === 'external' || mediaType === 'image') &&
// We make sure the selection and the node hasn't changed.
currentMediaInlineNode(editorView.state) === mediaInlineNode) {
dispatch(editorView.state.tr.setMeta(mediaLinkingPluginKey, {
type: MediaLinkingActionsTypes.showToolbar
}));
}
});
}
}
return true;
};
const hideLinkingToolbarCommand = createMediaLinkingCommand({
type: MediaLinkingActionsTypes.hideToolbar
});
export const hideLinkingToolbar = (state, dispatch, view, focusFloatingToolbar) => {
hideLinkingToolbarCommand(state, dispatch, view);
// restore focus on the editor so keyboard shortcuts aren't lost to the browser
if (view && !focusFloatingToolbar) {
view.focus();
}
};
function getCurrentUrl(state) {
const {
link: linkType
} = state.schema.marks;
const mediaLinkingState = getMediaLinkingState(state);
if (!mediaLinkingState || mediaLinkingState.mediaPos === null) {
return;
}
const $pos = state.doc.resolve(mediaLinkingState.mediaPos);
const node = state.doc.nodeAt($pos.pos);
if (!node) {
return;
}
const hasLink = linkType.isInSet(node.marks);
if (!hasLink) {
return;
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const link = node.marks.find(mark => mark.type === linkType); // Already check exist
const url = link.attrs.href;
return url;
}
function toggleLinkMark(tr, state, {
forceRemove = false,
url
}) {
const mediaLinkingState = getMediaLinkingState(state);
if (!mediaLinkingState || mediaLinkingState.mediaPos === null) {
return tr;
}
const $pos = state.doc.resolve(mediaLinkingState.mediaPos);
const node = state.doc.nodeAt($pos.pos);
if (!node) {
return tr;
}
const linkMark = state.schema.marks.link;
const {
media,
mediaInline
} = state.schema.nodes;
if (node.type !== mediaInline) {
const toggleBlockLinkMark = createToggleBlockMarkOnRange(linkMark, (prevAttrs, node) => {
// Only add mark to media
if (!node || node.type !== media) {
return; //No op
}
if (forceRemove) {
return false;
}
const href = normalizeUrl(url);
if (prevAttrs && prevAttrs.href === href) {
return; //No op
}
if (href.trim() === '') {
return false; // remove
}
return {
...prevAttrs,
href: href
};
}, [media]);
toggleBlockLinkMark($pos.pos, $pos.pos + node.nodeSize, tr, state);
}
const toggleInlineLinkMark = createToggleInlineMarkOnRange(linkMark, (prevAttrs, node) => {
// Only add mark to mediaInline
if (!node || node.type !== mediaInline) {
return; //No op
}
if (forceRemove) {
return false;
}
const href = normalizeUrl(url);
if (prevAttrs && prevAttrs.href === href) {
return; //No op
}
if (href.trim() === '') {
return false; // remove
}
return {
...prevAttrs,
href: href
};
});
toggleInlineLinkMark($pos.pos, $pos.pos + node.nodeSize, tr, state);
return tr;
}
const fireAnalyticForMediaLink = (tr, action, attributes = undefined, editorAnalyticsAPI) => {
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent({
action,
eventType: EVENT_TYPE.TRACK,
actionSubject: ACTION_SUBJECT.MEDIA,
actionSubjectId: ACTION_SUBJECT_ID.LINK,
attributes
})(tr);
return tr;
};
export const unlink = editorAnalyticsAPI => createMediaLinkingCommand({
type: MediaLinkingActionsTypes.unlink
}, (tr, state) => {
const transaction = toggleLinkMark(tr, state, {
forceRemove: true
});
return fireAnalyticForMediaLink(transaction, ACTION.DELETED, {
...getNodeTypeAndMediaTypeAttributes(state)
}, editorAnalyticsAPI);
});
const getAction = (newUrl, state) => {
const currentUrl = getCurrentUrl(state);
if (!currentUrl) {
return ACTION.ADDED;
} else if (newUrl !== currentUrl) {
return ACTION.EDITED;
}
return undefined;
};
const getNodeTypeAndMediaTypeAttributes = state => {
const mediaLinkingState = getMediaLinkingState(state);
const {
allowInlineImages
} = getMediaPluginState(state);
const {
mediaInline,
mediaSingle
} = state.schema.nodes;
if (!mediaLinkingState || mediaLinkingState.mediaPos === null) {
return;
}
const $pos = state.doc.resolve(mediaLinkingState.mediaPos);
const node = state.doc.nodeAt($pos.pos);
if (!node) {
return {};
}
if (allowInlineImages && node.type === mediaInline) {
return {
type: mediaInline.name,
mediaType: node.attrs.type
};
}
return {
type: mediaSingle.name,
mediaType: node.attrs.type
};
};
export const setUrlToMedia = (url, inputMethod, editorAnalyticsAPI) => createMediaLinkingCommand({
type: MediaLinkingActionsTypes.setUrl,
payload: normalizeUrl(url)
}, (tr, state) => {
const action = getAction(url, state);
if (!action) {
return tr;
}
const nodeTypeAndMediaTypeAttrs = getNodeTypeAndMediaTypeAttributes(state);
try {
const toggleLinkMarkResult = toggleLinkMark(tr, state, {
url: url
});
fireAnalyticForMediaLink(tr, action, action === ACTION.ADDED ? {
inputMethod,
...nodeTypeAndMediaTypeAttrs
} : nodeTypeAndMediaTypeAttrs, editorAnalyticsAPI);
return toggleLinkMarkResult;
} catch (e) {
fireAnalyticForMediaLink(tr, ACTION.ERRORED, {
action: action,
...nodeTypeAndMediaTypeAttrs
}, editorAnalyticsAPI);
throw e;
}
});