UNPKG

@liveblocks/react-ui

Version:

A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.

237 lines (233 loc) 7.37 kB
'use strict'; var slate = require('slate'); var isText = require('../utils/is-text.cjs'); var marks = require('../utils/marks.cjs'); var customLinks = require('./custom-links.cjs'); function withAutoLinks(editor) { const { isInline, normalizeNode, deleteBackward } = editor; editor.isInline = (element) => { return element.type === "auto-link" ? true : isInline(element); }; editor.normalizeNode = (entry) => { const [node, path] = entry; if (customLinks.isComposerBodyCustomLink(node)) { return; } if (isText.isText(node)) { const parentNode = slate.Node.parent(editor, path); if (customLinks.isComposerBodyCustomLink(parentNode)) { return; } else if (isComposerBodyAutoLink(parentNode)) { const parentPath = slate.Path.parent(path); handleLinkEdit(editor, [parentNode, parentPath]); if (!isText.isPlainText(node)) { const marks$1 = marks.filterActiveMarks(node); slate.Transforms.unsetNodes(editor, marks$1, { at: path }); } } else { handleLinkCreate(editor, [node, path]); handleNeighbours(editor, [node, path]); } } normalizeNode(entry); }; editor.deleteBackward = (unit) => { deleteBackward(unit); const { selection } = editor; if (!selection) return; if (!slate.Range.isCollapsed(selection)) return; const [match] = slate.Editor.nodes(editor, { at: selection, match: isComposerBodyAutoLink, mode: "lowest" }); if (!match) return; slate.Transforms.unwrapNodes(editor, { match: isComposerBodyAutoLink }); }; return editor; } function isComposerBodyAutoLink(node) { return slate.Element.isElement(node) && node.type === "auto-link"; } const URL_REGEX = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/; const PUNCTUATION_OR_SPACE = /[.,;!?\s()]/; const PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/; const PARENTHESES = /[()]/; function isSeparator(char) { return PUNCTUATION_OR_SPACE.test(char); } function endsWithSeparator(textContent) { const lastCharacter = textContent[textContent.length - 1]; return lastCharacter !== void 0 ? isSeparator(lastCharacter) : false; } function startsWithSeparator(textContent) { const firstCharacter = textContent[0]; return firstCharacter !== void 0 ? isSeparator(firstCharacter) : false; } function endsWithPeriodOrQuestionMark(textContent) { return textContent[textContent.length - 1] === "." || textContent[textContent.length - 1] === "?"; } function getUrlLogicalLength(url) { if (!PARENTHESES.test(url)) { return url.length; } let logicalLength = 0; let parenthesesCount = 0; for (const character of url) { if (character === "(") { parenthesesCount++; } if (character === ")") { parenthesesCount--; if (parenthesesCount < 0) { break; } } logicalLength++; } return logicalLength; } function isPreviousNodeValid(editor, path) { const entry = slate.Editor.previous(editor, { at: path }); if (!entry) return true; return isText.isText(entry[0]) && (endsWithSeparator(entry[0].text) || entry[0].text === ""); } function isNextNodeValid(editor, path) { const entry = slate.Editor.next(editor, { at: path }); if (!entry) return true; return isText.isText(entry[0]) && (startsWithSeparator(entry[0].text) || entry[0].text === ""); } function isContentAroundValid(editor, entry, start, end) { const [node, path] = entry; const text = node.text; const contentBefore = text[start - 1]; const contentBeforeIsValid = start > 0 && contentBefore ? isSeparator(contentBefore) : isPreviousNodeValid(editor, path); const contentAfter = text[end]; const contentAfterIsValid = end < text.length && contentAfter ? isSeparator(contentAfter) : isNextNodeValid(editor, path); return contentBeforeIsValid && contentAfterIsValid; } const handleLinkEdit = (editor, entry) => { const [node, path] = entry; const children = slate.Node.children(editor, path); for (const [child] of children) { if (isText.isText(child)) continue; slate.Transforms.unwrapNodes(editor, { at: path }); return; } const text = slate.Node.string(node); const match = URL_REGEX.exec(text); const matchContent = match?.[0]; if (!match || matchContent !== text) { slate.Transforms.unwrapNodes(editor, { at: path }); return; } if (endsWithPeriodOrQuestionMark(text)) { slate.Transforms.unwrapNodes(editor, { at: path }); const textBeforePeriod = text.slice(0, text.length - 1); slate.Transforms.wrapNodes( editor, { type: "auto-link", url: textBeforePeriod, children: [] }, { at: { anchor: { path, offset: 0 }, focus: { path, offset: textBeforePeriod.length } }, split: true } ); return; } const logicalLength = getUrlLogicalLength(text); if (logicalLength < text.length) { slate.Transforms.unwrapNodes(editor, { at: path }); const logicalText = text.slice(0, logicalLength); slate.Transforms.wrapNodes( editor, { type: "auto-link", url: logicalText, children: [] }, { at: { anchor: { path, offset: 0 }, focus: { path, offset: logicalText.length } }, split: true } ); return; } if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) { slate.Transforms.unwrapNodes(editor, { at: path }); return; } if (node.url !== text) { slate.Transforms.setNodes(editor, { url: matchContent }, { at: path }); return; } }; const handleLinkCreate = (editor, entry) => { const [node, path] = entry; const match = URL_REGEX.exec(node.text); const matchContent = match?.[0]; if (!match || matchContent === void 0) { return; } const start = match.index; const end = start + matchContent.length; if (!isContentAroundValid(editor, entry, start, end)) return; slate.Transforms.wrapNodes( editor, { type: "auto-link", url: matchContent, children: [] }, { at: { anchor: { path, offset: start }, focus: { path, offset: end } }, split: true } ); return; }; const handleNeighbours = (editor, entry) => { const [node, path] = entry; const text = node.text; const previousSibling = slate.Editor.previous(editor, { at: path }); if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) { if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) { slate.Transforms.unwrapNodes(editor, { at: previousSibling[1] }); slate.Transforms.mergeNodes(editor, { at: path }); return; } if (!startsWithSeparator(text)) { slate.Transforms.unwrapNodes(editor, { at: previousSibling[1] }); return; } } const nextSibling = slate.Editor.next(editor, { at: path }); if (nextSibling && isComposerBodyAutoLink(nextSibling[0]) && !endsWithSeparator(text)) { slate.Transforms.unwrapNodes(editor, { at: nextSibling[1] }); return; } }; exports.isComposerBodyAutoLink = isComposerBodyAutoLink; exports.withAutoLinks = withAutoLinks; //# sourceMappingURL=auto-links.cjs.map