@atlaskit/editor-plugin-card
Version:
Card plugin for @atlaskit/editor-core
134 lines (129 loc) • 4.42 kB
JavaScript
/**
* Bounds the per-editor work, since every tracked link is mapped through every transaction. The
* first pasted links win. Sized well above a typical paste; overflow is counted, not silent.
*/
export const MAX_PASTED_INLINE_LINKS = 100;
const isSameLink = (a, b) => a.pos === b.pos && a.url === b.url;
/**
* Map candidates through a transaction. Anything that was deleted, replaced by another node or
* given a different URL stops being a candidate. Returns the same array when nothing changed.
*/
export const mapPastedInlineLinks = (links, tr) => {
if (tr.getMeta('replaceDocument')) {
return links.length ? [] : links;
}
if (!tr.docChanged || !links.length) {
return links;
}
let changed = false;
const mapped = [];
for (const link of links) {
const result = tr.mapping.mapResult(link.pos, 1);
const node = result.deleted ? null : tr.doc.nodeAt(result.pos);
if ((node === null || node === void 0 ? void 0 : node.type.name) !== 'inlineCard' || node.attrs.url !== link.url) {
changed = true;
continue;
}
if (result.pos !== link.pos) {
changed = true;
mapped.push({
pos: result.pos,
url: link.url
});
} else {
mapped.push(link);
}
}
return changed ? mapped : links;
};
/** Append new candidates, skipping duplicates and counting any dropped by the cap. */
export const addPastedInlineLinksWithOverflow = (links, additions) => {
let next = links;
let dropped = 0;
for (const addition of additions) {
if (next.some(link => isSameLink(link, addition))) {
continue;
}
if (next.length >= MAX_PASTED_INLINE_LINKS) {
dropped += 1;
} else {
next = [...next, addition];
}
}
return {
dropped,
links: next
};
};
/** Append new candidates, skipping duplicates and anything beyond the cap. */
export const addPastedInlineLinks = (links, additions) => addPastedInlineLinksWithOverflow(links, additions).links;
export const removePastedInlineLink = (links, target) => {
const next = links.filter(link => !isSameLink(link, target));
return next.length === links.length ? links : next;
};
/** Inline cards inserted by a paste transaction (copy and paste of existing cards). */
export const getPastedInlineCards = tr => {
if (!tr.docChanged || tr.getMeta('uiEvent') !== 'paste' || tr.getMeta('isRemote')) {
return [];
}
const found = [];
tr.mapping.maps.forEach((stepMap, index) => {
const later = tr.mapping.slice(index + 1);
stepMap.forEach((_oldStart, _oldEnd, newStart, newEnd) => {
tr.doc.nodesBetween(later.map(newStart, -1), later.map(newEnd, 1), (node, pos) => {
if (node.type.name === 'inlineCard' && typeof node.attrs.url === 'string') {
found.push({
pos,
url: node.attrs.url
});
}
});
});
});
return found;
};
/** Inline cards created from clipboard URLs that just resolved in this transaction. */
export const getResolvedPastedInlineCards = (tr, requests, resolvedUrl, clipboardSource) => {
if (tr.getMeta('isRemote')) {
return [];
}
const found = [];
for (const request of requests) {
if (request.url !== resolvedUrl || request.appearance !== 'inline' || request.source !== clipboardSource) {
continue;
}
const pos = tr.mapping.map(request.pos, -1);
if (pos < 0 || pos >= tr.doc.content.size) {
continue;
}
const node = tr.doc.nodeAt(pos);
if ((node === null || node === void 0 ? void 0 : node.type.name) === 'inlineCard' && node.attrs.url === request.url) {
found.push({
pos,
url: request.url
});
}
}
return found;
};
/** Add candidates unless tracking is off or a link has already been displayed. */
export const withPastedInlineLinks = (state, additions) => {
var _state$pastedInlineLi;
if (!state.pastedInlineLinks || state.pastedInlineLinkShown || !additions.length) {
return state;
}
const {
dropped,
links: next
} = addPastedInlineLinksWithOverflow(state.pastedInlineLinks, additions);
if (next === state.pastedInlineLinks && !dropped) {
return state;
}
return {
...state,
pastedInlineLinks: next,
...(dropped ? {
pastedInlineLinksDropped: ((_state$pastedInlineLi = state.pastedInlineLinksDropped) !== null && _state$pastedInlineLi !== void 0 ? _state$pastedInlineLi : 0) + dropped
} : {})
};
};