@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
132 lines (129 loc) • 5.02 kB
JavaScript
import { isSafeUrl } from '@atlaskit/adf-schema/url';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { NATIVE_EMBED_EXTENSION_KEY, NATIVE_EMBED_EXTENSION_TYPE } from '@atlaskit/editor-common/extensions';
/**
* A native embed is an `extension` node that `editor-plugin-native-embeds` builds from
* embedCard ADF (via the `embedCardNodeTransformer` registered on the card plugin).
* A 1P link that resolves to an embed appearance — a whiteboard, page, slide or
* database — therefore lands in the document as one of these rather than as an
* `embedCard`, so card node types alone are not enough to describe what was pasted.
*
* `editor-plugin-native-embeds` owns the equivalent predicate, but the card plugin
* cannot depend on it: that would close the cycle
* editor-plugin-card -> editor-plugin-native-embeds -> editor-plugin-card.
*/
export const isNativeEmbedNode = node => {
var _node$attrs, _node$attrs2;
return (node === null || node === void 0 ? void 0 : node.type.name) === 'extension' && ((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.extensionType) === NATIVE_EMBED_EXTENSION_TYPE && typeof ((_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.extensionKey) === 'string' && node.attrs.extensionKey.includes(NATIVE_EMBED_EXTENSION_KEY);
};
/**
* Reads the embedded URL from a native embed node. The URL is stored in
* `parameters.macroParams`, where values may be wrapped (`{ value }`) or bare, and is
* mirrored in `parameters.macroMetadata` so it survives a revert to a card.
*/
export const getNativeEmbedUrl = node => {
var _node$attrs3, _parameters$macroPara, _ref, _parameters$macroMeta;
const parameters = (_node$attrs3 = node.attrs) === null || _node$attrs3 === void 0 ? void 0 : _node$attrs3.parameters;
const macroParamUrl = parameters === null || parameters === void 0 ? void 0 : (_parameters$macroPara = parameters.macroParams) === null || _parameters$macroPara === void 0 ? void 0 : _parameters$macroPara.url;
const url = (_ref = macroParamUrl && typeof macroParamUrl === 'object' && 'value' in macroParamUrl ? macroParamUrl.value : macroParamUrl) !== null && _ref !== void 0 ? _ref : parameters === null || parameters === void 0 ? void 0 : (_parameters$macroMeta = parameters.macroMetadata) === null || _parameters$macroMeta === void 0 ? void 0 : _parameters$macroMeta.url;
return typeof url === 'string' ? url : undefined;
};
const canReplaceNodeWith = (view, pos, nodeType) => {
if (!nodeType) {
return false;
}
try {
const $pos = view.state.doc.resolve(pos);
const index = $pos.index();
return $pos.parent.canReplaceWith(index, index + 1, nodeType);
} catch {
return false;
}
};
/**
* Whether a native embed at `pos` can be replaced by the given appearance. `url` and
* `inline` both become a paragraph, `block` becomes a blockCard.
*/
export const isNativeEmbedAppearanceSupported = ({
editorView,
pos,
appearance
}) => {
const {
paragraph,
inlineCard,
blockCard
} = editorView.state.schema.nodes;
if (appearance === 'block') {
return canReplaceNodeWith(editorView, pos, blockCard);
}
if (appearance === 'inline' && !inlineCard) {
return false;
}
if (appearance === 'url' && !editorView.state.schema.marks.link) {
return false;
}
return canReplaceNodeWith(editorView, pos, paragraph);
};
/**
* Replaces a native embed with a link, an inline card or a block card. Mirrors
* `setNativeEmbedAppearance` in `editor-plugin-native-embeds`, which cannot be reused
* here because of the package cycle described on `isNativeEmbedNode`.
*/
export const changeNativeEmbedAppearance = ({
editorView,
pos,
node,
appearance,
url,
editorAnalyticsApi
}) => {
const {
state,
dispatch
} = editorView;
const {
paragraph,
inlineCard,
blockCard
} = state.schema.nodes;
const {
link
} = state.schema.marks;
if (!isSafeUrl(url)) {
return false;
}
let content;
if (appearance === 'url' && link && paragraph) {
content = paragraph.create(null, state.schema.text(url, [link.create({
href: url
})]));
} else if (appearance === 'inline' && inlineCard && paragraph) {
content = paragraph.create(null, inlineCard.create({
url
}));
} else if (appearance === 'block' && blockCard) {
content = blockCard.create({
url
});
}
if (!content) {
return false;
}
try {
const tr = state.tr.replaceWith(pos, pos + node.nodeSize, content).scrollIntoView();
editorAnalyticsApi === null || editorAnalyticsApi === void 0 ? void 0 : editorAnalyticsApi.attachAnalyticsEvent({
action: ACTION.CHANGED_TYPE,
actionSubject: ACTION_SUBJECT.NATIVE_EMBED,
eventType: EVENT_TYPE.TRACK,
attributes: {
newType: appearance,
previousType: 'nativeEmbed'
}
})(tr);
dispatch(tr);
return true;
} catch {
return false;
}
};