@bratislava/wysimark-editor
Version:
The customized fork of wysimark editor - A modern and clean rich text editor for React that supports 100% of CommonMark and GitHub Flavored Markdown.
1,726 lines (1,632 loc) • 264 kB
JavaScript
// react-shim.js
import React from "react";
// src/entry/index.tsx
import throttle3 from "lodash.throttle";
import { useCallback as useCallback16, useRef as useRef14 } from "react";
import { Editor as Editor63, Transforms as Transforms48 } from "slate";
import { ReactEditor as ReactEditor18, Slate as Slate2 } from "slate-react";
// src/entry/FullscreenWrap.tsx
import styled from "@emotion/styled";
import { clsx } from "clsx";
import { useEffect, useState } from "react";
import { jsx } from "react/jsx-runtime";
var $Container = styled("div")`
background: white;
&.--fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
}
`;
var FullscreenWrap = ({ children, editor }) => {
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
editor.fullscreen.toggleFullscreen = () => {
setIsFullscreen((prev) => {
editor.fullscreen.isFullscreen = !prev;
return !prev;
});
};
}, []);
return /* @__PURE__ */ jsx($Container, { className: clsx({ "--fullscreen": isFullscreen }), children });
};
// src/convert/parse/index.ts
import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import { unified } from "unified";
// src/convert/utils.ts
function assert(pass, message) {
if (!pass)
throw new Error(`${message}`);
}
function assertElementType(element, type) {
if (element.type !== type)
throw new Error(
`Expected element to be of type ${JSON.stringify(
element
)} but is ${JSON.stringify(element, null, 2)}`
);
}
function assertUnreachable(x) {
throw new Error(
`Didn't expect to get here with value ${JSON.stringify(x, null, 2)}`
);
}
// src/convert/parse/parse-blockquote.ts
function parseBlockquote(content) {
return [{ type: "block-quote", children: parseContents(content.children) }];
}
// src/convert/parse/parse-code-block.ts
function parseCodeBlock(content) {
const codeLines = content.value.split("\n");
return [
{
type: "code-block",
language: content.lang || "text",
children: codeLines.map((codeLine) => ({
type: "code-block-line",
children: [{ text: codeLine }]
}))
}
];
}
// src/convert/parse/parse-footnote-definition.ts
function parseFootnoteDefinition(footnote) {
return [
{
type: "block-quote",
children: [
/**
* Insert an initial paragraph with the footnote identifier in square
* brackets.
*/
{ type: "paragraph", children: [{ text: `[${footnote.identifier}]` }] },
/**
* The rest of the children are parsed as is and supports the full range
* of element types like headings, lists and nested block quotes.
*/
...parseContents(footnote.children)
]
}
];
}
// src/convert/parse/parse-phrasing-content/normalize-segments.ts
import { Text as SlateText3 } from "slate";
// src/convert/parse/parse-phrasing-content/normalize-segment.ts
import { Text as SlateText2 } from "slate";
// src/convert/serialize/serialize-line/utils/is-utils.ts
import * as Slate from "slate";
function isText(segment) {
return Slate.Text.isText(segment);
}
function isElement(segment) {
return Slate.Element.isElement(segment);
}
function isPlainSpace(segment) {
return Slate.Text.isText(segment) && !!segment.text.match(/^\s+$/) && !segment.code;
}
// src/convert/serialize/serialize-line/utils/mark-utils/mark-convert-utils.ts
var MARK_KEY_TO_TOKEN = {
bold: "**",
italic: "_",
// ins: "++",
strike: "~~",
sup: "^",
sub: "~",
/**
* IMPORTANT!
*
* We noop `code` here.
*
* We accept the `code` mark so as not to throw an error if it is found. We do
* this because we handle `code` text specially because of the way it needs to
* be escaped.
*
* This is handled in the `serializeLine` code.
*/
code: ""
};
function convertMarkToSymbol(mark) {
if (mark in MARK_KEY_TO_TOKEN)
return MARK_KEY_TO_TOKEN[mark];
throw new Error(
`Could not find mark ${JSON.stringify(mark)} in MARK_KEY_TO_TOKEN lookup`
);
}
function convertMarksToSymbolsExceptCode(marks) {
return marks.map(convertMarkToSymbol).join("");
}
// src/convert/serialize/serialize-line/utils/mark-utils/mark-get-utils.ts
import { Text as SlateText } from "slate";
function getMarksFromText(text) {
const { text: _, ...marks } = text;
return Object.keys(marks);
}
function getMarksFromSegment(segment) {
if (SlateText.isText(segment)) {
if (isPlainSpace(segment)) {
throw new Error(
`You probably didn't mean to do this. We should only be getting marks from segments that are not plain space segments.`
);
}
return getMarksFromText(segment);
} else if (segment.type === "anchor") {
return getCommonAnchorMarks(segment.children);
} else {
throw new Error(`Unhandled type ${segment.type}`);
}
}
function getCommonAnchorMarks(segments) {
let commonMarks;
for (const segment of segments) {
if (!isText(segment)) {
if (segment.type === "image-inline")
continue;
throw new Error(
`Expected every segment in an anchor to be a Text segment`
);
}
if (isPlainSpace(segment))
continue;
const currentMarks = getMarksFromText(segment);
if (commonMarks === void 0) {
commonMarks = currentMarks;
continue;
}
commonMarks = commonMarks.filter(
(commonMark) => currentMarks.includes(commonMark)
);
}
if (commonMarks === void 0)
throw new Error(
`No text segments were found as children in this anchor which should not be possible`
);
return commonMarks;
}
// src/convert/serialize/serialize-line/utils/mark-utils/mark-order-utils.ts
var ORDERED_MARK_KEYS = [
"bold",
"italic",
"underline",
"strike",
"sup",
"sub",
"code"
];
function sortMarks(marks) {
return marks.slice().sort((a, b) => ORDERED_MARK_KEYS.indexOf(a) - ORDERED_MARK_KEYS.indexOf(b));
}
// src/convert/serialize/serialize-line/utils/text-utils.ts
var ESCAPES = [
"\\",
// escape
"`",
// code
"*",
// bold/italic/hr
"_",
// bold/italic/hr
"[",
// link/list
"]",
// link/list
"(",
// link
")",
// link
"#",
// headings
"+",
// list
"-",
// hr/list
".",
// numbered list
"!",
// image
"|",
// table
"^",
// sup
"~",
// sub/strikethrough
"<",
// link/html
">",
// link/html
/**
* Includes all the characters in the list of Backslash escapes in the example
* for GitHub Flavored Markdown.
*
* https://github.github.com/gfm/#backslash-escapes
*/
"{",
"}",
"=",
":",
";",
"$",
"%",
"&",
"?",
'"',
"'",
",",
"\\",
"/",
"@"
];
var ESCAPES_REGEXP = new RegExp(
`(${ESCAPES.map((symbol) => `\\${symbol}`).join("|")})`,
"g"
);
function escapeText(s) {
return s.replace(ESCAPES_REGEXP, (s2) => `\\${s2}`);
}
// src/convert/parse/parse-phrasing-content/normalize-segment.ts
function areMarksEqual(a, b) {
const marksA = getMarksFromText(a);
const marksB = getMarksFromText(b);
return marksA.length == marksB.length && marksA.every((v) => marksB.includes(v));
}
function normalizeSegment(segment, mutablePrevSegment) {
const segmentIsText = SlateText2.isText(segment);
const prevSegmentIsText = SlateText2.isText(mutablePrevSegment);
if (mutablePrevSegment && !prevSegmentIsText && !segmentIsText) {
return [{ text: "" }, segment];
}
if (!segmentIsText)
return [segment];
if (mutablePrevSegment === void 0 || !prevSegmentIsText)
return [segment];
const marksEqual = areMarksEqual(mutablePrevSegment, segment);
if (marksEqual) {
mutablePrevSegment.text = [mutablePrevSegment.text, segment.text].join("");
return [];
}
return [segment];
}
// src/convert/parse/parse-phrasing-content/normalize-segments.ts
function normalizeSegments(segments) {
const nextSegments = [];
for (let i = 0; i < segments.length; i++) {
const mutablePrevSegment = nextSegments[nextSegments.length - 1];
nextSegments.push(...normalizeSegment(segments[i], mutablePrevSegment));
}
if (nextSegments.length === 0)
nextSegments.push({ text: "" });
if (!SlateText3.isText(nextSegments[0]))
nextSegments.unshift({ text: "" });
if (!SlateText3.isText(nextSegments[nextSegments.length - 1]))
nextSegments.push({ text: "" });
return nextSegments;
}
// src/convert/parse/parse-phrasing-content/parse-inline-image/parse-generic-image.ts
function parseGenericImage(image) {
return {
url: image.url,
title: image.title || void 0,
alt: image.alt || void 0
};
}
// src/convert/parseUrl.ts
var URL_REGEX = /^(\/[^?#]*)(?:\?([^#]*))?(#.*)?$/;
function parseUrl(url) {
try {
const urlData = new URL(url);
return {
origin: urlData.origin,
hostname: urlData.hostname,
pathname: urlData.pathname,
searchParams: urlData.searchParams,
hash: urlData.hash
};
} catch (error) {
const matchdata = url.match(URL_REGEX);
if (matchdata === null)
throw new Error(`Invalid format should not happen: ${url}`);
const [_, pathname, searchParams, hash] = [...matchdata];
return {
origin: "",
hostname: "",
pathname: pathname || "",
searchParams: new URLSearchParams(searchParams),
hash: hash || ""
};
}
}
// src/convert/parse/parse-phrasing-content/parse-inline-image/parse-utils.ts
function parseSize(s) {
if (typeof s !== "string")
return null;
const sizeMatch = s.match(/^(\d+)x(\d+)$/);
if (sizeMatch === null)
return null;
return {
width: parseInt(sizeMatch[1]),
height: parseInt(sizeMatch[2])
};
}
// src/convert/parse/parse-phrasing-content/parse-inline-image/parse-portive-image.ts
function parsePortiveImage(image) {
const url = parseUrl(image.url);
if (!url.hostname.match(/[.]portive[.]com$/i))
return;
const sizeParam = url.searchParams.get("size");
if (sizeParam === null)
return;
const size = parseSize(sizeParam);
if (size === null)
return;
const srcSizeMatch = url.pathname.match(/[-][-](\d+)x(\d+)[.][a-zA-Z]+$/);
if (srcSizeMatch === null)
return;
return {
url: `${url.origin}${url.pathname}`,
title: image.title || void 0,
alt: image.alt || void 0,
width: size.width,
height: size.height,
srcWidth: parseInt(srcSizeMatch[1]),
srcHeight: parseInt(srcSizeMatch[2])
};
}
// src/convert/parse/parse-phrasing-content/parse-inline-image/parse-uncommon-mark-image.ts
function parseUncommonMarkImage(image) {
const url = parseUrl(image.url);
if (url.hash.length === 0)
return;
const params = new URLSearchParams(url.hash.slice(1));
const size = parseSize(params.get("size"));
const srcSize = parseSize(params.get("srcSize"));
if (!size || !srcSize)
return;
return {
url: `${url.origin}${url.pathname}`,
title: image.title || void 0,
alt: image.alt || void 0,
width: size.width,
height: size.height,
srcWidth: srcSize.width,
srcHeight: srcSize.height
};
}
// src/convert/parse/parse-phrasing-content/parse-inline-image/image-parsers.ts
var imageParsers = [
parsePortiveImage,
parseUncommonMarkImage,
parseGenericImage
];
// src/convert/parse/parse-phrasing-content/parse-inline-image/index.ts
function parseInlineImage(image) {
for (const imageParser of imageParsers) {
const imageData = imageParser(image);
if (!imageData)
continue;
return [
{
type: "image-inline",
...imageData,
children: [{ text: "" }]
}
];
}
throw new Error(`Shouldn't get here because last parser always returns data`);
}
// src/convert/parse/parse-phrasing-content/parse-phrasing-content.ts
function parsePhrasingContents(phrasingContents, marks = {}) {
const segments = [];
for (const phrasingContent of phrasingContents) {
segments.push(...parsePhrasingContent(phrasingContent, marks));
}
const nextInlines = normalizeSegments(segments);
return nextInlines;
}
function parsePhrasingContent(phrasingContent, marks = {}) {
switch (phrasingContent.type) {
case "delete":
return parsePhrasingContents(phrasingContent.children, {
...marks,
strike: true
});
case "emphasis":
return parsePhrasingContents(phrasingContent.children, {
...marks,
italic: true
});
case "footnoteReference":
return [{ text: `[${phrasingContent.identifier}]` }];
case "html":
return [{ text: phrasingContent.value, code: true }];
case "image":
return parseInlineImage(phrasingContent);
case "inlineCode": {
return [{ text: phrasingContent.value, ...marks, code: true }];
}
case "link":
return [
{
type: "anchor",
href: phrasingContent.url,
title: (
/**
* Ensure that `title` is undefined if it's null.
*/
phrasingContent.title == null ? void 0 : phrasingContent.title
),
children: parsePhrasingContents(phrasingContent.children, marks)
}
];
case "strong":
return parsePhrasingContents(phrasingContent.children, {
...marks,
bold: true
});
case "text":
return [{ text: phrasingContent.value, ...marks }];
case "linkReference":
case "imageReference":
throw new Error(
`linkReference and imageReference should be converted to link and image through our transformInlineLinks function`
);
case "break":
return [{ text: "\n" }];
case "footnote":
throw new Error("footnote is not supported yet");
}
assertUnreachable(phrasingContent);
}
// src/convert/parse/parse-heading.ts
function parseHeading(content) {
return [
{
type: "heading",
level: content.depth,
children: parsePhrasingContents(content.children)
}
];
}
// src/convert/parse/parse-html.ts
function parseHTML(content) {
return [
{
type: "code-block",
language: "html",
children: content.value.split("\n").map((line) => ({
type: "code-block-line",
children: [{ text: line }]
}))
}
];
}
// src/convert/parse/parse-list/parse-list-item-child.ts
function parseListItemChild(child, {
depth,
ordered,
checked
}) {
switch (child.type) {
case "paragraph":
if (checked === true || checked === false) {
return [
{
type: "task-list-item",
depth,
checked,
children: parsePhrasingContents(child.children)
}
];
} else if (ordered) {
return [
{
type: "ordered-list-item",
depth,
children: parsePhrasingContents(child.children)
}
];
} else {
return [
{
type: "unordered-list-item",
depth,
children: parsePhrasingContents(child.children)
}
];
}
case "list":
return parseList(child, depth + 1);
default:
return parseContent(child);
}
}
// src/convert/parse/parse-list/parse-list-item.ts
function parseListItem(listItem, options) {
const elements = [];
for (const child of listItem.children) {
elements.push(
...parseListItemChild(child, { ...options, checked: listItem.checked })
);
}
return elements;
}
// src/convert/parse/parse-list/parse-list.ts
function parseList(list, depth = 0) {
const elements = [];
for (const listItem of list.children) {
elements.push(
...parseListItem(listItem, { depth, ordered: !!list.ordered })
);
}
return elements;
}
// src/convert/parse/parse-paragraph.ts
function isImageBlock(segments) {
if (segments.length !== 3)
return false;
if (!("text" in segments[0]) || segments[0].text !== "")
return false;
if (!("text" in segments[2]) || segments[2].text !== "")
return false;
if (!("type" in segments[1]) || segments[1].type !== "image-inline")
return false;
return true;
}
var NBSP = "\xA0";
function isSingleNBSP(segments) {
if (segments.length !== 1)
return false;
if (!("text" in segments[0]) || segments[0].text !== NBSP)
return false;
return true;
}
function parseParagraph(content) {
const segments = parsePhrasingContents(content.children);
if (isImageBlock(segments)) {
const imageSegment = segments[1];
const imageBlockElement = {
...imageSegment,
type: "image-block"
};
return [imageBlockElement];
}
if (isSingleNBSP(segments)) {
return [
{
type: "paragraph",
children: [{ text: "" }]
}
];
}
return [
{
type: "paragraph",
children: segments
}
];
}
// src/convert/parse/parse-table.ts
function parseTable(table) {
if (table.align == null)
throw new Error(`Expected an array of AlignType for table.align`);
return [
{
type: "table",
columns: table.align.map((align) => ({
align: align || "left"
})),
children: table.children.map(parseTableRow)
}
];
}
function parseTableRow(row) {
if (row.type !== "tableRow")
throw new Error(`Expected a tableRow`);
return { type: "table-row", children: row.children.map(parseTableCell) };
}
function parseTableCell(cell) {
if (cell.type !== "tableCell")
throw new Error(`Expected a tableCell`);
return {
type: "table-cell",
children: [
{
type: "table-content",
children: parsePhrasingContents(cell.children)
}
]
};
}
// src/convert/parse/parse-thematic-break.ts
function parseThematicBreak() {
return [
{
type: "horizontal-rule",
children: [{ text: "" }]
}
];
}
// src/convert/parse/parse-content.ts
function parseContents(contents) {
const elements = [];
for (const content of contents) {
elements.push(...parseContent(content));
}
return elements;
}
function parseContent(content) {
switch (content.type) {
case "blockquote":
return parseBlockquote(content);
case "code":
return parseCodeBlock(content);
case "definition":
throw new Error(`The type "definition" should not exist. See comments`);
case "footnoteDefinition":
return parseFootnoteDefinition(content);
case "heading":
return parseHeading(content);
case "html":
return parseHTML(content);
case "list":
return parseList(content);
case "paragraph":
return parseParagraph(content);
case "table":
return parseTable(content);
case "thematicBreak":
return parseThematicBreak();
case "yaml":
return [];
}
assertUnreachable(content);
}
// src/convert/parse/transform-inline-links.ts
import { definitions } from "mdast-util-definitions";
import { SKIP, visit } from "unist-util-visit";
function transformInlineLinks(tree) {
const definition = definitions(tree);
visit(tree, (n, index, p) => {
const node = n;
const parent = p;
if (node.type === "definition" && parent !== null && typeof index === "number") {
parent.children.splice(index, 1);
return [SKIP, index];
}
if (node.type === "imageReference" || node.type === "linkReference") {
const identifier = "identifier" in node && typeof node.identifier === "string" ? node.identifier : "";
const def = definition(identifier);
if (def && parent !== null && typeof index === "number") {
const replacement = node.type === "imageReference" ? { type: "image", url: def.url, title: def.title, alt: node.alt } : {
type: "link",
url: def.url,
title: def.title,
children: node.children
};
parent.children[index] = replacement;
return [SKIP, index];
}
}
});
}
// src/convert/parse/index.ts
var parser = unified().use(remarkParse).use(remarkGfm);
function parseToAst(markdown) {
const ast = parser.parse(markdown);
transformInlineLinks(ast);
return ast;
}
function parse(markdown) {
const ast = parseToAst(markdown);
if (ast.children.length === 0) {
return [{ type: "paragraph", children: [{ text: "" }] }];
}
return parseContents(ast.children);
}
// src/convert/serialize/normalize/normalizeElementListDepths.ts
function isListItemElement(element) {
return element.type === "ordered-list-item" || element.type === "unordered-list-item" || element.type === "task-list-item";
}
function normalizeElementListDepths(elements) {
const normalizedElements = [];
let previousDepth = -1;
for (const element of elements) {
if (!isListItemElement(element)) {
normalizedElements.push(element);
previousDepth = -1;
continue;
}
const nextDepth = element.depth > previousDepth + 1 ? previousDepth + 1 : element.depth;
normalizedElements.push({ ...element, depth: nextDepth });
previousDepth = nextDepth;
}
return normalizedElements;
}
// src/convert/serialize/serialize-code-block/serialize-code-line.ts
function serializeCodeLine(codeLine) {
if (codeLine.type !== "code-block-line")
throw new Error(
`Expected all children of code-block to be a codeline but is ${JSON.stringify(
codeLine,
null,
2
)}`
);
return codeLine.children.map((segment) => segment.text).join("");
}
// src/convert/serialize/serialize-code-block/index.ts
function serializeCodeBlock(codeBlock) {
const lines = [];
let backticks = 3;
for (const codeLine of codeBlock.children) {
const lineOfCode = serializeCodeLine(codeLine);
const match = lineOfCode.match(/^([`]+)/);
if (match)
backticks = Math.max(backticks, match[1].length + 1);
lines.push(lineOfCode);
}
lines.unshift(`${"`".repeat(backticks)}${codeBlock.language}`);
lines.push(`${"`".repeat(backticks)}`);
return `${lines.join("\n")}
`;
}
// src/convert/serialize/serialize-image-shared/serialize-generic-image-url.ts
function serializeGenericImageUrl(image) {
return image.url;
}
// src/convert/serialize/serialize-image-shared/serialize-portive-image-url.ts
function serializePortiveImageUrl(image) {
if (image.url.startsWith("$"))
return "";
const { hostname } = parseUrl(image.url);
if (hostname.match(/[.]portive[.]com$/i) && image.width && image.height)
return `${image.url}?size=${image.width}x${image.height}`;
}
// src/convert/serialize/serialize-image-shared/serialize-uncommonmark-image-url.ts
function serializeUncommonmarkImageUrl(image) {
if (image.width && image.height && image.srcWidth && image.srcHeight)
return `${image.url}#srcSize=${image.srcWidth}x${image.srcHeight}&size=${image.width}x${image.height}`;
}
// src/convert/serialize/serialize-image-shared/index.ts
var urlSerializers = [
serializePortiveImageUrl,
serializeUncommonmarkImageUrl,
serializeGenericImageUrl
];
function serializeImageShared(image) {
for (const urlSerializer of urlSerializers) {
const url = urlSerializer(image);
if (typeof url === "string") {
if (url === "")
return "";
return ``;
}
}
throw new Error(`Shouldn't get here`);
}
// src/convert/serialize/serialize-image-block/index.ts
function serializeImageBlock(element) {
const serializedImageShared = serializeImageShared(element);
if (serializedImageShared === "") {
return "";
}
return `${serializedImageShared}
`;
}
// src/convert/serialize/serialize-line/serialize-line.ts
import { Element as SlateElement, Text as SlateText5 } from "slate";
// src/convert/serialize/serialize-line/diff-marks/find-marks-to-add.ts
function findMarksToAdd(orderedMarks, targetMarks) {
const marksWeNeedToAdd = targetMarks.filter(
(mark) => !orderedMarks.includes(mark)
);
const orderedMarksToAdd = sortMarks(marksWeNeedToAdd);
return { orderedMarksToAdd };
}
// src/convert/serialize/serialize-line/diff-marks/find-marks-to-remove.ts
function findMarksToRemove(orderedMarks, targetMarks) {
const nextOrderedMarks = [...orderedMarks];
const marksWeNeedToRemove = orderedMarks.filter(
(mark) => !targetMarks.includes(mark)
);
const orderedMarksToRemove = [];
for (let i = 0; i < orderedMarks.length; i++) {
if (marksWeNeedToRemove.length === 0)
break;
const markToRemove = nextOrderedMarks.pop();
if (markToRemove === void 0) {
throw new Error(
`This shouldn't happen unless we made a mistake in the algorithm`
);
}
orderedMarksToRemove.push(markToRemove);
const index = marksWeNeedToRemove.indexOf(markToRemove);
if (index !== -1) {
marksWeNeedToRemove.splice(index, 1);
}
}
return { orderedMarksToRemove, nextOrderedMarks };
}
// src/convert/serialize/serialize-line/diff-marks/index.ts
function diffMarks(orderedMarks, targetMarks) {
const { orderedMarksToRemove, nextOrderedMarks } = findMarksToRemove(
orderedMarks,
targetMarks
);
const { orderedMarksToAdd } = findMarksToAdd(nextOrderedMarks, targetMarks);
return {
remove: orderedMarksToRemove,
add: orderedMarksToAdd,
nextOrderedMarks: [...nextOrderedMarks, ...orderedMarksToAdd]
};
}
// src/convert/serialize/serialize-line/normalize-line/index.ts
import { Element as Element2 } from "slate";
// src/convert/serialize/serialize-line/normalize-line/normalizers/merge-adjacent-spaces.ts
function mergeAdjacentSpaces({
node,
nextNode,
nodes,
index
}) {
if (!isText(node) || !isPlainSpace(node) || node.code)
return false;
if (!isText(nextNode) || !isPlainSpace(nextNode) || node.code)
return false;
nodes.splice(index, 2, { text: `${node.text}${nextNode.text}` });
return true;
}
// src/convert/serialize/serialize-line/normalize-line/normalizers/move-spaces-out-of-anchors.ts
function moveSpacesAtStartOfAnchor({
node,
nodes,
prevNode,
index
}) {
if (!isElement(node))
return false;
if (node.type !== "anchor")
return false;
node;
const firstChild = node.children[0];
if (isText(firstChild) && isPlainSpace(firstChild)) {
node.children.splice(0, 1);
if (isText(prevNode) && isPlainSpace(prevNode)) {
prevNode.text = `${prevNode.text}${firstChild.text}`;
} else {
nodes.splice(index, 0, { text: firstChild.text });
}
return true;
}
return false;
}
function moveSpacesAtEndOfAnchor({
node,
nodes,
nextNode,
index
}) {
if (!isElement(node))
return false;
if (node.type !== "anchor")
return false;
node;
const lastChild = node.children[node.children.length - 1];
if (isText(lastChild) && isPlainSpace(lastChild)) {
node.children.splice(node.children.length - 1, 1);
if (isText(nextNode) && isPlainSpace(nextNode)) {
nextNode.text = `${lastChild.text}${nextNode.text}`;
} else {
nodes.splice(index + 1, 0, { text: lastChild.text });
}
return true;
}
return false;
}
// src/convert/serialize/serialize-line/normalize-line/normalizers/must-have-one-text-child.ts
function mustHaveOneTextChild({ node }) {
if (!isElement(node))
return false;
if (node.type !== "line")
return false;
if (node.children.length > 0)
return false;
node.children.push({ text: "" });
return true;
}
// src/convert/serialize/serialize-line/normalize-line/normalizers/slice-spaces-at-node-boundaries.ts
function sliceSpacesAtNodeBoundaries({
node,
nodes,
index
}) {
if (!isText(node))
return false;
if (isPlainSpace(node))
return false;
if (node.code)
return false;
const match = node.text.match(/^(\s*)(.*?)(\s*)$/);
if (!match)
return false;
if (match[1].length === 0 && match[3].length === 0)
return false;
const nextSegments = [
{ text: match[1] },
{ ...node, text: match[2] },
{ text: match[3] }
].filter((text) => text.text !== "");
nodes.splice(index, 1, ...nextSegments);
return true;
}
// src/convert/serialize/serialize-line/normalize-line/normalizers/trim-spaces-at-end-of-line.ts
function trimSpaceAtEndOfLine({
index,
nodes,
node,
parent
}) {
if (index !== nodes.length - 1)
return false;
if (nodes.length <= 1)
return false;
if (!isText(node))
return false;
if (!isPlainSpace(node))
return false;
if (parent && isElement(parent) && parent.type === "line") {
nodes.splice(nodes.length - 1, 1);
return true;
}
return false;
}
// src/convert/serialize/serialize-line/normalize-line/normalizers/trim-spaces-at-start-of-line.ts
function trimSpaceAtStartOfLine({
index,
nodes,
node,
parent
}) {
if (index !== 0)
return false;
if (nodes.length === 0)
return false;
if (!isText(node))
return false;
if (!isPlainSpace(node))
return false;
if (parent && isElement(parent) && parent.type === "line") {
nodes.splice(0, 1);
return true;
}
return false;
}
// src/convert/serialize/serialize-line/normalize-line/normalizers/index.ts
var normalizers = [
sliceSpacesAtNodeBoundaries,
moveSpacesAtStartOfAnchor,
moveSpacesAtEndOfAnchor,
mergeAdjacentSpaces,
trimSpaceAtStartOfLine,
trimSpaceAtEndOfLine,
mustHaveOneTextChild
];
// src/convert/serialize/serialize-line/normalize-line/run-normalizers-on-node.ts
function runNormalizersOnNode(normalizeOptions) {
for (const normalizer of normalizers) {
const isHandled = normalizer(normalizeOptions);
if (isHandled) {
return true;
}
}
return false;
}
// src/convert/serialize/serialize-line/normalize-line/normalize-nodes.ts
var MAX_RERUNS = 72;
function normalizeNodes(nodes, parent) {
let isAnyUpdated = false;
let isUpdated;
let runs = 0;
const maxReruns = (nodes.length + 1) * MAX_RERUNS;
do {
isUpdated = false;
runs = runs + 1;
if (runs > maxReruns)
throw new Error(
`There have been ${runs} normalization passes (72x the number of nodes at this level). This likely indicates a bug in the code.`
);
segmentLoop:
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (isElement(node)) {
const isChildrenUpdated = normalizeNodes(
node.children,
node
);
if (isChildrenUpdated) {
isUpdated = true;
isAnyUpdated = true;
break segmentLoop;
}
}
const prevNode = nodes[i - 1];
const nextNode = nodes[i + 1];
const options = {
parent,
node,
prevNode,
nextNode,
index: i,
nodes
};
if (runNormalizersOnNode(options)) {
isUpdated = true;
isAnyUpdated = true;
break segmentLoop;
}
}
} while (isUpdated);
return isAnyUpdated;
}
// src/convert/serialize/serialize-line/normalize-line/index.ts
var duplicateSegments = (segments) => {
return segments.map((segment) => {
if (Element2.isElement(segment) && segment.type === "anchor") {
return {
...segment,
children: duplicateSegments(segment.children)
};
} else {
return segment;
}
});
};
function normalizeLine(segments) {
const line = {
type: "line",
children: duplicateSegments(segments)
};
normalizeNodes([line], void 0);
return line.children;
}
// src/convert/serialize/serialize-line/segment/serialize-segment.ts
import { Text as SlateText4 } from "slate";
// src/convert/serialize/serialize-line/segment/serialize-code-text.ts
function serializeCodeText(text) {
let max = 0;
for (const match of text.text.matchAll(/[`]+/g)) {
max = Math.max(max, match[0].length);
}
if (max === 0)
return `\`${text.text.replace(/[`]/g, "\\`")}\``;
return `${"`".repeat(max + 1)} ${text.text} ${"`".repeat(max + 1)}`;
}
// src/convert/serialize/serialize-line/segment/serialize-anchor.ts
function escapeTitle(title) {
return title.replace(/"/g, '\\"');
}
function serializeAnchor(anchor) {
const commonAnchorMarks = getCommonAnchorMarks(anchor.children);
if (anchor.href.startsWith("$"))
return serializeLine(
anchor.children,
commonAnchorMarks,
commonAnchorMarks
);
if (typeof anchor.title === "string" && anchor.title.length > 0) {
return (
/**
* TODO: Handle anchor children more elegantly in serializeAnchor.
*
* We type cast `children` as `Segment` here because the children of an
* `anchor` is limited to be Inline types. There are two things to do
* related to this though:
*
* - [ ] consider fixing the `anchor` type to actually limit the
* children as expected.
* - [ ] consider expanding the definition of `Segment` to include
* inline images as that is an acceptable inline value which is
* currently not defined as part of Segment.
*/
`[${serializeLine(
anchor.children,
commonAnchorMarks,
commonAnchorMarks
)}](${anchor.href} "${escapeTitle(anchor.title)}")`
);
} else {
return (
/**
* TODO: Handle anchor children more elegantly in serializeAnchor.
*
* We type cast `children` as `Segment` here because the children of an
* `anchor` is limited to be Inline types. There are two things to do
* related to this though:
*
* - [ ] consider fixing the `anchor` type to actually limit the
* children as expected.
* - [ ] consider expanding the definition of `Segment` to include
* inline images as that is an acceptable inline value which is
* currently not defined as part of Segment.
*/
`[${serializeLine(
anchor.children,
commonAnchorMarks,
commonAnchorMarks
)}](${anchor.href})`
);
}
}
// src/convert/serialize/serialize-line/segment/serialize-non-code-text.ts
function serializeNonCodeText(text) {
return escapeText(text.text);
}
// src/convert/serialize/serialize-line/segment/serialize-segment.ts
function serializeSegment(segment) {
if (SlateText4.isText(segment)) {
if (segment.code)
return serializeCodeText(segment);
return serializeNonCodeText(segment);
}
switch (segment.type) {
case "anchor": {
return serializeAnchor(segment);
}
case "image-inline":
return serializeImageShared(segment);
default:
assertUnreachable(segment);
}
}
// src/convert/serialize/serialize-line/serialize-line.ts
function serializeLine(inputSegments, leadingMarks = [], trailingMarks = []) {
const segments = normalizeLine(inputSegments);
const substrings = [];
let leadingDiff = diffMarks(leadingMarks, getMarksFromSegment(segments[0]));
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (SlateText5.isText(segment) && isPlainSpace(segment)) {
substrings.push(segment.text);
continue;
}
substrings.push(convertMarksToSymbolsExceptCode(leadingDiff.add));
substrings.push(serializeSegment(segment));
const nextMarks = getNextMarks(segments, i, trailingMarks);
const trailingDiff = diffMarks(leadingDiff.nextOrderedMarks, nextMarks);
substrings.push(convertMarksToSymbolsExceptCode(trailingDiff.remove));
leadingDiff = trailingDiff;
}
return substrings.join("");
}
function getNextMarks(segments, index, trailingMarks) {
for (let i = index + 1; i < segments.length; i++) {
const segment = segments[i];
if (isPlainSpace(segment))
continue;
if (SlateElement.isElement(segment) && segment.type === "image-inline")
continue;
return getMarksFromSegment(segment);
}
return trailingMarks;
}
// src/convert/serialize/serialize-table/index.ts
function serializeTable(element) {
const lines = [];
lines.push(serializeTableRow(element.children[0]));
lines.push(serializeColumns(element.columns));
element.children.slice(1).forEach((row) => {
lines.push(serializeTableRow(row));
});
return `${lines.join("\n")}
`;
}
function serializeColumns(columns) {
const isAllLeft = columns.every((column) => column.align === "left");
if (isAllLeft) {
return `|${columns.map(() => "---").join("|")}|`;
}
return `|${columns.map((column) => serializeAlign(column.align)).join("|")}|`;
}
function serializeAlign(align) {
switch (align) {
case "left":
return ":---";
case "center":
return ":---:";
case "right":
return "---:";
}
}
function serializeTableRow(element) {
assertElementType(element, "table-row");
return `|${element.children.map(serializeTableCell).join("|")}|`;
}
function serializeTableCell(element) {
assertElementType(element, "table-cell");
assert(
element.children.length === 1,
`Expected table-cell to have one child but is ${JSON.stringify(
element.children
)}`
);
return element.children.map(serializeTableContent).join();
}
function serializeTableContent(element) {
assertElementType(element, "table-content");
return serializeLine(element.children);
}
// src/convert/serialize/serialize-element.ts
var LIST_INDENT_SIZE = 4;
function serializeElement(element, orders) {
switch (element.type) {
case "anchor":
return `[${serializeLine(element.children)}](${element.href})`;
case "block-quote": {
const lines = serializeElements(element.children);
return `${lines.split("\n").map((line) => `> ${line}`.trim()).join("\n")}
`;
}
case "code-block":
return serializeCodeBlock(element);
case "code-block-line":
throw new Error(
`code-block-line should only be present as child of code-block`
);
case "heading":
return `${"#".repeat(element.level)} ${serializeLine(
element.children
)}
`;
case "horizontal-rule":
return "---\n\n";
case "paragraph":
return `${serializeLine(element.children)}
`;
case "table":
return serializeTable(element);
case "table-row":
case "table-cell":
case "table-content":
throw new Error(
`Table elements should only be present as children of table which should be handled by serializeTable. Got ${element.type} may indicate an error in normalization.`
);
case "unordered-list-item": {
const indent2 = " ".repeat(element.depth * LIST_INDENT_SIZE);
return `${indent2}- ${serializeLine(element.children)}
`;
}
case "ordered-list-item": {
const indent2 = " ".repeat(element.depth * LIST_INDENT_SIZE);
return `${indent2}${orders[element.depth]}. ${serializeLine(
element.children
)}
`;
}
case "task-list-item": {
const indent2 = " ".repeat(element.depth * LIST_INDENT_SIZE);
let line = serializeLine(element.children);
if (line.trim() === "") {
line = " ";
}
return `${indent2}- [${element.checked ? "x" : " "}] ${line}
`;
}
case "image-block":
return serializeImageBlock(element);
case "image-inline":
throw new Error(
`This shouldn't happen because inlines are handled in serializeSegment`
);
}
assertUnreachable(element);
}
// src/convert/serialize/serialize-elements.ts
function serializeElements(elements) {
const segments = [];
let orders = [];
for (const element of elements) {
if (element.type === "ordered-list-item") {
orders[element.depth] = (orders[element.depth] || 0) + 1;
orders = orders.slice(0, element.depth + 1);
} else if (element.type === "unordered-list-item" || element.type === "task-list-item") {
orders = orders.slice(0, element.depth);
} else {
orders = [];
}
segments.push(serializeElement(element, orders));
}
const joined = segments.join("");
if (joined.trim() === "")
return "";
return replaceConsecutiveNewlines(replaceLeadingNewlines(joined)).trim();
}
function replaceLeadingNewlines(input) {
return input.replace(/^\n\n/g, " \n\n");
}
function replaceConsecutiveNewlines(input) {
return input.replace(/(\n{4,})/g, (match) => {
const newlineCount = match.length;
const count = Math.floor((newlineCount - 2) / 2);
return "\n\n" + Array(count).fill(" ").join("\n\n") + "\n\n";
});
}
// src/convert/serialize/index.ts
function serialize(elements) {
const normalizedElements = normalizeElementListDepths(elements);
return serializeElements(normalizedElements);
}
// src/sink/create-plugin/index.ts
var createPlugin = (fn) => {
return { fn };
};
// src/sink/editable/index.tsx
import { useEffect as useEffect2, useMemo } from "react";
import { Editor } from "slate";
import { useSlateStatic } from "slate-react";
// src/sink/editable/utils.ts
function defined(value) {
return !!value;
}
// src/sink/editable/create-decorate.ts
function createDecorate(originalFn, plugins2) {
const fns = plugins2.map((plugin) => plugin.editableProps?.decorate).filter(defined);
return function(entry) {
const ranges = [];
for (const fn of fns) {
const resultRanges = fn(entry);
ranges.push(...resultRanges);
}
if (originalFn)
ranges.push(...originalFn(entry));
return ranges;
};
}
// src/sink/editable/create-editable.tsx
import { Editable } from "slate-react";
import { jsx as jsx2 } from "react/jsx-runtime";
function createEditable(plugins2) {
const fns = plugins2.map((plugin) => plugin.renderEditable).filter(defined);
let CurrentRenderEditable = (props) => /* @__PURE__ */ jsx2(Editable, { ...props });
for (const fn of fns) {
const PrevRenderEditable = CurrentRenderEditable;
CurrentRenderEditable = (props) => {
return fn({
attributes: props,
Editable: PrevRenderEditable
});
};
}
return CurrentRenderEditable;
}
// src/sink/editable/create-handler.ts
function extractEditableFns(plugins2, key2) {
const fns = [];
for (const plugin of plugins2) {
const maybeFn = plugin.editableProps?.[key2];
if (maybeFn)
fns.push(maybeFn);
}
return fns;
}
function createHandlerFn(fns, originalFn) {
return function(event) {
for (const fn of fns) {
if (fn(event))
return;
}
originalFn?.(event);
};
}
var createOnKeyDown = (originalFn, plugins2) => {
const fns = extractEditableFns(plugins2, "onKeyDown");
return createHandlerFn(fns, originalFn);
};
var createOnKeyUp = (originalFn, plugins2) => {
const fns = extractEditableFns(plugins2, "onKeyUp");
return createHandlerFn(fns, originalFn);
};
var createOnPaste = (originalFn, plugins2) => {
const fns = extractEditableFns(plugins2, "onPaste");
return createHandlerFn(fns, originalFn);
};
var createOnDrop = (originalFn, plugins2) => {
const fns = extractEditableFns(plugins2, "onDrop");
return createHandlerFn(fns, originalFn);
};
// src/sink/editable/create-render-element.ts
function createRenderElement(originalFn, plugins2) {
const fns = plugins2.map((plugin) => plugin.editableProps?.renderElement).filter(defined);
return function renderElement5(renderElementProps) {
for (const fn of fns) {
const result = fn(renderElementProps);
if (result)
return result;
}
if (originalFn === void 0) {
throw new Error(
`Element with type ${renderElementProps.element.type} not handled. Note that renderElement is not defined on SinkEditable so this is only the result of checking the Sink Plugins.`
);
}
return originalFn(renderElementProps);
};
}
// src/sink/editable/create-render-leaf.ts
import { cloneElement } from "react";
function createRenderLeaf(originalFn, plugins2) {
if (originalFn === void 0) {
throw new Error(`renderLeaf was not defined on SinkEditable`);
}
const fns = plugins2.map((plugin) => plugin.editableProps?.renderLeaf).filter(defined).reverse();
return function(renderLeafProps) {
let value = originalFn({
...renderLeafProps,
/**
* We override this because `attributes` should only appear on the
* uppermost leaf element if there are several nested ones and it's
* possible that this won't be the uppermost leaf.
*
* We add attributes back on at the very end so no need to worry if
* we omit it here.
*/
attributes: {}
});
for (const fn of fns) {
const possibleValue = fn({
...renderLeafProps,
children: value
});
if (possibleValue) {
value = possibleValue;
}
}
value = cloneElement(value, renderLeafProps.attributes);
return value;
};
}
// src/sink/editable/create-render-placeholder.tsx
function createRenderPlaceholder(originalFn, plugins2) {
if (originalFn)
return originalFn;
const fns = plugins2.map((plugin) => plugin.editableProps?.renderPlaceholder).filter(defined);
if (fns.length === 0)
return void 0;
return function(renderPlaceholderProps) {
if (fns.length > 1) {
throw new Error(
`Only one plugin can define renderPlaceholder but there are ${fns.length}`
);
}
const fn = fns[0];
if (fn == null)
throw new Error(`Expected fn to be defined`);
return fn(renderPlaceholderProps);
};
}
// src/sink/editable/styles.tsx
import styled2 from "@emotion/styled";
var SinkReset = styled2("div")`
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
box-sizing: border-box;
`;
// src/sink/editable/index.tsx
import { jsx as jsx3 } from "react/jsx-runtime";
function SinkEditable(originalProps) {
const editor = useSlateStatic();
useEffect2(() => {
Editor.normalize(editor, { force: true });
}, []);
const { plugins: plugins2 } = editor.sink;
const nextProps = useMemo(
() => ({
...originalProps,
decorate: createDecorate(originalProps.decorate, plugins2),
renderElement: createRenderElement(originalProps.renderElement, plugins2),
renderLeaf: createRenderLeaf(originalProps.renderLeaf, plugins2),
renderPlaceholder: createRenderPlaceholder(
originalProps.renderPlaceholder,
plugins2
),
/**
* NOTE: We skip `onKeyUp` as it is deprecated. If somebody needs it in new
* code, we can add it back in.
*
* https://developer.mozilla.org/en-US/docs/Web/API/Element/keypress_event
*/
onKeyDown: createOnKeyDown(originalProps.onKeyDown, plugins2),
onKeyUp: createOnKeyUp(originalProps.onKeyUp, plugins2),
onPaste: createOnPaste(originalProps.onPaste, plugins2),
onDrop: createOnDrop(originalProps.onDrop, plugins2)
}),
Object.values(originalProps)
);
const NextEditable = useMemo(() => createEditable(plugins2), [plugins2]);
return /* @__PURE__ */ jsx3(NextEditable, { ...nextProps });
}
// src/sink/editor/create-boolean-action.ts
function createBooleanAction(editor, actionKey, plugins2) {
const originalAction = editor[actionKey];
const actionPlugins = plugins2.filter((plugin) => plugin.editor?.[actionKey]);
return function nextBooleanAction(node) {
for (const plugin of actionPlugins) {
const result = plugin.editor?.[actionKey]?.(node);
if (typeof result === "boolean")
return result;
}
return originalAction(node);
};
}
// src/sink/editor/create-void-action.ts
function createVoidAction(editor, actionKey, plugins2) {
const originalAction = editor[actionKey];
const actionPlugins = plugins2.filter((plugin) => plugin.editor?.[actionKey]);
return function nextVoidAction(...args) {
let isHandled = false;
const afterHandledCallbacks = [];
for (const plugin of actionPlugins) {
const response = plugin.editor?.[actionKey]?.(...args);
if (typeof response === "function") {
afterHandledCallbacks.push(response);
} else if (response === true) {
isHandled = true;
break;
}
}
if (!isHandled) {
originalAction(...args);
}
afterHandledCallbacks.forEach((callback) => callback());
};
}
// src/sink/editor/index.ts
function createWithSink(pluginFns) {
return (originalEditor, options) => {
const editor = originalEditor;
const plugins2 = pluginFns.map(
(plugin) => plugin(editor, options, { createPolicy: (x) => x })
);
editor.sink = { plugins: plugins2 };
editor.isMaster = "isMaster" in editor ? editor.isMaster : () => false;
editor.isSlave = "isSlave" in editor ? editor.isSlave : () => false;
editor.isStandalone = "isStandalone" in editor ? editor.isStandalone : () => false;
Object.assign(editor, {
/**
* void
*/
normalizeNode: createVoidAction(editor, "normalizeNode", plugins2),
deleteBackward: createVoidAction(editor, "deleteBackward", plugins2),
deleteForward: createVoidAction(editor, "deleteForward", plugins2),
deleteFragment: createVoidAction(editor, "deleteFragment", plugins2),
insertBreak: createVoidAction(editor, "insertBreak", plugins2),
insertFragment: createVoidAction(editor, "insertFragment", plugins2),
insertNode: createVoidAction(editor, "insertNode", plugins2),
insertText: createVoidAction(editor, "insertText", plugins2),
/**
* boolean
*/
isInline: createBooleanAction(editor, "isInline", plugins2),
isVoid: createBooleanAction(editor, "isVoid", plugins2),
isMaster: createBooleanAction(editor, "isMaster", plugins2),
isSlave: createBooleanAction(editor, "isSlave", plugins2),
isStandalone: createBooleanAction(editor, "isStandalone", plugins2)
});
return editor;
};
}
// src/sink/create-sink/index.tsx
var createSink = (pluginFunctions) => {
const fns = pluginFunctions.map((plugin) => plugin.fn);
const withSink2 = createWithSink(fns);
const returnValue = { withSink: withSink2, SinkEditable };
return returnValue;
};
// src/sink/is-debug.ts
var isDebug = false;
// src/sink/utils/core-utils/better-at.ts
import { Element as Element3 } from "slate";
import { ReactEdito