UNPKG

@atlaskit/editor-plugin-paste-options-toolbar

Version:

Paste options toolbar for @atlaskit/editor-core

135 lines (125 loc) 4.62 kB
/** * 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; } }; /** * Returns true if the slice represents a single smart-link card node. * Handles two shapes: * - paragraph > inlineCard|blockCard (smartlink from editor/renderer, wrapped in a paragraph) * - inlineCard|blockCard (top-level card with no paragraph wrapper) */ const isSingleSmartLinkCard = slice => { const isSupportedCard = node => node.type.name === 'inlineCard' || node.type.name === 'blockCard'; if (slice.content.childCount !== 1) { return false; } const topNode = slice.content.child(0); // Top-level inlineCard/blockCard (no paragraph wrapper) if (isSupportedCard(topNode)) { return true; } // paragraph > inlineCard/blockCard if (topNode.type.name !== 'paragraph') { return false; } if (topNode.childCount !== 1) { return false; } return isSupportedCard(topNode.child(0)); }; /** * 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 if the slice represents a single bare link with no label. * Handles two shapes: * - paragraph > text(link mark) (standard rich-text paste, wrapped in a paragraph) * - text(link mark) (top-level text node with no paragraph wrapper) * Whitespace-only sibling text nodes are ignored in both cases. */ const isSingleBareLink = slice => { // Top-level text node with a link mark (no paragraph wrapper) const significantTopChildren = significantChildren(slice.content); if (significantTopChildren.length === 1 && isUrlOnlyLinkTextNode(significantTopChildren[0])) { return true; } // paragraph > text(link mark) if (slice.content.childCount !== 1) { return false; } const topNode = slice.content.child(0); if (topNode.type.name !== 'paragraph') { return false; } const children = significantChildren(topNode.content); if (children.length !== 1) { return false; } return isUrlOnlyLinkTextNode(children[0]); }; /** * 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); };