UNPKG

@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
// 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 `![${image.alt}](${url}${typeof image.title === "string" ? ` "${image.title}"` : ""})`; } } 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 = "&#32;"; } 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, "&nbsp;\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("&nbsp;").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