@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
JavaScript
'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