@atlaskit/editor-plugin-paste-options-toolbar
Version:
Paste options toolbar for @atlaskit/editor-core
105 lines (99 loc) • 3.98 kB
JavaScript
/**
* Returns true if the given text node's sole mark is a `link` mark whose href
* matches the node's text content exactly (i.e. the link label IS the URL).
* Or if the text content is a parseable URL.
*/
const isUrlOnlyLinkTextNode = node => {
var _node$marks$0$attrs$h, _node$marks$0$attrs, _node$text;
if (!node.isText) {
return false;
}
if (node.marks.length !== 1 || node.marks[0].type.name !== 'link') {
return false;
}
const href = (_node$marks$0$attrs$h = (_node$marks$0$attrs = node.marks[0].attrs) === null || _node$marks$0$attrs === void 0 ? void 0 : _node$marks$0$attrs.href) !== null && _node$marks$0$attrs$h !== void 0 ? _node$marks$0$attrs$h : '';
// Check if the text content is a URL
const textOfNode = (_node$text = node.text) !== null && _node$text !== void 0 ? _node$text : '';
const parseableUrl = isParseableUrl(textOfNode);
return node.text === href || parseableUrl;
};
/**
* Checks if a given string is parseable as a URL.
*/
const isParseableUrl = text => {
var _URL;
if (typeof ((_URL = URL) === null || _URL === void 0 ? void 0 : _URL.canParse) === 'function') {
return URL.canParse(text);
}
// Fallback for older environments
try {
new URL(text);
return true;
} catch (e) {
return false;
}
};
const isSupportedCard = node => node.type.name === 'inlineCard' || node.type.name === 'blockCard' || node.type.name === 'embedCard';
const transparentSingleChildContainerNodeTypes = new Set(['paragraph', 'listItem', 'bulletList', 'orderedList']);
/**
* Returns the children of a Fragment, filtering out whitespace-only text nodes.
* This handles trailing/leading spaces that browsers sometimes include when
* copying a link (e.g. `<a href="...">URL</a> `).
*/
const significantChildren = fragment => {
const children = [];
fragment.forEach(child => {
var _child$text;
if (child.isText && ((_child$text = child.text) === null || _child$text === void 0 ? void 0 : _child$text.trim()) === '') {
return;
}
children.push(child);
});
return children;
};
/**
* Returns true when the fragment contains exactly one meaningful leaf that
* satisfies `isSupportedLeaf`.
*
* Transparent single-child containers (paragraph, listItem, bulletList, and
* orderedList) are traversed recursively. Whitespace-only sibling text nodes
* are ignored at each level.
*/
const containsExactlyOneMeaningfulLeaf = (fragment, isSupportedLeaf) => {
const children = significantChildren(fragment);
if (children.length !== 1) {
return false;
}
const [child] = children;
if (isSupportedLeaf(child)) {
return true;
}
if (!transparentSingleChildContainerNodeTypes.has(child.type.name)) {
return false;
}
return containsExactlyOneMeaningfulLeaf(child.content, isSupportedLeaf);
};
const isSingleSmartLinkCard = slice => containsExactlyOneMeaningfulLeaf(slice.content, isSupportedCard);
const isSingleBareLink = slice => containsExactlyOneMeaningfulLeaf(slice.content, isUrlOnlyLinkTextNode);
/**
* Returns `true` when the pasted content is NOT a single standalone link.
*
* A paste is considered a "single link" (returns `false`) when:
* - The slice contains exactly one paragraph with one text node whose text
* equals its `link` mark href (bare URL link, no custom label), OR
* - The slice contains exactly one paragraph with a single `inlineCard` node
* (smartlink from the renderer or editor).
*
* Returns `true` (not a single link) when:
* - The pasted link has a custom label (text ≠ href)
* - There are multiple paragraphs or sibling nodes
* - There is additional text alongside the link in the same paragraph
* - The slice is absent (plain-text paste)
*/
export const isNotSingleLink = slice => {
if (!slice || !slice.content.size) {
// No rich-text slice → plain text paste, not a single link in the relevant sense
return true;
}
return !isSingleBareLink(slice) && !isSingleSmartLinkCard(slice);
};