🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@milkdown/transformer

Package Overview
Dependencies
Maintainers
1
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@milkdown/transformer - npm Package Compare versions

Comparing version
7.21.2
to
7.21.3
+44
-50
lib/index.js

@@ -196,3 +196,3 @@ import { createNodeInParserFail, parserMatchError, serializerMatchError, stackOverFlow } from "@milkdown/exception";

var SerializerState = class extends Stack {
#marks;
#openMarks;
static {

@@ -209,3 +209,3 @@ this.create = (schema, remark) => {

super();
this.#marks = Mark.none;
this.#openMarks = [];
this.#matchTarget = (node) => {

@@ -227,29 +227,26 @@ const result = Object.values({

};
this.#runNode = (node) => {
const { marks } = node;
this.#orderMarks = (marks) => {
const getPriority = (x) => x.type.spec.priority ?? 50;
if ([...marks].sort((a, b) => getPriority(a) - getPriority(b)).every((mark) => !this.#runProseMark(mark, node))) this.#runProseNode(node);
marks.forEach((mark) => this.#closeMark(mark));
const rest = [...marks].sort((a, b) => getPriority(a) - getPriority(b));
const continuing = [];
this.#openMarks.forEach(({ mark }) => {
const index = rest.findIndex((x) => x.eq(mark));
if (index >= 0) continuing.push(...rest.splice(index, 1));
});
return continuing.concat(rest);
};
this.#searchType = (child, type) => {
if (child.type === type) return child;
if (child.children?.length !== 1) return child;
const searchNode = (node) => {
if (node.type === type) return node.value != null ? null : node;
if (node.children?.length !== 1) return null;
const [firstChild] = node.children;
if (!firstChild) return null;
return searchNode(firstChild);
};
const target = searchNode(child);
if (!target) return child;
const tmp = target.children ? [...target.children] : void 0;
const node = {
...child,
children: tmp
};
node.children = tmp;
target.children = [node];
return target;
this.#closeEndedMarks = (next) => {
const nextMarks = next?.marks;
let keep = 0;
while (keep < this.#openMarks.length) {
const { mark, spanning } = this.#openMarks[keep];
if (spanning && nextMarks?.some((x) => x.eq(mark))) keep++;
else break;
}
for (let i = this.#openMarks.length - 1; i >= keep; i--) this.#closeMark(this.#openMarks[i].mark);
};
this.#runNode = (node, next) => {
if (this.#orderMarks(node.marks).every((mark) => !this.#runProseMark(mark, node))) this.#runProseNode(node);
this.#closeEndedMarks(next);
};
this.#maybeMergeChildren = (node) => {

@@ -262,3 +259,2 @@ const { children } = node;

if (last && last.isMark && child.isMark) {
child = this.#searchType(child, last.type);
const { children: currChildren, ...currRest } = child;

@@ -295,18 +291,6 @@ const { children: prevChildren, ...prevRest } = last;

const children = element.children;
let first = -1;
let last = -1;
const findIndex = (node) => {
if (!node) return;
node.forEach((child, index) => {
if (child.type === "text" && child.value) {
if (first < 0) first = index;
last = index;
}
});
};
if (children) {
findIndex(children);
const lastChild = children?.[last];
const firstChild = children?.[first];
if (lastChild && lastChild.value.endsWith(" ")) {
const firstChild = children[0];
const lastChild = children.at(-1);
if (lastChild && lastChild.type === "text" && lastChild.value.endsWith(" ")) {
const text = lastChild.value;

@@ -317,3 +301,3 @@ const trimmed = text.trimEnd();

}
if (firstChild && firstChild.value.startsWith(" ")) {
if (firstChild && firstChild.type === "text" && firstChild.value.startsWith(" ")) {
const text = firstChild.value;

@@ -351,4 +335,7 @@ const trimmed = text.trimStart();

this.#openMark = (mark, type, value, props) => {
if (mark.isInSet(this.#marks)) return this;
this.#marks = mark.addToSet(this.#marks);
if (this.#openMarks.some((x) => x.mark.eq(mark))) return this;
this.#openMarks.push({
mark,
spanning: value == null
});
return this.openNode(type, value, {

@@ -360,4 +347,9 @@ ...props,

this.#closeMark = (mark) => {
if (!mark.isInSet(this.#marks)) return;
this.#marks = mark.type.removeFromSet(this.#marks);
let index = -1;
for (let i = this.#openMarks.length - 1; i >= 0; i--) if (this.#openMarks[i].mark.eq(mark)) {
index = i;
break;
}
if (index < 0) return;
this.#openMarks.splice(index, 1);
this.#closeNodeAndPush(true);

@@ -382,4 +374,4 @@ };

if (isFragment(nodes)) {
nodes.forEach((node) => {
this.#runNode(node);
nodes.forEach((node, _offset, index) => {
this.#runNode(node, nodes.maybeChild(index + 1) ?? void 0);
});

@@ -393,2 +385,3 @@ return this;

this.run = (tree) => {
this.#openMarks = [];
this.next(tree);

@@ -402,4 +395,5 @@ return this;

#runProseMark;
#orderMarks;
#closeEndedMarks;
#runNode;
#searchType;
#maybeMergeChildren;

@@ -406,0 +400,0 @@ #createMarkdownNode;

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","names":["#hasText","#matchTarget","#marks","#addNodeAndPush","#closeNodeAndPush","#maybeMerge","#runNode","#matchTarget","#runProseMark","#runProseNode","#closeMark","#searchType","#maybeMergeChildren","#addNodeAndPush","#moveSpaces","#closeNodeAndPush","#createMarkdownNode","#marks","#openMark","#runNode"],"sources":["../src/utility/stack.ts","../src/parser/stack-element.ts","../src/parser/state.ts","../src/serializer/stack-element.ts","../src/serializer/state.ts"],"sourcesContent":["import { stackOverFlow } from '@milkdown/exception'\n\n/// The element of the stack, which holds an array of nodes.\nexport abstract class StackElement<Node> {\n /// A method that can `push` a node into the element.\n abstract push(node: Node, ...rest: Node[]): void\n}\n\n/// The stack that is used to store the elements.\n///\n/// > Generally, you don't need to use this class directly.\n///\n/// When using the stack, users can call `stack.open` to push a new element into the stack.\n/// And use `stack.push` to push a node into the top element.\n/// Then use `stack.close` to close the top element and pop it.\n///\n/// For example: `stack.open(A).push(B).push(C).close()` will generate a structure like `A(B, C)`.\nexport class Stack<Node, Element extends StackElement<Node>> {\n protected elements: Element[] = []\n\n /// Get the size of the stack.\n size = (): number => {\n return this.elements.length\n }\n\n /// Get the top element of the stack.\n top = (): Element | undefined => {\n return this.elements.at(-1)\n }\n\n /// Push a node into the top element.\n push = (node: Node): void => {\n this.top()?.push(node)\n }\n\n /// Push a new element.\n open = (node: Element): void => {\n this.elements.push(node)\n }\n\n /// Close the top element and pop it.\n close = (): Element => {\n const el = this.elements.pop()\n if (!el) throw stackOverFlow()\n\n return el\n }\n}\n","import type { Attrs, Node, NodeType } from '@milkdown/prose/model'\n\nimport { StackElement } from '../utility'\n\nexport class ParserStackElement extends StackElement<Node> {\n constructor(\n public type: NodeType,\n public content: Node[],\n public attrs?: Attrs\n ) {\n super()\n }\n\n push(node: Node, ...rest: Node[]) {\n this.content.push(node, ...rest)\n }\n\n pop(): Node | undefined {\n return this.content.pop()\n }\n\n static create(type: NodeType, content: Node[], attrs?: Attrs) {\n return new ParserStackElement(type, content, attrs)\n }\n}\n","import type {\n Attrs,\n MarkType,\n Node,\n NodeType,\n Schema,\n} from '@milkdown/prose/model'\n\nimport {\n createNodeInParserFail,\n parserMatchError,\n stackOverFlow,\n} from '@milkdown/exception'\nimport { Mark } from '@milkdown/prose/model'\n\nimport type {\n MarkSchema,\n MarkdownNode,\n NodeSchema,\n RemarkParser,\n} from '../utility'\nimport type { Parser } from './types'\n\nimport { Stack } from '../utility'\nimport { ParserStackElement } from './stack-element'\n\n/// A state machine for parser. Transform remark AST into prosemirror state.\nexport class ParserState extends Stack<Node, ParserStackElement> {\n /// The schema in current editor.\n readonly schema: Schema\n\n /// @internal\n #marks: readonly Mark[] = Mark.none\n\n /// Create a parser from schema and remark instance.\n ///\n /// ```typescript\n /// const parser = ParserState.create(schema, remark)\n /// const prosemirrorNode = parser(SomeMarkdownText)\n /// ```\n static create = (schema: Schema, remark: RemarkParser): Parser => {\n const state = new this(schema)\n return (text) => {\n state.run(remark, text)\n return state.toDoc()\n }\n }\n\n /// @internal\n constructor(schema: Schema) {\n super()\n this.schema = schema\n }\n\n /// @internal\n #hasText = (node: Node): node is Node & { text: string } => node.isText\n\n /// @internal\n #maybeMerge = (a: Node, b: Node): Node | undefined => {\n if (this.#hasText(a) && this.#hasText(b) && Mark.sameSet(a.marks, b.marks))\n return this.schema.text(a.text + b.text, a.marks)\n\n return undefined\n }\n\n /// @internal\n #matchTarget = (node: MarkdownNode): NodeType | MarkType => {\n const result = Object.values({\n ...this.schema.nodes,\n ...this.schema.marks,\n }).find((x): x is NodeType | MarkType => {\n const spec = x.spec as NodeSchema | MarkSchema\n return spec.parseMarkdown.match(node)\n })\n\n if (!result) throw parserMatchError(node)\n\n return result\n }\n\n /// @internal\n #runNode = (node: MarkdownNode) => {\n const type = this.#matchTarget(node)\n const spec = type.spec as NodeSchema | MarkSchema\n\n spec.parseMarkdown.runner(this, node, type as NodeType & MarkType)\n }\n\n /// Inject root node for prosemirror state.\n injectRoot = (node: MarkdownNode, nodeType: NodeType, attrs?: Attrs) => {\n this.openNode(nodeType, attrs)\n this.next(node.children)\n\n return this\n }\n\n /// Open a new node, the next operations will\n /// add nodes into that new node until `closeNode` is called.\n openNode = (nodeType: NodeType, attrs?: Attrs) => {\n this.open(ParserStackElement.create(nodeType, [], attrs))\n return this\n }\n\n /// @internal\n #closeNodeAndPush = (): Node => {\n this.#marks = Mark.none\n const element = this.close()\n\n return this.#addNodeAndPush(element.type, element.attrs, element.content)\n }\n\n /// Close the current node and push it into the parent node.\n closeNode = () => {\n try {\n this.#closeNodeAndPush()\n } catch (e) {\n console.error(e)\n }\n return this\n }\n\n /// @internal\n #addNodeAndPush = (\n nodeType: NodeType,\n attrs?: Attrs,\n content?: Node[]\n ): Node => {\n const node = nodeType.createAndFill(attrs, content, this.#marks)\n if (!node) throw createNodeInParserFail(nodeType, attrs, content)\n\n this.push(node)\n\n return node\n }\n\n /// Add a node into current node.\n addNode = (nodeType: NodeType, attrs?: Attrs, content?: Node[]) => {\n try {\n this.#addNodeAndPush(nodeType, attrs, content)\n } catch (e) {\n console.error(e)\n }\n return this\n }\n\n /// Open a new mark, the next nodes added will have that mark.\n openMark = (markType: MarkType, attrs?: Attrs) => {\n const mark = markType.create(attrs)\n\n this.#marks = mark.addToSet(this.#marks)\n return this\n }\n\n /// Close a opened mark.\n closeMark = (markType: MarkType) => {\n this.#marks = markType.removeFromSet(this.#marks)\n return this\n }\n\n /// Add a text node into current node.\n addText = (text: string) => {\n try {\n const topElement = this.top()\n if (!topElement) throw stackOverFlow()\n\n const prevNode = topElement.pop()\n const currNode = this.schema.text(text, this.#marks)\n\n if (!prevNode) {\n topElement.push(currNode)\n return this\n }\n\n const merged = this.#maybeMerge(prevNode, currNode)\n if (merged) {\n topElement.push(merged)\n return this\n }\n topElement.push(prevNode, currNode)\n return this\n } catch (e) {\n console.error(e)\n return this\n }\n }\n\n /// @internal\n build = (): Node => {\n let doc: Node | undefined\n\n do doc = this.#closeNodeAndPush()\n while (this.size())\n\n return doc\n }\n\n /// Give the node or node list back to the state and\n /// the state will find a proper runner (by `match` method in parser spec) to handle it.\n next = (nodes: MarkdownNode | MarkdownNode[] = []) => {\n ;[nodes].flat().forEach((node) => this.#runNode(node))\n return this\n }\n\n /// Build the current state into a [prosemirror document](https://prosemirror.net/docs/ref/#model.Document_Structure).\n toDoc = () => this.build()\n\n /// Transform a markdown string into prosemirror state.\n run = (remark: RemarkParser, markdown: string) => {\n const tree = remark.runSync(\n remark.parse(markdown),\n markdown\n ) as MarkdownNode\n this.next(tree)\n\n return this\n }\n}\n","import type { MarkdownNode } from '..'\nimport type { JSONRecord } from '../utility'\n\nimport { StackElement } from '../utility'\n\nexport class SerializerStackElement extends StackElement<MarkdownNode> {\n constructor(\n public type: string,\n public children?: MarkdownNode[],\n public value?: string,\n public props: JSONRecord = {}\n ) {\n super()\n }\n\n static create = (\n type: string,\n children?: MarkdownNode[],\n value?: string,\n props: JSONRecord = {}\n ) => new SerializerStackElement(type, children, value, props)\n\n push = (node: MarkdownNode, ...rest: MarkdownNode[]) => {\n if (!this.children) this.children = []\n\n this.children.push(node, ...rest)\n }\n\n pop = (): MarkdownNode | undefined => this.children?.pop()\n}\n","import type {\n Fragment,\n MarkType,\n Node,\n NodeType,\n Schema,\n} from '@milkdown/prose/model'\n\nimport { serializerMatchError } from '@milkdown/exception'\nimport { Mark } from '@milkdown/prose/model'\n\nimport type {\n JSONRecord,\n MarkSchema,\n MarkdownNode,\n NodeSchema,\n RemarkParser,\n Root,\n} from '../utility'\nimport type { Serializer } from './types'\n\nimport { Stack } from '../utility'\nimport { SerializerStackElement } from './stack-element'\n\nconst isFragment = (x: Node | Fragment): x is Fragment =>\n Object.prototype.hasOwnProperty.call(x, 'size')\n\n/// State for serializer.\n/// Transform prosemirror state into remark AST.\nexport class SerializerState extends Stack<\n MarkdownNode,\n SerializerStackElement\n> {\n /// @internal\n #marks: readonly Mark[] = Mark.none\n /// Get the schema of state.\n readonly schema: Schema\n\n /// Create a serializer from schema and remark instance.\n ///\n /// ```typescript\n /// const serializer = SerializerState.create(schema, remark)\n /// const markdown = parser(prosemirrorDoc)\n /// ```\n static create = (schema: Schema, remark: RemarkParser): Serializer => {\n const state = new this(schema)\n return (content: Node) => {\n state.run(content)\n return state.toString(remark)\n }\n }\n\n /// @internal\n constructor(schema: Schema) {\n super()\n this.schema = schema\n }\n\n /// @internal\n #matchTarget = (node: Node | Mark): NodeType | MarkType => {\n const result = Object.values({\n ...this.schema.nodes,\n ...this.schema.marks,\n }).find((x): x is NodeType | MarkType => {\n const spec = x.spec as NodeSchema | MarkSchema\n return spec.toMarkdown.match(node as Node & Mark)\n })\n\n if (!result) throw serializerMatchError(node.type)\n\n return result\n }\n\n /// @internal\n #runProseNode = (node: Node) => {\n const type = this.#matchTarget(node)\n const spec = type.spec as NodeSchema\n return spec.toMarkdown.runner(this, node)\n }\n\n /// @internal\n #runProseMark = (mark: Mark, node: Node) => {\n const type = this.#matchTarget(mark)\n const spec = type.spec as MarkSchema\n return spec.toMarkdown.runner(this, mark, node)\n }\n\n /// @internal\n #runNode = (node: Node) => {\n const { marks } = node\n const getPriority = (x: Mark) => x.type.spec.priority ?? 50\n const tmp = [...marks].sort((a, b) => getPriority(a) - getPriority(b))\n const unPreventNext = tmp.every((mark) => !this.#runProseMark(mark, node))\n if (unPreventNext) this.#runProseNode(node)\n\n marks.forEach((mark) => this.#closeMark(mark))\n }\n\n /// @internal\n #searchType = (child: MarkdownNode, type: string): MarkdownNode => {\n if (child.type === type) return child\n\n if (child.children?.length !== 1) return child\n\n const searchNode = (node: MarkdownNode): MarkdownNode | null => {\n if (node.type === type) return node.value != null ? null : node\n\n if (node.children?.length !== 1) return null\n\n const [firstChild] = node.children\n if (!firstChild) return null\n\n return searchNode(firstChild)\n }\n\n const target = searchNode(child)\n\n if (!target) return child\n\n const tmp = target.children ? [...target.children] : undefined\n const node = { ...child, children: tmp }\n node.children = tmp\n target.children = [node]\n\n return target\n }\n\n /// @internal\n #maybeMergeChildren = (node: MarkdownNode): MarkdownNode => {\n const { children } = node\n if (!children) return node\n\n node.children = children.reduce((nextChildren, child, index) => {\n if (index === 0) return [child]\n\n const last = nextChildren.at(-1)\n if (last && last.isMark && child.isMark) {\n child = this.#searchType(child, last.type)\n const { children: currChildren, ...currRest } = child\n const { children: prevChildren, ...prevRest } = last\n if (\n child.type === last.type &&\n currChildren &&\n prevChildren &&\n JSON.stringify(currRest) === JSON.stringify(prevRest)\n ) {\n const next = {\n ...prevRest,\n children: [...prevChildren, ...currChildren],\n }\n return nextChildren\n .slice(0, -1)\n .concat(this.#maybeMergeChildren(next))\n }\n }\n return nextChildren.concat(child)\n }, [] as MarkdownNode[])\n\n return node\n }\n\n /// @internal\n #createMarkdownNode = (element: SerializerStackElement) => {\n const node: MarkdownNode = {\n ...element.props,\n type: element.type,\n }\n\n if (element.children) node.children = element.children\n\n if (element.value) node.value = element.value\n\n return node\n }\n\n /// Open a new node, the next operations will\n /// add nodes into that new node until `closeNode` is called.\n openNode = (type: string, value?: string, props?: JSONRecord) => {\n this.open(SerializerStackElement.create(type, undefined, value, props))\n return this\n }\n\n #moveSpaces = (\n element: SerializerStackElement,\n onPush: () => MarkdownNode\n ) => {\n let startSpaces = ''\n let endSpaces = ''\n const children = element.children\n let first = -1\n let last = -1\n const findIndex = (node: MarkdownNode[]) => {\n if (!node) return\n node.forEach((child, index) => {\n if (child.type === 'text' && child.value) {\n if (first < 0) first = index\n\n last = index\n }\n })\n }\n\n if (children) {\n findIndex(children)\n const lastChild = children?.[last] as\n | (MarkdownNode & { value: string })\n | undefined\n const firstChild = children?.[first] as\n | (MarkdownNode & { value: string })\n | undefined\n if (lastChild && lastChild.value.endsWith(' ')) {\n const text = lastChild.value\n const trimmed = text.trimEnd()\n endSpaces = text.slice(trimmed.length)\n lastChild.value = trimmed\n }\n if (firstChild && firstChild.value.startsWith(' ')) {\n const text = firstChild.value\n const trimmed = text.trimStart()\n startSpaces = text.slice(0, text.length - trimmed.length)\n firstChild.value = trimmed\n }\n }\n\n if (startSpaces.length) this.#addNodeAndPush('text', undefined, startSpaces)\n\n const result = onPush()\n\n if (endSpaces.length) this.#addNodeAndPush('text', undefined, endSpaces)\n\n return result\n }\n\n /// @internal\n #closeNodeAndPush = (trim: boolean = false): MarkdownNode => {\n const element = this.close()\n\n const onPush = () =>\n this.#addNodeAndPush(\n element.type,\n element.children,\n element.value,\n element.props\n )\n\n if (trim) return this.#moveSpaces(element, onPush)\n\n return onPush()\n }\n\n /// Close the current node and push it into the parent node.\n closeNode = () => {\n this.#closeNodeAndPush()\n return this\n }\n\n /// @internal\n #addNodeAndPush = (\n type: string,\n children?: MarkdownNode[],\n value?: string,\n props?: JSONRecord\n ): MarkdownNode => {\n const element = SerializerStackElement.create(type, children, value, props)\n const node: MarkdownNode = this.#maybeMergeChildren(\n this.#createMarkdownNode(element)\n )\n this.push(node)\n return node\n }\n\n /// Add a node into current node.\n addNode = (\n type: string,\n children?: MarkdownNode[],\n value?: string,\n props?: JSONRecord\n ) => {\n this.#addNodeAndPush(type, children, value, props)\n return this\n }\n\n /// @internal\n #openMark = (\n mark: Mark,\n type: string,\n value?: string,\n props?: JSONRecord\n ) => {\n const isIn = mark.isInSet(this.#marks)\n\n if (isIn) return this\n\n this.#marks = mark.addToSet(this.#marks)\n return this.openNode(type, value, { ...props, isMark: true })\n }\n\n /// @internal\n #closeMark = (mark: Mark): void => {\n const isIn = mark.isInSet(this.#marks)\n\n if (!isIn) return\n\n this.#marks = mark.type.removeFromSet(this.#marks)\n this.#closeNodeAndPush(true)\n }\n\n /// Open a new mark, the next nodes added will have that mark.\n /// The mark will be closed automatically.\n withMark = (mark: Mark, type: string, value?: string, props?: JSONRecord) => {\n this.#openMark(mark, type, value, props)\n return this\n }\n\n /// Close a opened mark.\n /// In most cases you don't need this because\n /// marks will be closed automatically.\n closeMark = (mark: Mark) => {\n this.#closeMark(mark)\n return this\n }\n\n /// @internal\n build = (): MarkdownNode => {\n let doc: MarkdownNode | null = null\n do doc = this.#closeNodeAndPush()\n while (this.size())\n\n return doc\n }\n\n /// Give the node or node list back to the state and\n /// the state will find a proper runner (by `match` method in serializer spec) to handle it.\n next = (nodes: Node | Fragment) => {\n if (isFragment(nodes)) {\n nodes.forEach((node) => {\n this.#runNode(node)\n })\n return this\n }\n this.#runNode(nodes)\n return this\n }\n\n /// Use a remark parser to serialize current AST stored.\n override toString = (remark: RemarkParser): string =>\n remark.stringify(this.build() as Root)\n\n /// Transform a prosemirror node tree into remark AST.\n run = (tree: Node) => {\n this.next(tree)\n\n return this\n }\n}\n"],"mappings":"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"}
{"version":3,"file":"index.js","names":["#hasText","#matchTarget","#marks","#addNodeAndPush","#closeNodeAndPush","#maybeMerge","#runNode","#matchTarget","#openMarks","#closeMark","#orderMarks","#runProseMark","#runProseNode","#closeEndedMarks","#maybeMergeChildren","#addNodeAndPush","#moveSpaces","#closeNodeAndPush","#createMarkdownNode","#openMark","#runNode"],"sources":["../src/utility/stack.ts","../src/parser/stack-element.ts","../src/parser/state.ts","../src/serializer/stack-element.ts","../src/serializer/state.ts"],"sourcesContent":["import { stackOverFlow } from '@milkdown/exception'\n\n/// The element of the stack, which holds an array of nodes.\nexport abstract class StackElement<Node> {\n /// A method that can `push` a node into the element.\n abstract push(node: Node, ...rest: Node[]): void\n}\n\n/// The stack that is used to store the elements.\n///\n/// > Generally, you don't need to use this class directly.\n///\n/// When using the stack, users can call `stack.open` to push a new element into the stack.\n/// And use `stack.push` to push a node into the top element.\n/// Then use `stack.close` to close the top element and pop it.\n///\n/// For example: `stack.open(A).push(B).push(C).close()` will generate a structure like `A(B, C)`.\nexport class Stack<Node, Element extends StackElement<Node>> {\n protected elements: Element[] = []\n\n /// Get the size of the stack.\n size = (): number => {\n return this.elements.length\n }\n\n /// Get the top element of the stack.\n top = (): Element | undefined => {\n return this.elements.at(-1)\n }\n\n /// Push a node into the top element.\n push = (node: Node): void => {\n this.top()?.push(node)\n }\n\n /// Push a new element.\n open = (node: Element): void => {\n this.elements.push(node)\n }\n\n /// Close the top element and pop it.\n close = (): Element => {\n const el = this.elements.pop()\n if (!el) throw stackOverFlow()\n\n return el\n }\n}\n","import type { Attrs, Node, NodeType } from '@milkdown/prose/model'\n\nimport { StackElement } from '../utility'\n\nexport class ParserStackElement extends StackElement<Node> {\n constructor(\n public type: NodeType,\n public content: Node[],\n public attrs?: Attrs\n ) {\n super()\n }\n\n push(node: Node, ...rest: Node[]) {\n this.content.push(node, ...rest)\n }\n\n pop(): Node | undefined {\n return this.content.pop()\n }\n\n static create(type: NodeType, content: Node[], attrs?: Attrs) {\n return new ParserStackElement(type, content, attrs)\n }\n}\n","import type {\n Attrs,\n MarkType,\n Node,\n NodeType,\n Schema,\n} from '@milkdown/prose/model'\n\nimport {\n createNodeInParserFail,\n parserMatchError,\n stackOverFlow,\n} from '@milkdown/exception'\nimport { Mark } from '@milkdown/prose/model'\n\nimport type {\n MarkSchema,\n MarkdownNode,\n NodeSchema,\n RemarkParser,\n} from '../utility'\nimport type { Parser } from './types'\n\nimport { Stack } from '../utility'\nimport { ParserStackElement } from './stack-element'\n\n/// A state machine for parser. Transform remark AST into prosemirror state.\nexport class ParserState extends Stack<Node, ParserStackElement> {\n /// The schema in current editor.\n readonly schema: Schema\n\n /// @internal\n #marks: readonly Mark[] = Mark.none\n\n /// Create a parser from schema and remark instance.\n ///\n /// ```typescript\n /// const parser = ParserState.create(schema, remark)\n /// const prosemirrorNode = parser(SomeMarkdownText)\n /// ```\n static create = (schema: Schema, remark: RemarkParser): Parser => {\n const state = new this(schema)\n return (text) => {\n state.run(remark, text)\n return state.toDoc()\n }\n }\n\n /// @internal\n constructor(schema: Schema) {\n super()\n this.schema = schema\n }\n\n /// @internal\n #hasText = (node: Node): node is Node & { text: string } => node.isText\n\n /// @internal\n #maybeMerge = (a: Node, b: Node): Node | undefined => {\n if (this.#hasText(a) && this.#hasText(b) && Mark.sameSet(a.marks, b.marks))\n return this.schema.text(a.text + b.text, a.marks)\n\n return undefined\n }\n\n /// @internal\n #matchTarget = (node: MarkdownNode): NodeType | MarkType => {\n const result = Object.values({\n ...this.schema.nodes,\n ...this.schema.marks,\n }).find((x): x is NodeType | MarkType => {\n const spec = x.spec as NodeSchema | MarkSchema\n return spec.parseMarkdown.match(node)\n })\n\n if (!result) throw parserMatchError(node)\n\n return result\n }\n\n /// @internal\n #runNode = (node: MarkdownNode) => {\n const type = this.#matchTarget(node)\n const spec = type.spec as NodeSchema | MarkSchema\n\n spec.parseMarkdown.runner(this, node, type as NodeType & MarkType)\n }\n\n /// Inject root node for prosemirror state.\n injectRoot = (node: MarkdownNode, nodeType: NodeType, attrs?: Attrs) => {\n this.openNode(nodeType, attrs)\n this.next(node.children)\n\n return this\n }\n\n /// Open a new node, the next operations will\n /// add nodes into that new node until `closeNode` is called.\n openNode = (nodeType: NodeType, attrs?: Attrs) => {\n this.open(ParserStackElement.create(nodeType, [], attrs))\n return this\n }\n\n /// @internal\n #closeNodeAndPush = (): Node => {\n this.#marks = Mark.none\n const element = this.close()\n\n return this.#addNodeAndPush(element.type, element.attrs, element.content)\n }\n\n /// Close the current node and push it into the parent node.\n closeNode = () => {\n try {\n this.#closeNodeAndPush()\n } catch (e) {\n console.error(e)\n }\n return this\n }\n\n /// @internal\n #addNodeAndPush = (\n nodeType: NodeType,\n attrs?: Attrs,\n content?: Node[]\n ): Node => {\n const node = nodeType.createAndFill(attrs, content, this.#marks)\n if (!node) throw createNodeInParserFail(nodeType, attrs, content)\n\n this.push(node)\n\n return node\n }\n\n /// Add a node into current node.\n addNode = (nodeType: NodeType, attrs?: Attrs, content?: Node[]) => {\n try {\n this.#addNodeAndPush(nodeType, attrs, content)\n } catch (e) {\n console.error(e)\n }\n return this\n }\n\n /// Open a new mark, the next nodes added will have that mark.\n openMark = (markType: MarkType, attrs?: Attrs) => {\n const mark = markType.create(attrs)\n\n this.#marks = mark.addToSet(this.#marks)\n return this\n }\n\n /// Close a opened mark.\n closeMark = (markType: MarkType) => {\n this.#marks = markType.removeFromSet(this.#marks)\n return this\n }\n\n /// Add a text node into current node.\n addText = (text: string) => {\n try {\n const topElement = this.top()\n if (!topElement) throw stackOverFlow()\n\n const prevNode = topElement.pop()\n const currNode = this.schema.text(text, this.#marks)\n\n if (!prevNode) {\n topElement.push(currNode)\n return this\n }\n\n const merged = this.#maybeMerge(prevNode, currNode)\n if (merged) {\n topElement.push(merged)\n return this\n }\n topElement.push(prevNode, currNode)\n return this\n } catch (e) {\n console.error(e)\n return this\n }\n }\n\n /// @internal\n build = (): Node => {\n let doc: Node | undefined\n\n do doc = this.#closeNodeAndPush()\n while (this.size())\n\n return doc\n }\n\n /// Give the node or node list back to the state and\n /// the state will find a proper runner (by `match` method in parser spec) to handle it.\n next = (nodes: MarkdownNode | MarkdownNode[] = []) => {\n ;[nodes].flat().forEach((node) => this.#runNode(node))\n return this\n }\n\n /// Build the current state into a [prosemirror document](https://prosemirror.net/docs/ref/#model.Document_Structure).\n toDoc = () => this.build()\n\n /// Transform a markdown string into prosemirror state.\n run = (remark: RemarkParser, markdown: string) => {\n const tree = remark.runSync(\n remark.parse(markdown),\n markdown\n ) as MarkdownNode\n this.next(tree)\n\n return this\n }\n}\n","import type { MarkdownNode } from '..'\nimport type { JSONRecord } from '../utility'\n\nimport { StackElement } from '../utility'\n\nexport class SerializerStackElement extends StackElement<MarkdownNode> {\n constructor(\n public type: string,\n public children?: MarkdownNode[],\n public value?: string,\n public props: JSONRecord = {}\n ) {\n super()\n }\n\n static create = (\n type: string,\n children?: MarkdownNode[],\n value?: string,\n props: JSONRecord = {}\n ) => new SerializerStackElement(type, children, value, props)\n\n push = (node: MarkdownNode, ...rest: MarkdownNode[]) => {\n if (!this.children) this.children = []\n\n this.children.push(node, ...rest)\n }\n\n pop = (): MarkdownNode | undefined => this.children?.pop()\n}\n","import type {\n Fragment,\n Mark,\n MarkType,\n Node,\n NodeType,\n Schema,\n} from '@milkdown/prose/model'\n\nimport { serializerMatchError } from '@milkdown/exception'\n\nimport type {\n JSONRecord,\n MarkSchema,\n MarkdownNode,\n NodeSchema,\n RemarkParser,\n Root,\n} from '../utility'\nimport type { Serializer } from './types'\n\nimport { Stack } from '../utility'\nimport { SerializerStackElement } from './stack-element'\n\nconst isFragment = (x: Node | Fragment): x is Fragment =>\n Object.prototype.hasOwnProperty.call(x, 'size')\n\ninterface OpenMark {\n mark: Mark\n /// Whether the mark node can span multiple prosemirror nodes.\n /// Value based marks (e.g. inlineCode) hold their content as a single\n /// value and must be closed after the node that carries them.\n spanning: boolean\n}\n\n/// State for serializer.\n/// Transform prosemirror state into remark AST.\nexport class SerializerState extends Stack<\n MarkdownNode,\n SerializerStackElement\n> {\n /// @internal\n #openMarks: OpenMark[] = []\n /// Get the schema of state.\n readonly schema: Schema\n\n /// Create a serializer from schema and remark instance.\n ///\n /// ```typescript\n /// const serializer = SerializerState.create(schema, remark)\n /// const markdown = parser(prosemirrorDoc)\n /// ```\n static create = (schema: Schema, remark: RemarkParser): Serializer => {\n const state = new this(schema)\n return (content: Node) => {\n state.run(content)\n return state.toString(remark)\n }\n }\n\n /// @internal\n constructor(schema: Schema) {\n super()\n this.schema = schema\n }\n\n /// @internal\n #matchTarget = (node: Node | Mark): NodeType | MarkType => {\n const result = Object.values({\n ...this.schema.nodes,\n ...this.schema.marks,\n }).find((x): x is NodeType | MarkType => {\n const spec = x.spec as NodeSchema | MarkSchema\n return spec.toMarkdown.match(node as Node & Mark)\n })\n\n if (!result) throw serializerMatchError(node.type)\n\n return result\n }\n\n /// @internal\n #runProseNode = (node: Node) => {\n const type = this.#matchTarget(node)\n const spec = type.spec as NodeSchema\n return spec.toMarkdown.runner(this, node)\n }\n\n /// @internal\n #runProseMark = (mark: Mark, node: Node) => {\n const type = this.#matchTarget(mark)\n const spec = type.spec as MarkSchema\n return spec.toMarkdown.runner(this, mark, node)\n }\n\n /// @internal\n /// Order the marks of a node so that marks which are already open keep\n /// their current nesting order and can stay open, followed by the newly\n /// opened marks sorted by priority.\n #orderMarks = (marks: readonly Mark[]): Mark[] => {\n const getPriority = (x: Mark) => x.type.spec.priority ?? 50\n const rest = [...marks].sort((a, b) => getPriority(a) - getPriority(b))\n const continuing: Mark[] = []\n this.#openMarks.forEach(({ mark }) => {\n const index = rest.findIndex((x) => x.eq(mark))\n if (index >= 0) continuing.push(...rest.splice(index, 1))\n })\n return continuing.concat(rest)\n }\n\n /// @internal\n /// Close open marks that do not continue into the next node.\n /// A mark can only stay open if every mark outside of it also stays open,\n /// so scan from the outermost mark and close everything starting at the\n /// first mark that ends here.\n #closeEndedMarks = (next?: Node) => {\n const nextMarks = next?.marks\n let keep = 0\n while (keep < this.#openMarks.length) {\n const { mark, spanning } = this.#openMarks[keep] as OpenMark\n if (spanning && nextMarks?.some((x) => x.eq(mark))) keep++\n else break\n }\n for (let i = this.#openMarks.length - 1; i >= keep; i--)\n this.#closeMark((this.#openMarks[i] as OpenMark).mark)\n }\n\n /// @internal\n #runNode = (node: Node, next?: Node) => {\n const marks = this.#orderMarks(node.marks)\n const unPreventNext = marks.every((mark) => !this.#runProseMark(mark, node))\n if (unPreventNext) this.#runProseNode(node)\n\n this.#closeEndedMarks(next)\n }\n\n /// @internal\n #maybeMergeChildren = (node: MarkdownNode): MarkdownNode => {\n const { children } = node\n if (!children) return node\n\n node.children = children.reduce((nextChildren, child, index) => {\n if (index === 0) return [child]\n\n const last = nextChildren.at(-1)\n if (last && last.isMark && child.isMark) {\n const { children: currChildren, ...currRest } = child\n const { children: prevChildren, ...prevRest } = last\n if (\n child.type === last.type &&\n currChildren &&\n prevChildren &&\n JSON.stringify(currRest) === JSON.stringify(prevRest)\n ) {\n const next = {\n ...prevRest,\n children: [...prevChildren, ...currChildren],\n }\n return nextChildren\n .slice(0, -1)\n .concat(this.#maybeMergeChildren(next))\n }\n }\n return nextChildren.concat(child)\n }, [] as MarkdownNode[])\n\n return node\n }\n\n /// @internal\n #createMarkdownNode = (element: SerializerStackElement) => {\n const node: MarkdownNode = {\n ...element.props,\n type: element.type,\n }\n\n if (element.children) node.children = element.children\n\n if (element.value) node.value = element.value\n\n return node\n }\n\n /// Open a new node, the next operations will\n /// add nodes into that new node until `closeNode` is called.\n openNode = (type: string, value?: string, props?: JSONRecord) => {\n this.open(SerializerStackElement.create(type, undefined, value, props))\n return this\n }\n\n #moveSpaces = (\n element: SerializerStackElement,\n onPush: () => MarkdownNode\n ) => {\n let startSpaces = ''\n let endSpaces = ''\n const children = element.children\n\n if (children) {\n const firstChild = children[0] as\n | (MarkdownNode & { value: string })\n | undefined\n const lastChild = children.at(-1) as\n | (MarkdownNode & { value: string })\n | undefined\n if (\n lastChild &&\n lastChild.type === 'text' &&\n lastChild.value.endsWith(' ')\n ) {\n const text = lastChild.value\n const trimmed = text.trimEnd()\n endSpaces = text.slice(trimmed.length)\n lastChild.value = trimmed\n }\n if (\n firstChild &&\n firstChild.type === 'text' &&\n firstChild.value.startsWith(' ')\n ) {\n const text = firstChild.value\n const trimmed = text.trimStart()\n startSpaces = text.slice(0, text.length - trimmed.length)\n firstChild.value = trimmed\n }\n }\n\n if (startSpaces.length) this.#addNodeAndPush('text', undefined, startSpaces)\n\n const result = onPush()\n\n if (endSpaces.length) this.#addNodeAndPush('text', undefined, endSpaces)\n\n return result\n }\n\n /// @internal\n #closeNodeAndPush = (trim: boolean = false): MarkdownNode => {\n const element = this.close()\n\n const onPush = () =>\n this.#addNodeAndPush(\n element.type,\n element.children,\n element.value,\n element.props\n )\n\n if (trim) return this.#moveSpaces(element, onPush)\n\n return onPush()\n }\n\n /// Close the current node and push it into the parent node.\n closeNode = () => {\n this.#closeNodeAndPush()\n return this\n }\n\n /// @internal\n #addNodeAndPush = (\n type: string,\n children?: MarkdownNode[],\n value?: string,\n props?: JSONRecord\n ): MarkdownNode => {\n const element = SerializerStackElement.create(type, children, value, props)\n const node: MarkdownNode = this.#maybeMergeChildren(\n this.#createMarkdownNode(element)\n )\n this.push(node)\n return node\n }\n\n /// Add a node into current node.\n addNode = (\n type: string,\n children?: MarkdownNode[],\n value?: string,\n props?: JSONRecord\n ) => {\n this.#addNodeAndPush(type, children, value, props)\n return this\n }\n\n /// @internal\n #openMark = (\n mark: Mark,\n type: string,\n value?: string,\n props?: JSONRecord\n ) => {\n const isIn = this.#openMarks.some((x) => x.mark.eq(mark))\n\n if (isIn) return this\n\n this.#openMarks.push({ mark, spanning: value == null })\n return this.openNode(type, value, { ...props, isMark: true })\n }\n\n /// @internal\n #closeMark = (mark: Mark): void => {\n let index = -1\n for (let i = this.#openMarks.length - 1; i >= 0; i--) {\n if ((this.#openMarks[i] as OpenMark).mark.eq(mark)) {\n index = i\n break\n }\n }\n\n if (index < 0) return\n\n this.#openMarks.splice(index, 1)\n this.#closeNodeAndPush(true)\n }\n\n /// Open a new mark, the next nodes added will have that mark.\n /// The mark will be kept open across nodes that share it and\n /// closed automatically when it no longer applies.\n withMark = (mark: Mark, type: string, value?: string, props?: JSONRecord) => {\n this.#openMark(mark, type, value, props)\n return this\n }\n\n /// Close a opened mark.\n /// In most cases you don't need this because\n /// marks will be closed automatically.\n closeMark = (mark: Mark) => {\n this.#closeMark(mark)\n return this\n }\n\n /// @internal\n build = (): MarkdownNode => {\n let doc: MarkdownNode | null = null\n do doc = this.#closeNodeAndPush()\n while (this.size())\n\n return doc\n }\n\n /// Give the node or node list back to the state and\n /// the state will find a proper runner (by `match` method in serializer spec) to handle it.\n /// When a node list is given, marks shared between adjacent nodes are kept\n /// open across them so that they are serialized as one continuous span.\n next = (nodes: Node | Fragment) => {\n if (isFragment(nodes)) {\n nodes.forEach((node, _offset, index) => {\n this.#runNode(node, nodes.maybeChild(index + 1) ?? undefined)\n })\n return this\n }\n this.#runNode(nodes)\n return this\n }\n\n /// Use a remark parser to serialize current AST stored.\n override toString = (remark: RemarkParser): string =>\n remark.stringify(this.build() as Root)\n\n /// Transform a prosemirror node tree into remark AST.\n run = (tree: Node) => {\n this.#openMarks = []\n this.next(tree)\n\n return this\n }\n}\n"],"mappings":"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"}

@@ -1,3 +0,2 @@

import type { Fragment, Node, Schema } from '@milkdown/prose/model';
import { Mark } from '@milkdown/prose/model';
import type { Fragment, Mark, Node, Schema } from '@milkdown/prose/model';
import type { JSONRecord, MarkdownNode, RemarkParser } from '../utility';

@@ -4,0 +3,0 @@ import type { Serializer } from './types';

@@ -1,1 +0,1 @@

{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/serializer/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EAER,IAAI,EAEJ,MAAM,EACP,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAE5C,OAAO,KAAK,EACV,UAAU,EAEV,YAAY,EAEZ,YAAY,EAEb,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAOxD,qBAAa,eAAgB,SAAQ,KAAK,CACxC,YAAY,EACZ,sBAAsB,CACvB;;IAIC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAQvB,MAAM,CAAC,MAAM,GAAI,QAAQ,MAAM,EAAE,QAAQ,YAAY,KAAG,UAAU,CAMjE;gBAGW,MAAM,EAAE,MAAM;IA4H1B,QAAQ,GAAI,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,UAAU,UAG3D;IAuED,SAAS,aAGR;IAkBD,OAAO,GACL,MAAM,MAAM,EACZ,WAAW,YAAY,EAAE,EACzB,QAAQ,MAAM,EACd,QAAQ,UAAU,UAInB;IA6BD,QAAQ,GAAI,MAAM,IAAI,EAAE,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,UAAU,UAGvE;IAKD,SAAS,GAAI,MAAM,IAAI,UAGtB;IAGD,KAAK,QAAO,YAAY,CAMvB;IAID,IAAI,GAAI,OAAO,IAAI,GAAG,QAAQ,UAS7B;IAGQ,QAAQ,GAAI,QAAQ,YAAY,KAAG,MAAM,CACV;IAGxC,GAAG,GAAI,MAAM,IAAI,UAIhB;CACF"}
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/serializer/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,IAAI,EAEJ,IAAI,EAEJ,MAAM,EACP,MAAM,uBAAuB,CAAA;AAI9B,OAAO,KAAK,EACV,UAAU,EAEV,YAAY,EAEZ,YAAY,EAEb,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAexD,qBAAa,eAAgB,SAAQ,KAAK,CACxC,YAAY,EACZ,sBAAsB,CACvB;;IAIC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAQvB,MAAM,CAAC,MAAM,GAAI,QAAQ,MAAM,EAAE,QAAQ,YAAY,KAAG,UAAU,CAMjE;gBAGW,MAAM,EAAE,MAAM;IA4H1B,QAAQ,GAAI,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,UAAU,UAG3D;IAkED,SAAS,aAGR;IAkBD,OAAO,GACL,MAAM,MAAM,EACZ,WAAW,YAAY,EAAE,EACzB,QAAQ,MAAM,EACd,QAAQ,UAAU,UAInB;IAoCD,QAAQ,GAAI,MAAM,IAAI,EAAE,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE,QAAQ,UAAU,UAGvE;IAKD,SAAS,GAAI,MAAM,IAAI,UAGtB;IAGD,KAAK,QAAO,YAAY,CAMvB;IAMD,IAAI,GAAI,OAAO,IAAI,GAAG,QAAQ,UAS7B;IAGQ,QAAQ,GAAI,QAAQ,YAAY,KAAG,MAAM,CACV;IAGxC,GAAG,GAAI,MAAM,IAAI,UAKhB;CACF"}
{
"name": "@milkdown/transformer",
"version": "7.21.2",
"version": "7.21.3",
"keywords": [

@@ -32,4 +32,4 @@ "markdown",

"unified": "^11.0.3",
"@milkdown/exception": "7.21.2",
"@milkdown/prose": "7.21.2"
"@milkdown/exception": "7.21.3",
"@milkdown/prose": "7.21.3"
},

@@ -36,0 +36,0 @@ "scripts": {

@@ -1,2 +0,2 @@

import type { Mark, Schema } from '@milkdown/prose/model'
import type { Fragment, Mark, Node, Schema } from '@milkdown/prose/model'

@@ -7,26 +7,13 @@ import { describe, expect, it } from 'vitest'

const boldMark = {
isInSet: (arr: string[]) => arr.includes('bold'),
addToSet: (arr: string[]) => arr.concat('bold'),
type: {
removeFromSet: (arr: string[]) => arr.filter((x) => x !== 'bold'),
},
} as unknown as Mark
const createMockMark = (name: string, priority?: number) =>
({
name,
eq: (other: { name: string }) => other.name === name,
type: { spec: { priority } },
}) as unknown as Mark
const italicMark = {
isInSet: (arr: string[]) => arr.includes('italic'),
addToSet: (arr: string[]) => arr.concat('italic'),
type: {
removeFromSet: (arr: string[]) => arr.filter((x) => x !== 'italic'),
},
} as unknown as Mark
const boldMark = createMockMark('bold')
const italicMark = createMockMark('italic')
const inlineCodeMark = createMockMark('inlineCode', 100)
const inlineCodeMark = {
isInSet: (arr: string[]) => arr.includes('inlineCode'),
addToSet: (arr: string[]) => arr.concat('inlineCode'),
type: {
removeFromSet: (arr: string[]) => arr.filter((x) => x !== 'inlineCode'),
},
} as unknown as Mark
const schema = {

@@ -56,7 +43,64 @@ nodes: {

},
text: {
spec: {
toMarkdown: {
match: (node: { type: string }) => node.type === 'text',
runner: (state: SerializerState, node: { value: string }) => {
state.addNode('text', undefined, node.value)
},
},
},
},
},
marks: {},
text: (text: string, marks: string[]) => ({ text, marks, isText: true }),
marks: {
bold: {
spec: {
toMarkdown: {
match: (mark: { name: string }) => mark.name === 'bold',
runner: (state: SerializerState, mark: Mark) => {
state.withMark(mark, 'bold')
},
},
},
},
italic: {
spec: {
toMarkdown: {
match: (mark: { name: string }) => mark.name === 'italic',
runner: (state: SerializerState, mark: Mark) => {
state.withMark(mark, 'italic')
},
},
},
},
inlineCode: {
spec: {
toMarkdown: {
match: (mark: { name: string }) => mark.name === 'inlineCode',
runner: (state: SerializerState, mark: Mark, node: Node) => {
state.withMark(
mark,
'inlineCode',
(node as never as { value: string }).value
)
return true
},
},
},
},
},
} as unknown as Schema
const text = (value: string, marks: Mark[] = []) =>
({ type: 'text', value, marks }) as unknown as Node
const fragment = (...nodes: Node[]) =>
({
size: nodes.length,
forEach: (fn: (node: Node, offset: number, index: number) => void) => {
nodes.forEach((node, index) => fn(node, 0, index))
},
maybeChild: (index: number) => nodes[index] ?? null,
}) as unknown as Fragment
describe('serializer-state', () => {

@@ -224,3 +268,3 @@ it('node', () => {

// The bold wrapper around inlineCode should be preserved,
// not restructured by searchType
// not restructured by the merge.
expect(state.top()).toMatchObject({

@@ -290,2 +334,156 @@ type: 'doc',

})
it('keeps a mark open across nodes that share it', () => {
const state = new SerializerState(schema)
// **a *b* c** — the strong mark spans all three text nodes.
state.openNode('doc')
state.openNode('paragraph')
state.next(
fragment(
text('a ', [boldMark]),
text('b', [boldMark, italicMark]),
text(' c', [boldMark])
)
)
state.closeNode()
expect(state.top()).toMatchObject({
type: 'doc',
children: [
{
type: 'paragraph',
children: [
{
type: 'bold',
isMark: true,
children: [
{ type: 'text', value: 'a ' },
{
type: 'italic',
isMark: true,
children: [{ type: 'text', value: 'b' }],
},
{ type: 'text', value: ' c' },
],
},
],
},
],
})
})
it('keeps continuing marks outside of newly opened marks', () => {
const state = new SerializerState(schema)
// *a **b** c* — the italic mark opens first and must stay the
// outer mark when bold opens on the second text node, even if the
// mark set of that node lists bold first.
state.openNode('doc')
state.openNode('paragraph')
state.next(
fragment(
text('a ', [italicMark]),
text('b', [boldMark, italicMark]),
text(' c', [italicMark])
)
)
state.closeNode()
expect(state.top()).toMatchObject({
type: 'doc',
children: [
{
type: 'paragraph',
children: [
{
type: 'italic',
isMark: true,
children: [
{ type: 'text', value: 'a ' },
{
type: 'bold',
isMark: true,
children: [{ type: 'text', value: 'b' }],
},
{ type: 'text', value: ' c' },
],
},
],
},
],
})
})
it('closes value-based marks after the node that carries them', () => {
const state = new SerializerState(schema)
// **`code` b** — inlineCode holds its content as a value and must not
// stay open, while the bold mark spans both text nodes.
state.openNode('doc')
state.openNode('paragraph')
state.next(
fragment(text('code', [boldMark, inlineCodeMark]), text(' b', [boldMark]))
)
state.closeNode()
expect(state.top()).toMatchObject({
type: 'doc',
children: [
{
type: 'paragraph',
children: [
{
type: 'bold',
isMark: true,
children: [
{
type: 'inlineCode',
isMark: true,
value: 'code',
},
{ type: 'text', value: ' b' },
],
},
],
},
],
})
})
it('does not trim spaces inside a mark spanning multiple nodes', () => {
const state = new SerializerState(schema)
// **a *b*** — the space between "a" and the italic node is internal
// and must be preserved when the bold mark closes.
state.openNode('doc')
state.openNode('paragraph')
state.next(
fragment(text('a ', [boldMark]), text('b', [boldMark, italicMark]))
)
state.closeNode()
expect(state.top()).toMatchObject({
type: 'doc',
children: [
{
type: 'paragraph',
children: [
{
type: 'bold',
isMark: true,
children: [
{ type: 'text', value: 'a ' },
{
type: 'italic',
isMark: true,
children: [{ type: 'text', value: 'b' }],
},
],
},
],
},
],
})
})
})
import type {
Fragment,
Mark,
MarkType,

@@ -10,3 +11,2 @@ Node,

import { serializerMatchError } from '@milkdown/exception'
import { Mark } from '@milkdown/prose/model'

@@ -29,2 +29,10 @@ import type {

interface OpenMark {
mark: Mark
/// Whether the mark node can span multiple prosemirror nodes.
/// Value based marks (e.g. inlineCode) hold their content as a single
/// value and must be closed after the node that carries them.
spanning: boolean
}
/// State for serializer.

@@ -37,3 +45,3 @@ /// Transform prosemirror state into remark AST.

/// @internal
#marks: readonly Mark[] = Mark.none
#openMarks: OpenMark[] = []
/// Get the schema of state.

@@ -92,39 +100,40 @@ readonly schema: Schema

/// @internal
#runNode = (node: Node) => {
const { marks } = node
/// Order the marks of a node so that marks which are already open keep
/// their current nesting order and can stay open, followed by the newly
/// opened marks sorted by priority.
#orderMarks = (marks: readonly Mark[]): Mark[] => {
const getPriority = (x: Mark) => x.type.spec.priority ?? 50
const tmp = [...marks].sort((a, b) => getPriority(a) - getPriority(b))
const unPreventNext = tmp.every((mark) => !this.#runProseMark(mark, node))
if (unPreventNext) this.#runProseNode(node)
marks.forEach((mark) => this.#closeMark(mark))
const rest = [...marks].sort((a, b) => getPriority(a) - getPriority(b))
const continuing: Mark[] = []
this.#openMarks.forEach(({ mark }) => {
const index = rest.findIndex((x) => x.eq(mark))
if (index >= 0) continuing.push(...rest.splice(index, 1))
})
return continuing.concat(rest)
}
/// @internal
#searchType = (child: MarkdownNode, type: string): MarkdownNode => {
if (child.type === type) return child
if (child.children?.length !== 1) return child
const searchNode = (node: MarkdownNode): MarkdownNode | null => {
if (node.type === type) return node.value != null ? null : node
if (node.children?.length !== 1) return null
const [firstChild] = node.children
if (!firstChild) return null
return searchNode(firstChild)
/// Close open marks that do not continue into the next node.
/// A mark can only stay open if every mark outside of it also stays open,
/// so scan from the outermost mark and close everything starting at the
/// first mark that ends here.
#closeEndedMarks = (next?: Node) => {
const nextMarks = next?.marks
let keep = 0
while (keep < this.#openMarks.length) {
const { mark, spanning } = this.#openMarks[keep] as OpenMark
if (spanning && nextMarks?.some((x) => x.eq(mark))) keep++
else break
}
for (let i = this.#openMarks.length - 1; i >= keep; i--)
this.#closeMark((this.#openMarks[i] as OpenMark).mark)
}
const target = searchNode(child)
/// @internal
#runNode = (node: Node, next?: Node) => {
const marks = this.#orderMarks(node.marks)
const unPreventNext = marks.every((mark) => !this.#runProseMark(mark, node))
if (unPreventNext) this.#runProseNode(node)
if (!target) return child
const tmp = target.children ? [...target.children] : undefined
const node = { ...child, children: tmp }
node.children = tmp
target.children = [node]
return target
this.#closeEndedMarks(next)
}

@@ -142,3 +151,2 @@

if (last && last.isMark && child.isMark) {
child = this.#searchType(child, last.type)
const { children: currChildren, ...currRest } = child

@@ -195,24 +203,15 @@ const { children: prevChildren, ...prevRest } = last

const children = element.children
let first = -1
let last = -1
const findIndex = (node: MarkdownNode[]) => {
if (!node) return
node.forEach((child, index) => {
if (child.type === 'text' && child.value) {
if (first < 0) first = index
last = index
}
})
}
if (children) {
findIndex(children)
const lastChild = children?.[last] as
const firstChild = children[0] as
| (MarkdownNode & { value: string })
| undefined
const firstChild = children?.[first] as
const lastChild = children.at(-1) as
| (MarkdownNode & { value: string })
| undefined
if (lastChild && lastChild.value.endsWith(' ')) {
if (
lastChild &&
lastChild.type === 'text' &&
lastChild.value.endsWith(' ')
) {
const text = lastChild.value

@@ -223,3 +222,7 @@ const trimmed = text.trimEnd()

}
if (firstChild && firstChild.value.startsWith(' ')) {
if (
firstChild &&
firstChild.type === 'text' &&
firstChild.value.startsWith(' ')
) {
const text = firstChild.value

@@ -297,7 +300,7 @@ const trimmed = text.trimStart()

) => {
const isIn = mark.isInSet(this.#marks)
const isIn = this.#openMarks.some((x) => x.mark.eq(mark))
if (isIn) return this
this.#marks = mark.addToSet(this.#marks)
this.#openMarks.push({ mark, spanning: value == null })
return this.openNode(type, value, { ...props, isMark: true })

@@ -308,7 +311,13 @@ }

#closeMark = (mark: Mark): void => {
const isIn = mark.isInSet(this.#marks)
let index = -1
for (let i = this.#openMarks.length - 1; i >= 0; i--) {
if ((this.#openMarks[i] as OpenMark).mark.eq(mark)) {
index = i
break
}
}
if (!isIn) return
if (index < 0) return
this.#marks = mark.type.removeFromSet(this.#marks)
this.#openMarks.splice(index, 1)
this.#closeNodeAndPush(true)

@@ -318,3 +327,4 @@ }

/// Open a new mark, the next nodes added will have that mark.
/// The mark will be closed automatically.
/// The mark will be kept open across nodes that share it and
/// closed automatically when it no longer applies.
withMark = (mark: Mark, type: string, value?: string, props?: JSONRecord) => {

@@ -344,6 +354,8 @@ this.#openMark(mark, type, value, props)

/// the state will find a proper runner (by `match` method in serializer spec) to handle it.
/// When a node list is given, marks shared between adjacent nodes are kept
/// open across them so that they are serialized as one continuous span.
next = (nodes: Node | Fragment) => {
if (isFragment(nodes)) {
nodes.forEach((node) => {
this.#runNode(node)
nodes.forEach((node, _offset, index) => {
this.#runNode(node, nodes.maybeChild(index + 1) ?? undefined)
})

@@ -362,2 +374,3 @@ return this

run = (tree: Node) => {
this.#openMarks = []
this.next(tree)

@@ -364,0 +377,0 @@

Sorry, the diff of this file is not supported yet