UNPKG

@milkdown/preset-commonmark

Version:

The commonmark preset of [milkdown](https://milkdown.dev/).

1,814 lines (1,813 loc) 61.9 kB
import { $markAttr, $markSchema, $inputRule, $command, $useKeymap, $node, $remark, $nodeAttr, $nodeSchema, $ctx, $prose } from "@milkdown/utils"; import { remarkStringifyOptionsCtx, commandsCtx, editorViewCtx } from "@milkdown/core"; import { toggleMark, setBlockType, wrapIn } from "@milkdown/prose/commands"; import { visit } from "unist-util-visit"; import { Fragment } from "@milkdown/prose/model"; import { expectDomTypeError } from "@milkdown/exception"; import { textblockTypeInputRule, wrappingInputRule, InputRule } from "@milkdown/prose/inputrules"; import { TextSelection, Selection, PluginKey, Plugin } from "@milkdown/prose/state"; import { markRule, findSelectedNodeOfType } from "@milkdown/prose"; import { sinkListItem, splitListItem, liftListItem } from "@milkdown/prose/schema-list"; import { ReplaceStep, AddMarkStep } from "@milkdown/prose/transform"; import { Decoration, DecorationSet } from "@milkdown/prose/view"; import remarkInlineLinks from "remark-inline-links"; function serializeText(state, node) { var _a; const lastIsHardBreak = node.childCount >= 1 && ((_a = node.lastChild) == null ? void 0 : _a.type.name) === "hardbreak"; if (!lastIsHardBreak) { state.next(node.content); return; } const contentArr = []; node.content.forEach((n, _, i) => { if (i === node.childCount - 1) return; contentArr.push(n); }); state.next(Fragment.fromArray(contentArr)); } function withMeta(plugin, meta) { Object.assign(plugin, { meta: { package: "@milkdown/preset-commonmark", ...meta } }); return plugin; } const emphasisAttr = $markAttr("emphasis"); withMeta(emphasisAttr, { displayName: "Attr<emphasis>", group: "Emphasis" }); const emphasisSchema = $markSchema("emphasis", (ctx) => ({ attrs: { marker: { default: ctx.get(remarkStringifyOptionsCtx).emphasis || "*", validate: "string" } }, parseDOM: [ { tag: "i" }, { tag: "em" }, { style: "font-style", getAttrs: (value) => value === "italic" } ], toDOM: (mark) => ["em", ctx.get(emphasisAttr.key)(mark)], parseMarkdown: { match: (node) => node.type === "emphasis", runner: (state, node, markType) => { state.openMark(markType, { marker: node.marker }); state.next(node.children); state.closeMark(markType); } }, toMarkdown: { match: (mark) => mark.type.name === "emphasis", runner: (state, mark) => { state.withMark(mark, "emphasis", void 0, { marker: mark.attrs.marker }); } } })); withMeta(emphasisSchema.mark, { displayName: "MarkSchema<emphasis>", group: "Emphasis" }); withMeta(emphasisSchema.ctx, { displayName: "MarkSchemaCtx<emphasis>", group: "Emphasis" }); const toggleEmphasisCommand = $command("ToggleEmphasis", (ctx) => () => { return toggleMark(emphasisSchema.type(ctx)); }); withMeta(toggleEmphasisCommand, { displayName: "Command<toggleEmphasisCommand>", group: "Emphasis" }); const emphasisStarInputRule = $inputRule((ctx) => { return markRule(/(?:^|[^*])\*([^*]+)\*$/, emphasisSchema.type(ctx), { getAttr: () => ({ marker: "*" }), updateCaptured: ({ fullMatch, start }) => !fullMatch.startsWith("*") ? { fullMatch: fullMatch.slice(1), start: start + 1 } : {} }); }); withMeta(emphasisStarInputRule, { displayName: "InputRule<emphasis>|Star", group: "Emphasis" }); const emphasisUnderscoreInputRule = $inputRule((ctx) => { return markRule(/(?:^|[^_])_([^_]+)_$/, emphasisSchema.type(ctx), { getAttr: () => ({ marker: "_" }), updateCaptured: ({ fullMatch, start }) => !fullMatch.startsWith("_") ? { fullMatch: fullMatch.slice(1), start: start + 1 } : {} }); }); withMeta(emphasisUnderscoreInputRule, { displayName: "InputRule<emphasis>|Underscore", group: "Emphasis" }); const emphasisKeymap = $useKeymap("emphasisKeymap", { ToggleEmphasis: { shortcuts: "Mod-i", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(toggleEmphasisCommand.key); } } }); withMeta(emphasisKeymap.ctx, { displayName: "KeymapCtx<emphasis>", group: "Emphasis" }); withMeta(emphasisKeymap.shortcuts, { displayName: "Keymap<emphasis>", group: "Emphasis" }); const strongAttr = $markAttr("strong"); withMeta(strongAttr, { displayName: "Attr<strong>", group: "Strong" }); const strongSchema = $markSchema("strong", (ctx) => ({ attrs: { marker: { default: ctx.get(remarkStringifyOptionsCtx).strong || "*", validate: "string" } }, parseDOM: [ // This works around a Google Docs misbehavior where // pasted content will be inexplicably wrapped in `<b>` // tags with a font-weight normal. { tag: "b", getAttrs: (node) => node.style.fontWeight != "normal" && null }, { tag: "strong" }, { style: "font-style", getAttrs: (value) => value === "bold" }, { style: "font-weight=400", clearMark: (m) => m.type.name == "strong" }, { style: "font-weight", getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null } ], toDOM: (mark) => ["strong", ctx.get(strongAttr.key)(mark)], parseMarkdown: { match: (node) => node.type === "strong", runner: (state, node, markType) => { state.openMark(markType, { marker: node.marker }); state.next(node.children); state.closeMark(markType); } }, toMarkdown: { match: (mark) => mark.type.name === "strong", runner: (state, mark) => { state.withMark(mark, "strong", void 0, { marker: mark.attrs.marker }); } } })); withMeta(strongSchema.mark, { displayName: "MarkSchema<strong>", group: "Strong" }); withMeta(strongSchema.ctx, { displayName: "MarkSchemaCtx<strong>", group: "Strong" }); const toggleStrongCommand = $command("ToggleStrong", (ctx) => () => { return toggleMark(strongSchema.type(ctx)); }); withMeta(toggleStrongCommand, { displayName: "Command<toggleStrongCommand>", group: "Strong" }); const strongInputRule = $inputRule((ctx) => { return markRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, strongSchema.type(ctx), { getAttr: (match) => { return { marker: match[0].startsWith("*") ? "*" : "_" }; } }); }); withMeta(strongInputRule, { displayName: "InputRule<strong>", group: "Strong" }); const strongKeymap = $useKeymap("strongKeymap", { ToggleBold: { shortcuts: ["Mod-b"], command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(toggleStrongCommand.key); } } }); withMeta(strongKeymap.ctx, { displayName: "KeymapCtx<strong>", group: "Strong" }); withMeta(strongKeymap.shortcuts, { displayName: "Keymap<strong>", group: "Strong" }); const inlineCodeAttr = $markAttr("inlineCode"); withMeta(inlineCodeAttr, { displayName: "Attr<inlineCode>", group: "InlineCode" }); const inlineCodeSchema = $markSchema("inlineCode", (ctx) => ({ priority: 100, code: true, parseDOM: [{ tag: "code" }], toDOM: (mark) => ["code", ctx.get(inlineCodeAttr.key)(mark)], parseMarkdown: { match: (node) => node.type === "inlineCode", runner: (state, node, markType) => { state.openMark(markType); state.addText(node.value); state.closeMark(markType); } }, toMarkdown: { match: (mark) => mark.type.name === "inlineCode", runner: (state, mark, node) => { state.withMark(mark, "inlineCode", node.text || ""); } } })); withMeta(inlineCodeSchema.mark, { displayName: "MarkSchema<inlineCode>", group: "InlineCode" }); withMeta(inlineCodeSchema.ctx, { displayName: "MarkSchemaCtx<inlineCode>", group: "InlineCode" }); const toggleInlineCodeCommand = $command( "ToggleInlineCode", (ctx) => () => (state, dispatch) => { const { selection, tr } = state; if (selection.empty) return false; const { from, to } = selection; const has = state.doc.rangeHasMark(from, to, inlineCodeSchema.type(ctx)); if (has) { dispatch == null ? void 0 : dispatch(tr.removeMark(from, to, inlineCodeSchema.type(ctx))); return true; } const restMarksName = Object.keys(state.schema.marks).filter( (x) => x !== inlineCodeSchema.type.name ); restMarksName.map((name) => state.schema.marks[name]).forEach((t) => { tr.removeMark(from, to, t); }); dispatch == null ? void 0 : dispatch(tr.addMark(from, to, inlineCodeSchema.type(ctx).create())); return true; } ); withMeta(toggleInlineCodeCommand, { displayName: "Command<toggleInlineCodeCommand>", group: "InlineCode" }); const inlineCodeInputRule = $inputRule((ctx) => { return markRule(/(?:`)([^`]+)(?:`)$/, inlineCodeSchema.type(ctx)); }); withMeta(inlineCodeInputRule, { displayName: "InputRule<inlineCodeInputRule>", group: "InlineCode" }); const inlineCodeKeymap = $useKeymap("inlineCodeKeymap", { ToggleInlineCode: { shortcuts: "Mod-e", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(toggleInlineCodeCommand.key); } } }); withMeta(inlineCodeKeymap.ctx, { displayName: "KeymapCtx<inlineCode>", group: "InlineCode" }); withMeta(inlineCodeKeymap.shortcuts, { displayName: "Keymap<inlineCode>", group: "InlineCode" }); const linkAttr = $markAttr("link"); withMeta(linkAttr, { displayName: "Attr<link>", group: "Link" }); const linkSchema = $markSchema("link", (ctx) => ({ attrs: { href: { validate: "string" }, title: { default: null, validate: "string|null" } }, parseDOM: [ { tag: "a[href]", getAttrs: (dom) => { if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom); return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }; } } ], toDOM: (mark) => ["a", { ...ctx.get(linkAttr.key)(mark), ...mark.attrs }], parseMarkdown: { match: (node) => node.type === "link", runner: (state, node, markType) => { const url = node.url; const title = node.title; state.openMark(markType, { href: url, title }); state.next(node.children); state.closeMark(markType); } }, toMarkdown: { match: (mark) => mark.type.name === "link", runner: (state, mark) => { state.withMark(mark, "link", void 0, { title: mark.attrs.title, url: mark.attrs.href }); } } })); withMeta(linkSchema.mark, { displayName: "MarkSchema<link>", group: "Link" }); const toggleLinkCommand = $command( "ToggleLink", (ctx) => (payload = {}) => toggleMark(linkSchema.type(ctx), payload) ); withMeta(toggleLinkCommand, { displayName: "Command<toggleLinkCommand>", group: "Link" }); const updateLinkCommand = $command( "UpdateLink", (ctx) => (payload = {}) => (state, dispatch) => { if (!dispatch) return false; let node; let pos = -1; const { selection } = state; const { from, to } = selection; state.doc.nodesBetween(from, from === to ? to + 1 : to, (n, p) => { if (linkSchema.type(ctx).isInSet(n.marks)) { node = n; pos = p; return false; } return void 0; }); if (!node) return false; const mark = node.marks.find(({ type }) => type === linkSchema.type(ctx)); if (!mark) return false; const start = pos; const end = pos + node.nodeSize; const { tr } = state; const linkMark = linkSchema.type(ctx).create({ ...mark.attrs, ...payload }); if (!linkMark) return false; dispatch( tr.removeMark(start, end, mark).addMark(start, end, linkMark).setSelection(new TextSelection(tr.selection.$anchor)).scrollIntoView() ); return true; } ); withMeta(updateLinkCommand, { displayName: "Command<updateLinkCommand>", group: "Link" }); const docSchema = $node("doc", () => ({ content: "block+", parseMarkdown: { match: ({ type }) => type === "root", runner: (state, node, type) => { state.injectRoot(node, type); } }, toMarkdown: { match: (node) => node.type.name === "doc", runner: (state, node) => { state.openNode("root"); state.next(node.content); } } })); withMeta(docSchema, { displayName: "NodeSchema<doc>", group: "Doc" }); function visitEmptyLine(ast) { return visit(ast, (node) => { var _a, _b; if (((_a = node.children) == null ? void 0 : _a.length) !== 1) return; const firstChild = (_b = node.children) == null ? void 0 : _b[0]; if (!firstChild || firstChild.type !== "html") return; const { value } = firstChild; if (!["<br />", "<br>", "<br/>"].includes(value)) { return; } node.children.splice(0, 1); }); } const remarkPreserveEmptyLinePlugin = $remark( "remark-preserve-empty-line", () => () => visitEmptyLine ); withMeta(remarkPreserveEmptyLinePlugin.plugin, { displayName: "Remark<remarkPreserveEmptyLine>", group: "Remark" }); withMeta(remarkPreserveEmptyLinePlugin.options, { displayName: "RemarkConfig<remarkPreserveEmptyLine>", group: "Remark" }); const paragraphAttr = $nodeAttr("paragraph"); withMeta(paragraphAttr, { displayName: "Attr<paragraph>", group: "Paragraph" }); const paragraphSchema = $nodeSchema("paragraph", (ctx) => ({ content: "inline*", group: "block", parseDOM: [{ tag: "p" }], toDOM: (node) => ["p", ctx.get(paragraphAttr.key)(node), 0], parseMarkdown: { match: (node) => node.type === "paragraph", runner: (state, node, type) => { state.openNode(type); if (node.children) state.next(node.children); else state.addText(node.value || ""); state.closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "paragraph", runner: (state, node) => { var _a; const view = ctx.get(editorViewCtx); const lastNode = (_a = view.state) == null ? void 0 : _a.doc.lastChild; state.openNode("paragraph"); if ((!node.content || node.content.size === 0) && node !== lastNode && shouldPreserveEmptyLine(ctx)) { state.addNode("html", void 0, "<br />"); } else { serializeText(state, node); } state.closeNode(); } } })); function shouldPreserveEmptyLine(ctx) { let shouldPreserveEmptyLine2 = false; try { ctx.get(remarkPreserveEmptyLinePlugin.id); shouldPreserveEmptyLine2 = true; } catch { shouldPreserveEmptyLine2 = false; } return shouldPreserveEmptyLine2; } withMeta(paragraphSchema.node, { displayName: "NodeSchema<paragraph>", group: "Paragraph" }); withMeta(paragraphSchema.ctx, { displayName: "NodeSchemaCtx<paragraph>", group: "Paragraph" }); const turnIntoTextCommand = $command( "TurnIntoText", (ctx) => () => setBlockType(paragraphSchema.type(ctx)) ); withMeta(turnIntoTextCommand, { displayName: "Command<turnIntoTextCommand>", group: "Paragraph" }); const paragraphKeymap = $useKeymap("paragraphKeymap", { TurnIntoText: { shortcuts: "Mod-Alt-0", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(turnIntoTextCommand.key); } } }); withMeta(paragraphKeymap.ctx, { displayName: "KeymapCtx<paragraph>", group: "Paragraph" }); withMeta(paragraphKeymap.shortcuts, { displayName: "Keymap<paragraph>", group: "Paragraph" }); const headingIndex = Array(6).fill(0).map((_, i) => i + 1); function defaultHeadingIdGenerator(node) { return node.textContent.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-"); } const headingIdGenerator = $ctx( defaultHeadingIdGenerator, "headingIdGenerator" ); withMeta(headingIdGenerator, { displayName: "Ctx<HeadingIdGenerator>", group: "Heading" }); const headingAttr = $nodeAttr("heading"); withMeta(headingAttr, { displayName: "Attr<heading>", group: "Heading" }); const headingSchema = $nodeSchema("heading", (ctx) => { const getId = ctx.get(headingIdGenerator.key); return { content: "inline*", group: "block", defining: true, attrs: { id: { default: "", validate: "string" }, level: { default: 1, validate: "number" } }, parseDOM: headingIndex.map((x) => ({ tag: `h${x}`, getAttrs: (node) => { if (!(node instanceof HTMLElement)) throw expectDomTypeError(node); return { level: x, id: node.id }; } })), toDOM: (node) => { return [ `h${node.attrs.level}`, { ...ctx.get(headingAttr.key)(node), id: node.attrs.id || getId(node) }, 0 ]; }, parseMarkdown: { match: ({ type }) => type === "heading", runner: (state, node, type) => { const depth = node.depth; state.openNode(type, { level: depth }); state.next(node.children); state.closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "heading", runner: (state, node) => { state.openNode("heading", void 0, { depth: node.attrs.level }); serializeText(state, node); state.closeNode(); } } }; }); withMeta(headingSchema.node, { displayName: "NodeSchema<heading>", group: "Heading" }); withMeta(headingSchema.ctx, { displayName: "NodeSchemaCtx<heading>", group: "Heading" }); const wrapInHeadingInputRule = $inputRule((ctx) => { return textblockTypeInputRule( /^(?<hashes>#+)\s$/, headingSchema.type(ctx), (match) => { var _a, _b; const x = ((_b = (_a = match.groups) == null ? void 0 : _a.hashes) == null ? void 0 : _b.length) || 0; const view = ctx.get(editorViewCtx); const { $from } = view.state.selection; const node = $from.node(); if (node.type.name === "heading") { let level = Number(node.attrs.level) + Number(x); if (level > 6) level = 6; return { level }; } return { level: x }; } ); }); withMeta(wrapInHeadingInputRule, { displayName: "InputRule<wrapInHeadingInputRule>", group: "Heading" }); const wrapInHeadingCommand = $command("WrapInHeading", (ctx) => { return (level) => { level ?? (level = 1); if (level < 1) return setBlockType(paragraphSchema.type(ctx)); return setBlockType(headingSchema.type(ctx), { level }); }; }); withMeta(wrapInHeadingCommand, { displayName: "Command<wrapInHeadingCommand>", group: "Heading" }); const downgradeHeadingCommand = $command( "DowngradeHeading", (ctx) => () => (state, dispatch, view) => { const { $from } = state.selection; const node = $from.node(); if (node.type !== headingSchema.type(ctx) || !state.selection.empty || $from.parentOffset !== 0) return false; const level = node.attrs.level - 1; if (!level) return setBlockType(paragraphSchema.type(ctx))(state, dispatch, view); dispatch == null ? void 0 : dispatch( state.tr.setNodeMarkup(state.selection.$from.before(), void 0, { ...node.attrs, level }) ); return true; } ); withMeta(downgradeHeadingCommand, { displayName: "Command<downgradeHeadingCommand>", group: "Heading" }); const headingKeymap = $useKeymap("headingKeymap", { TurnIntoH1: { shortcuts: "Mod-Alt-1", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInHeadingCommand.key, 1); } }, TurnIntoH2: { shortcuts: "Mod-Alt-2", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInHeadingCommand.key, 2); } }, TurnIntoH3: { shortcuts: "Mod-Alt-3", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInHeadingCommand.key, 3); } }, TurnIntoH4: { shortcuts: "Mod-Alt-4", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInHeadingCommand.key, 4); } }, TurnIntoH5: { shortcuts: "Mod-Alt-5", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInHeadingCommand.key, 5); } }, TurnIntoH6: { shortcuts: "Mod-Alt-6", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInHeadingCommand.key, 6); } }, DowngradeHeading: { shortcuts: ["Delete", "Backspace"], command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(downgradeHeadingCommand.key); } } }); withMeta(headingKeymap.ctx, { displayName: "KeymapCtx<heading>", group: "Heading" }); withMeta(headingKeymap.shortcuts, { displayName: "Keymap<heading>", group: "Heading" }); const blockquoteAttr = $nodeAttr("blockquote"); withMeta(blockquoteAttr, { displayName: "Attr<blockquote>", group: "Blockquote" }); const blockquoteSchema = $nodeSchema( "blockquote", (ctx) => ({ content: "block+", group: "block", defining: true, parseDOM: [{ tag: "blockquote" }], toDOM: (node) => ["blockquote", ctx.get(blockquoteAttr.key)(node), 0], parseMarkdown: { match: ({ type }) => type === "blockquote", runner: (state, node, type) => { state.openNode(type).next(node.children).closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "blockquote", runner: (state, node) => { state.openNode("blockquote").next(node.content).closeNode(); } } }) ); withMeta(blockquoteSchema.node, { displayName: "NodeSchema<blockquote>", group: "Blockquote" }); withMeta(blockquoteSchema.ctx, { displayName: "NodeSchemaCtx<blockquote>", group: "Blockquote" }); const wrapInBlockquoteInputRule = $inputRule( (ctx) => wrappingInputRule(/^\s*>\s$/, blockquoteSchema.type(ctx)) ); withMeta(wrapInBlockquoteInputRule, { displayName: "InputRule<wrapInBlockquoteInputRule>", group: "Blockquote" }); const wrapInBlockquoteCommand = $command( "WrapInBlockquote", (ctx) => () => wrapIn(blockquoteSchema.type(ctx)) ); withMeta(wrapInBlockquoteCommand, { displayName: "Command<wrapInBlockquoteCommand>", group: "Blockquote" }); const blockquoteKeymap = $useKeymap("blockquoteKeymap", { WrapInBlockquote: { shortcuts: "Mod-Shift-b", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInBlockquoteCommand.key); } } }); withMeta(blockquoteKeymap.ctx, { displayName: "KeymapCtx<blockquote>", group: "Blockquote" }); withMeta(blockquoteKeymap.shortcuts, { displayName: "Keymap<blockquote>", group: "Blockquote" }); const codeBlockAttr = $nodeAttr("codeBlock", () => ({ pre: {}, code: {} })); withMeta(codeBlockAttr, { displayName: "Attr<codeBlock>", group: "CodeBlock" }); const codeBlockSchema = $nodeSchema("code_block", (ctx) => { return { content: "text*", group: "block", marks: "", defining: true, code: true, attrs: { language: { default: "", validate: "string" } }, parseDOM: [ { tag: "pre", preserveWhitespace: "full", getAttrs: (dom) => { if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom); return { language: dom.dataset.language }; } } ], toDOM: (node) => { const attr = ctx.get(codeBlockAttr.key)(node); return [ "pre", { ...attr.pre, "data-language": node.attrs.language }, ["code", attr.code, 0] ]; }, parseMarkdown: { match: ({ type }) => type === "code", runner: (state, node, type) => { const language = node.lang; const value = node.value; state.openNode(type, { language }); if (value) state.addText(value); state.closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "code_block", runner: (state, node) => { var _a; state.addNode("code", void 0, ((_a = node.content.firstChild) == null ? void 0 : _a.text) || "", { lang: node.attrs.language }); } } }; }); withMeta(codeBlockSchema.node, { displayName: "NodeSchema<codeBlock>", group: "CodeBlock" }); withMeta(codeBlockSchema.ctx, { displayName: "NodeSchemaCtx<codeBlock>", group: "CodeBlock" }); const createCodeBlockInputRule = $inputRule( (ctx) => textblockTypeInputRule( /^```(?<language>[a-z]*)?[\s\n]$/, codeBlockSchema.type(ctx), (match) => { var _a; return { language: ((_a = match.groups) == null ? void 0 : _a.language) ?? "" }; } ) ); withMeta(createCodeBlockInputRule, { displayName: "InputRule<createCodeBlockInputRule>", group: "CodeBlock" }); const createCodeBlockCommand = $command( "CreateCodeBlock", (ctx) => (language = "") => setBlockType(codeBlockSchema.type(ctx), { language }) ); withMeta(createCodeBlockCommand, { displayName: "Command<createCodeBlockCommand>", group: "CodeBlock" }); const updateCodeBlockLanguageCommand = $command( "UpdateCodeBlockLanguage", () => ({ pos, language } = { pos: -1, language: "" }) => (state, dispatch) => { if (pos >= 0) { dispatch == null ? void 0 : dispatch(state.tr.setNodeAttribute(pos, "language", language)); return true; } return false; } ); withMeta(updateCodeBlockLanguageCommand, { displayName: "Command<updateCodeBlockLanguageCommand>", group: "CodeBlock" }); const codeBlockKeymap = $useKeymap("codeBlockKeymap", { CreateCodeBlock: { shortcuts: "Mod-Alt-c", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(createCodeBlockCommand.key); } } }); withMeta(codeBlockKeymap.ctx, { displayName: "KeymapCtx<codeBlock>", group: "CodeBlock" }); withMeta(codeBlockKeymap.shortcuts, { displayName: "Keymap<codeBlock>", group: "CodeBlock" }); const imageAttr = $nodeAttr("image"); withMeta(imageAttr, { displayName: "Attr<image>", group: "Image" }); const imageSchema = $nodeSchema("image", (ctx) => { return { inline: true, group: "inline", selectable: true, draggable: true, marks: "", atom: true, defining: true, isolating: true, attrs: { src: { default: "", validate: "string" }, alt: { default: "", validate: "string" }, title: { default: "", validate: "string" } }, parseDOM: [ { tag: "img[src]", getAttrs: (dom) => { if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom); return { src: dom.getAttribute("src") || "", alt: dom.getAttribute("alt") || "", title: dom.getAttribute("title") || dom.getAttribute("alt") || "" }; } } ], toDOM: (node) => { return ["img", { ...ctx.get(imageAttr.key)(node), ...node.attrs }]; }, parseMarkdown: { match: ({ type }) => type === "image", runner: (state, node, type) => { const url = node.url; const alt = node.alt; const title = node.title; state.addNode(type, { src: url, alt, title }); } }, toMarkdown: { match: (node) => node.type.name === "image", runner: (state, node) => { state.addNode("image", void 0, void 0, { title: node.attrs.title, url: node.attrs.src, alt: node.attrs.alt }); } } }; }); withMeta(imageSchema.node, { displayName: "NodeSchema<image>", group: "Image" }); withMeta(imageSchema.ctx, { displayName: "NodeSchemaCtx<image>", group: "Image" }); const insertImageCommand = $command( "InsertImage", (ctx) => (payload = {}) => (state, dispatch) => { if (!dispatch) return true; const { src = "", alt = "", title = "" } = payload; const node = imageSchema.type(ctx).create({ src, alt, title }); if (!node) return true; dispatch(state.tr.replaceSelectionWith(node).scrollIntoView()); return true; } ); withMeta(insertImageCommand, { displayName: "Command<insertImageCommand>", group: "Image" }); const updateImageCommand = $command( "UpdateImage", (ctx) => (payload = {}) => (state, dispatch) => { const nodeWithPos = findSelectedNodeOfType( state.selection, imageSchema.type(ctx) ); if (!nodeWithPos) return false; const { node, pos } = nodeWithPos; const newAttrs = { ...node.attrs }; const { src, alt, title } = payload; if (src !== void 0) newAttrs.src = src; if (alt !== void 0) newAttrs.alt = alt; if (title !== void 0) newAttrs.title = title; dispatch == null ? void 0 : dispatch( state.tr.setNodeMarkup(pos, void 0, newAttrs).scrollIntoView() ); return true; } ); withMeta(updateImageCommand, { displayName: "Command<updateImageCommand>", group: "Image" }); const insertImageInputRule = $inputRule( (ctx) => new InputRule( /!\[(?<alt>.*?)]\((?<filename>.*?)\s*(?="|\))"?(?<title>[^"]+)?"?\)/, (state, match, start, end) => { const [matched, alt, src = "", title] = match; if (matched) return state.tr.replaceWith( start, end, imageSchema.type(ctx).create({ src, alt, title }) ); return null; } ) ); withMeta(insertImageInputRule, { displayName: "InputRule<insertImageInputRule>", group: "Image" }); const hardbreakAttr = $nodeAttr("hardbreak", (node) => { return { "data-type": "hardbreak", "data-is-inline": node.attrs.isInline }; }); withMeta(hardbreakAttr, { displayName: "Attr<hardbreak>", group: "Hardbreak" }); const hardbreakSchema = $nodeSchema("hardbreak", (ctx) => ({ inline: true, group: "inline", attrs: { isInline: { default: false, validate: "boolean" } }, selectable: false, parseDOM: [ { tag: "br" }, { tag: 'span[data-type="hardbreak"]', getAttrs: () => ({ isInline: true }) } ], toDOM: (node) => node.attrs.isInline ? ["span", ctx.get(hardbreakAttr.key)(node), " "] : ["br", ctx.get(hardbreakAttr.key)(node)], parseMarkdown: { match: ({ type }) => type === "break", runner: (state, node, type) => { var _a; state.addNode(type, { isInline: Boolean( (_a = node.data) == null ? void 0 : _a.isInline ) }); } }, leafText: () => "\n", toMarkdown: { match: (node) => node.type.name === "hardbreak", runner: (state, node) => { if (node.attrs.isInline) state.addNode("text", void 0, "\n"); else state.addNode("break"); } } })); withMeta(hardbreakSchema.node, { displayName: "NodeSchema<hardbreak>", group: "Hardbreak" }); withMeta(hardbreakSchema.ctx, { displayName: "NodeSchemaCtx<hardbreak>", group: "Hardbreak" }); const insertHardbreakCommand = $command( "InsertHardbreak", (ctx) => () => (state, dispatch) => { var _a; const { selection, tr } = state; if (!(selection instanceof TextSelection)) return false; if (selection.empty) { const node = selection.$from.node(); if (node.childCount > 0 && ((_a = node.lastChild) == null ? void 0 : _a.type.name) === "hardbreak") { dispatch == null ? void 0 : dispatch( tr.replaceRangeWith( selection.to - 1, selection.to, state.schema.node("paragraph") ).setSelection(Selection.near(tr.doc.resolve(selection.to))).scrollIntoView() ); return true; } } dispatch == null ? void 0 : dispatch( tr.setMeta("hardbreak", true).replaceSelectionWith(hardbreakSchema.type(ctx).create()).scrollIntoView() ); return true; } ); withMeta(insertHardbreakCommand, { displayName: "Command<insertHardbreakCommand>", group: "Hardbreak" }); const hardbreakKeymap = $useKeymap("hardbreakKeymap", { InsertHardbreak: { shortcuts: "Shift-Enter", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(insertHardbreakCommand.key); } } }); withMeta(hardbreakKeymap.ctx, { displayName: "KeymapCtx<hardbreak>", group: "Hardbreak" }); withMeta(hardbreakKeymap.shortcuts, { displayName: "Keymap<hardbreak>", group: "Hardbreak" }); const hrAttr = $nodeAttr("hr"); withMeta(hrAttr, { displayName: "Attr<hr>", group: "Hr" }); const hrSchema = $nodeSchema("hr", (ctx) => ({ group: "block", parseDOM: [{ tag: "hr" }], toDOM: (node) => ["hr", ctx.get(hrAttr.key)(node)], parseMarkdown: { match: ({ type }) => type === "thematicBreak", runner: (state, _, type) => { state.addNode(type); } }, toMarkdown: { match: (node) => node.type.name === "hr", runner: (state) => { state.addNode("thematicBreak"); } } })); withMeta(hrSchema.node, { displayName: "NodeSchema<hr>", group: "Hr" }); withMeta(hrSchema.ctx, { displayName: "NodeSchemaCtx<hr>", group: "Hr" }); const insertHrInputRule = $inputRule( (ctx) => new InputRule(/^(?:---|___\s|\*\*\*\s)$/, (state, match, start, end) => { const { tr } = state; if (match[0]) tr.replaceWith(start - 1, end, hrSchema.type(ctx).create()); return tr; }) ); withMeta(insertHrInputRule, { displayName: "InputRule<insertHrInputRule>", group: "Hr" }); const insertHrCommand = $command( "InsertHr", (ctx) => () => (state, dispatch) => { if (!dispatch) return true; const paragraph = paragraphSchema.node.type(ctx).create(); const { tr, selection } = state; const { from } = selection; const node = hrSchema.type(ctx).create(); if (!node) return true; const _tr = tr.replaceSelectionWith(node).insert(from, paragraph); const sel = Selection.findFrom(_tr.doc.resolve(from), 1, true); if (!sel) return true; dispatch(_tr.setSelection(sel).scrollIntoView()); return true; } ); withMeta(insertHrCommand, { displayName: "Command<insertHrCommand>", group: "Hr" }); const bulletListAttr = $nodeAttr("bulletList"); withMeta(bulletListAttr, { displayName: "Attr<bulletList>", group: "BulletList" }); const bulletListSchema = $nodeSchema("bullet_list", (ctx) => { return { content: "listItem+", group: "block", attrs: { spread: { default: false, validate: "boolean" } }, parseDOM: [ { tag: "ul", getAttrs: (dom) => { if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom); return { spread: dom.dataset.spread === "true" }; } } ], toDOM: (node) => { return [ "ul", { ...ctx.get(bulletListAttr.key)(node), "data-spread": node.attrs.spread }, 0 ]; }, parseMarkdown: { match: ({ type, ordered }) => type === "list" && !ordered, runner: (state, node, type) => { const spread = node.spread != null ? `${node.spread}` : "false"; state.openNode(type, { spread }).next(node.children).closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "bullet_list", runner: (state, node) => { state.openNode("list", void 0, { ordered: false, spread: node.attrs.spread }).next(node.content).closeNode(); } } }; }); withMeta(bulletListSchema.node, { displayName: "NodeSchema<bulletList>", group: "BulletList" }); withMeta(bulletListSchema.ctx, { displayName: "NodeSchemaCtx<bulletList>", group: "BulletList" }); const wrapInBulletListInputRule = $inputRule( (ctx) => wrappingInputRule(/^\s*([-+*])\s$/, bulletListSchema.type(ctx)) ); withMeta(wrapInBulletListInputRule, { displayName: "InputRule<wrapInBulletListInputRule>", group: "BulletList" }); const wrapInBulletListCommand = $command( "WrapInBulletList", (ctx) => () => wrapIn(bulletListSchema.type(ctx)) ); withMeta(wrapInBulletListCommand, { displayName: "Command<wrapInBulletListCommand>", group: "BulletList" }); const bulletListKeymap = $useKeymap("bulletListKeymap", { WrapInBulletList: { shortcuts: "Mod-Alt-8", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInBulletListCommand.key); } } }); withMeta(bulletListKeymap.ctx, { displayName: "KeymapCtx<bulletListKeymap>", group: "BulletList" }); withMeta(bulletListKeymap.shortcuts, { displayName: "Keymap<bulletListKeymap>", group: "BulletList" }); const orderedListAttr = $nodeAttr("orderedList"); withMeta(orderedListAttr, { displayName: "Attr<orderedList>", group: "OrderedList" }); const orderedListSchema = $nodeSchema("ordered_list", (ctx) => ({ content: "listItem+", group: "block", attrs: { order: { default: 1, validate: "number" }, spread: { default: false, validate: "boolean" } }, parseDOM: [ { tag: "ol", getAttrs: (dom) => { if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom); return { spread: dom.dataset.spread, order: dom.hasAttribute("start") ? Number(dom.getAttribute("start")) : 1 }; } } ], toDOM: (node) => [ "ol", { ...ctx.get(orderedListAttr.key)(node), ...node.attrs.order === 1 ? {} : node.attrs.order, "data-spread": node.attrs.spread }, 0 ], parseMarkdown: { match: ({ type, ordered }) => type === "list" && !!ordered, runner: (state, node, type) => { const spread = node.spread != null ? `${node.spread}` : "true"; state.openNode(type, { spread }).next(node.children).closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "ordered_list", runner: (state, node) => { state.openNode("list", void 0, { ordered: true, start: 1, spread: node.attrs.spread === "true" }); state.next(node.content); state.closeNode(); } } })); withMeta(orderedListSchema.node, { displayName: "NodeSchema<orderedList>", group: "OrderedList" }); withMeta(orderedListSchema.ctx, { displayName: "NodeSchemaCtx<orderedList>", group: "OrderedList" }); const wrapInOrderedListInputRule = $inputRule( (ctx) => wrappingInputRule( /^\s*(\d+)\.\s$/, orderedListSchema.type(ctx), (match) => ({ order: Number(match[1]) }), (match, node) => node.childCount + node.attrs.order === Number(match[1]) ) ); withMeta(wrapInOrderedListInputRule, { displayName: "InputRule<wrapInOrderedListInputRule>", group: "OrderedList" }); const wrapInOrderedListCommand = $command( "WrapInOrderedList", (ctx) => () => wrapIn(orderedListSchema.type(ctx)) ); withMeta(wrapInOrderedListCommand, { displayName: "Command<wrapInOrderedListCommand>", group: "OrderedList" }); const orderedListKeymap = $useKeymap("orderedListKeymap", { WrapInOrderedList: { shortcuts: "Mod-Alt-7", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(wrapInOrderedListCommand.key); } } }); withMeta(orderedListKeymap.ctx, { displayName: "KeymapCtx<orderedList>", group: "OrderedList" }); withMeta(orderedListKeymap.shortcuts, { displayName: "Keymap<orderedList>", group: "OrderedList" }); const listItemAttr = $nodeAttr("listItem"); withMeta(listItemAttr, { displayName: "Attr<listItem>", group: "ListItem" }); const listItemSchema = $nodeSchema("list_item", (ctx) => ({ group: "listItem", content: "paragraph block*", attrs: { label: { default: "•", validate: "string" }, listType: { default: "bullet", validate: "string" }, spread: { default: true, validate: "boolean" } }, defining: true, parseDOM: [ { tag: "li", getAttrs: (dom) => { if (!(dom instanceof HTMLElement)) throw expectDomTypeError(dom); return { label: dom.dataset.label, listType: dom.dataset.listType, spread: dom.dataset.spread === "true" }; } } ], toDOM: (node) => [ "li", { ...ctx.get(listItemAttr.key)(node), "data-label": node.attrs.label, "data-list-type": node.attrs.listType, "data-spread": node.attrs.spread }, 0 ], parseMarkdown: { match: ({ type }) => type === "listItem", runner: (state, node, type) => { const label = node.label != null ? `${node.label}.` : "•"; const listType = node.label != null ? "ordered" : "bullet"; const spread = node.spread != null ? `${node.spread}` : "true"; state.openNode(type, { label, listType, spread }); state.next(node.children); state.closeNode(); } }, toMarkdown: { match: (node) => node.type.name === "list_item", runner: (state, node) => { state.openNode("listItem", void 0, { spread: node.attrs.spread }); state.next(node.content); state.closeNode(); } } })); withMeta(listItemSchema.node, { displayName: "NodeSchema<listItem>", group: "ListItem" }); withMeta(listItemSchema.ctx, { displayName: "NodeSchemaCtx<listItem>", group: "ListItem" }); const sinkListItemCommand = $command( "SinkListItem", (ctx) => () => sinkListItem(listItemSchema.type(ctx)) ); withMeta(sinkListItemCommand, { displayName: "Command<sinkListItemCommand>", group: "ListItem" }); const liftListItemCommand = $command( "LiftListItem", (ctx) => () => liftListItem(listItemSchema.type(ctx)) ); withMeta(liftListItemCommand, { displayName: "Command<liftListItemCommand>", group: "ListItem" }); const splitListItemCommand = $command( "SplitListItem", (ctx) => () => splitListItem(listItemSchema.type(ctx)) ); withMeta(splitListItemCommand, { displayName: "Command<splitListItemCommand>", group: "ListItem" }); function liftFirstListItem(ctx) { return (state, dispatch, view) => { const { selection } = state; if (!(selection instanceof TextSelection)) return false; const { empty, $from } = selection; if (!empty || $from.parentOffset !== 0) return false; const parentItem = $from.node(-1); if (parentItem.type !== listItemSchema.type(ctx) || parentItem.firstChild !== $from.node()) return false; const list = $from.node(-2); if (list.childCount > 1) return false; return liftListItem(listItemSchema.type(ctx))(state, dispatch, view); }; } const liftFirstListItemCommand = $command( "LiftFirstListItem", (ctx) => () => liftFirstListItem(ctx) ); withMeta(liftFirstListItemCommand, { displayName: "Command<liftFirstListItemCommand>", group: "ListItem" }); const listItemKeymap = $useKeymap("listItemKeymap", { NextListItem: { shortcuts: "Enter", command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(splitListItemCommand.key); } }, SinkListItem: { shortcuts: ["Tab", "Mod-]"], command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(sinkListItemCommand.key); } }, LiftListItem: { shortcuts: ["Shift-Tab", "Mod-["], command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(liftListItemCommand.key); } }, LiftFirstListItem: { shortcuts: ["Backspace", "Delete"], command: (ctx) => { const commands2 = ctx.get(commandsCtx); return () => commands2.call(liftFirstListItemCommand.key); } } }); withMeta(listItemKeymap.ctx, { displayName: "KeymapCtx<listItem>", group: "ListItem" }); withMeta(listItemKeymap.shortcuts, { displayName: "Keymap<listItem>", group: "ListItem" }); const textSchema = $node("text", () => ({ group: "inline", parseMarkdown: { match: ({ type }) => type === "text", runner: (state, node) => { state.addText(node.value); } }, toMarkdown: { match: (node) => node.type.name === "text", runner: (state, node) => { state.addNode("text", void 0, node.text); } } })); withMeta(textSchema, { displayName: "NodeSchema<text>", group: "Text" }); const htmlAttr = $nodeAttr("html"); withMeta(htmlAttr, { displayName: "Attr<html>", group: "Html" }); const htmlSchema = $nodeSchema("html", (ctx) => { return { atom: true, group: "inline", inline: true, attrs: { value: { default: "", validate: "string" } }, toDOM: (node) => { const span = document.createElement("span"); const attr = { ...ctx.get(htmlAttr.key)(node), "data-value": node.attrs.value, "data-type": "html" }; span.textContent = node.attrs.value; return ["span", attr, node.attrs.value]; }, parseDOM: [ { tag: 'span[data-type="html"]', getAttrs: (dom) => { return { value: dom.dataset.value ?? "" }; } } ], parseMarkdown: { match: ({ type }) => Boolean(type === "html"), runner: (state, node, type) => { state.addNode(type, { value: node.value }); } }, toMarkdown: { match: (node) => node.type.name === "html", runner: (state, node) => { state.addNode("html", void 0, node.attrs.value); } } }; }); withMeta(htmlSchema.node, { displayName: "NodeSchema<html>", group: "Html" }); withMeta(htmlSchema.ctx, { displayName: "NodeSchemaCtx<html>", group: "Html" }); const schema = [ docSchema, paragraphAttr, paragraphSchema, headingIdGenerator, headingAttr, headingSchema, hardbreakAttr, hardbreakSchema, blockquoteAttr, blockquoteSchema, codeBlockAttr, codeBlockSchema, hrAttr, hrSchema, imageAttr, imageSchema, bulletListAttr, bulletListSchema, orderedListAttr, orderedListSchema, listItemAttr, listItemSchema, emphasisAttr, emphasisSchema, strongAttr, strongSchema, inlineCodeAttr, inlineCodeSchema, linkAttr, linkSchema, htmlAttr, htmlSchema, textSchema ].flat(); const inputRules = [ wrapInBlockquoteInputRule, wrapInBulletListInputRule, wrapInOrderedListInputRule, createCodeBlockInputRule, insertHrInputRule, wrapInHeadingInputRule ].flat(); const markInputRules = [ emphasisStarInputRule, emphasisUnderscoreInputRule, inlineCodeInputRule, strongInputRule ]; const commands = [ turnIntoTextCommand, wrapInBlockquoteCommand, wrapInHeadingCommand, downgradeHeadingCommand, createCodeBlockCommand, insertHardbreakCommand, insertHrCommand, insertImageCommand, updateImageCommand, wrapInOrderedListCommand, wrapInBulletListCommand, sinkListItemCommand, splitListItemCommand, liftListItemCommand, liftFirstListItemCommand, toggleEmphasisCommand, toggleInlineCodeCommand, toggleStrongCommand, toggleLinkCommand, updateLinkCommand ]; const keymap = [ blockquoteKeymap, codeBlockKeymap, hardbreakKeymap, headingKeymap, listItemKeymap, orderedListKeymap, bulletListKeymap, paragraphKeymap, emphasisKeymap, inlineCodeKeymap, strongKeymap ].flat(); const remarkAddOrderInListPlugin = $remark( "remarkAddOrderInList", () => () => (tree) => { visit(tree, "list", (node) => { if (node.ordered) { const start = node.start ?? 1; node.children.forEach((child, index) => { child.label = index + start; }); } }); } ); withMeta(remarkAddOrderInListPlugin.plugin, { displayName: "Remark<remarkAddOrderInListPlugin>", group: "Remark" }); withMeta(remarkAddOrderInListPlugin.options, { displayName: "RemarkConfig<remarkAddOrderInListPlugin>", group: "Remark" }); const remarkLineBreak = $remark( "remarkLineBreak", () => () => (tree) => { const find = /[\t ]*(?:\r?\n|\r)/g; visit( tree, "text", (node, index, parent) => { if (!node.value || typeof node.value !== "string") return; const result = []; let start = 0; find.lastIndex = 0; let match = find.exec(node.value); while (match) { const position = match.index; if (start !== position) result.push({ type: "text", value: node.value.slice(start, position) }); result.push({ type: "break", data: { isInline: true } }); start = position + match[0].length; match = find.exec(node.value); } const hasResultAndIndex = result.length > 0 && parent && typeof index === "number"; if (!hasResultAndIndex) return; if (start < node.value.length) result.push({ type: "text", value: node.value.slice(start) }); parent.children.splice(index, 1, ...result); return index + result.length; } ); } ); withMeta(remarkLineBreak.plugin, { displayName: "Remark<remarkLineBreak>", group: "Remark" }); withMeta(remarkLineBreak.options, { displayName: "RemarkConfig<remarkLineBreak>", group: "Remark" }); const remarkInlineLinkPlugin = $remark( "remarkInlineLink", () => remarkInlineLinks ); withMeta(remarkInlineLinkPlugin.plugin, { displayName: "Remark<remarkInlineLinkPlugin>", group: "Remark" }); withMeta(remarkInlineLinkPlugin.options, { displayName: "RemarkConfig<remarkInlineLinkPlugin>", group: "Remark" }); const isParent = (node) => !!node.children; const isHTML = (node) => node.type === "html"; function flatMapWithDepth(ast, fn) { return transform(ast, 0, null)[0]; function transform(node, index, parent) { if (isParent(node)) { const out = []; for (let i = 0, n = node.children.length; i < n; i++) { const nthChild = node.children[i]; if (nthChild) { const xs = transform(nthChild, i, node); if (xs) { for (let j = 0, m = xs.length; j < m; j++) { const item = xs[j]; if (item) out.push(item); } } } } node.children = out; } return fn(node, index, parent); } } const remarkHtmlTransformer = $remark( "remarkHTMLTransformer", () => () => (tree) => { flatMapWithDepth(tree, (node, _index, parent) => {