@depup/prosemirror-markdown 1.13.4-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@depup/prosemirror-markdown",
3
+ "version": "1.13.4-depup.0",
4
+ "description": "[DepUp] ProseMirror Markdown integration",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ },
13
+ "sideEffects": false,
14
+ "license": "MIT",
15
+ "maintainers": [
16
+ {
17
+ "name": "Marijn Haverbeke",
18
+ "email": "marijn@haverbeke.berlin",
19
+ "web": "http://marijnhaverbeke.nl"
20
+ }
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git://github.com/prosemirror/prosemirror-markdown.git"
25
+ },
26
+ "dependencies": {
27
+ "markdown-it": "^14.1.1",
28
+ "prosemirror-model": "^1.25.4",
29
+ "@types/markdown-it": "^14.1.2"
30
+ },
31
+ "devDependencies": {
32
+ "@prosemirror/buildhelper": "^0.1.5",
33
+ "prosemirror-test-builder": "^1.0.0",
34
+ "punycode": "^1.4.0"
35
+ },
36
+ "scripts": {
37
+ "test": "pm-runtests"
38
+ },
39
+ "keywords": [
40
+ "depup",
41
+ "dependency-bumped",
42
+ "updated-deps",
43
+ "prosemirror-markdown"
44
+ ],
45
+ "depup": {
46
+ "changes": {
47
+ "markdown-it": {
48
+ "from": "^14.0.0",
49
+ "to": "^14.1.1"
50
+ },
51
+ "prosemirror-model": {
52
+ "from": "^1.25.0",
53
+ "to": "^1.25.4"
54
+ },
55
+ "@types/markdown-it": {
56
+ "from": "^14.0.0",
57
+ "to": "^14.1.2"
58
+ }
59
+ },
60
+ "depsUpdated": 3,
61
+ "originalPackage": "prosemirror-markdown",
62
+ "originalVersion": "1.13.4",
63
+ "processedAt": "2026-03-17T16:38:11.090Z",
64
+ "smokeTest": "passed"
65
+ }
66
+ }
package/src/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # prosemirror-markdown
2
+
3
+ [ [**WEBSITE**](http://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror-markdown/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**GITTER**](https://gitter.im/ProseMirror/prosemirror) ]
4
+
5
+ This is a (non-core) module for [ProseMirror](http://prosemirror.net).
6
+ ProseMirror is a well-behaved rich semantic content editor based on
7
+ contentEditable, with support for collaborative editing and custom
8
+ document schemas.
9
+
10
+ This module implements a ProseMirror
11
+ [schema](https://prosemirror.net/docs/guide/#schema) that corresponds to
12
+ the document schema used by [CommonMark](http://commonmark.org/), and
13
+ a parser and serializer to convert between ProseMirror documents in
14
+ that schema and CommonMark/Markdown text.
15
+
16
+ This code is released under an
17
+ [MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE).
18
+ There's a [forum](http://discuss.prosemirror.net) for general
19
+ discussion and support requests, and the
20
+ [Github bug tracker](https://github.com/prosemirror/prosemirror/issues)
21
+ is the place to report issues.
22
+
23
+ We aim to be an inclusive, welcoming community. To make that explicit,
24
+ we have a [code of
25
+ conduct](http://contributor-covenant.org/version/1/1/0/) that applies
26
+ to communication around the project.
27
+
28
+ ## Documentation
29
+
30
+ @schema
31
+
32
+ @MarkdownParser
33
+
34
+ @ParseSpec
35
+
36
+ @defaultMarkdownParser
37
+
38
+ @MarkdownSerializer
39
+
40
+ @MarkdownSerializerState
41
+
42
+ @defaultMarkdownSerializer
@@ -0,0 +1,272 @@
1
+ // @ts-ignore
2
+ import MarkdownIt from "markdown-it"
3
+ import Token from "markdown-it/lib/token.mjs"
4
+ import {schema} from "./schema"
5
+ import {Mark, MarkType, Node, Attrs, Schema, NodeType} from "prosemirror-model"
6
+
7
+ function maybeMerge(a: Node, b: Node): Node | undefined {
8
+ if (a.isText && b.isText && Mark.sameSet(a.marks, b.marks))
9
+ return (a as any).withText(a.text! + b.text!)
10
+ }
11
+
12
+ // Object used to track the context of a running parse.
13
+ class MarkdownParseState {
14
+ stack: {type: NodeType, attrs: Attrs | null, content: Node[], marks: readonly Mark[]}[]
15
+
16
+ constructor(
17
+ readonly schema: Schema,
18
+ readonly tokenHandlers: {[token: string]: (stat: MarkdownParseState, token: Token, tokens: Token[], i: number) => void}
19
+ ) {
20
+ this.stack = [{type: schema.topNodeType, attrs: null, content: [], marks: Mark.none}]
21
+ }
22
+
23
+ top() {
24
+ return this.stack[this.stack.length - 1]
25
+ }
26
+
27
+ push(elt: Node) {
28
+ if (this.stack.length) this.top().content.push(elt)
29
+ }
30
+
31
+ // Adds the given text to the current position in the document,
32
+ // using the current marks as styling.
33
+ addText(text: string) {
34
+ if (!text) return
35
+ let top = this.top(), nodes = top.content, last = nodes[nodes.length - 1]
36
+ let node = this.schema.text(text, top.marks), merged
37
+ if (last && (merged = maybeMerge(last, node))) nodes[nodes.length - 1] = merged
38
+ else nodes.push(node)
39
+ }
40
+
41
+ // Adds the given mark to the set of active marks.
42
+ openMark(mark: Mark) {
43
+ let top = this.top()
44
+ top.marks = mark.addToSet(top.marks)
45
+ }
46
+
47
+ // Removes the given mark from the set of active marks.
48
+ closeMark(mark: MarkType) {
49
+ let top = this.top()
50
+ top.marks = mark.removeFromSet(top.marks)
51
+ }
52
+
53
+ parseTokens(toks: Token[]) {
54
+ for (let i = 0; i < toks.length; i++) {
55
+ let tok = toks[i]
56
+ let handler = this.tokenHandlers[tok.type]
57
+ if (!handler)
58
+ throw new Error("Token type `" + tok.type + "` not supported by Markdown parser")
59
+ handler(this, tok, toks, i)
60
+ }
61
+ }
62
+
63
+ // Add a node at the current position.
64
+ addNode(type: NodeType, attrs: Attrs | null, content?: readonly Node[]) {
65
+ let top = this.top()
66
+ let node = type.createAndFill(attrs, content, top ? top.marks : [])
67
+ if (!node) return null
68
+ this.push(node)
69
+ return node
70
+ }
71
+
72
+ // Wrap subsequent content in a node of the given type.
73
+ openNode(type: NodeType, attrs: Attrs | null) {
74
+ this.stack.push({type: type, attrs: attrs, content: [], marks: Mark.none})
75
+ }
76
+
77
+ // Close and return the node that is currently on top of the stack.
78
+ closeNode() {
79
+ let info = this.stack.pop()!
80
+ return this.addNode(info.type, info.attrs, info.content)
81
+ }
82
+ }
83
+
84
+ function attrs(spec: ParseSpec, token: Token, tokens: Token[], i: number) {
85
+ if (spec.getAttrs) return spec.getAttrs(token, tokens, i)
86
+ // For backwards compatibility when `attrs` is a Function
87
+ else if (spec.attrs instanceof Function) return spec.attrs(token)
88
+ else return spec.attrs
89
+ }
90
+
91
+ // Code content is represented as a single token with a `content`
92
+ // property in Markdown-it.
93
+ function noCloseToken(spec: ParseSpec, type: string) {
94
+ return spec.noCloseToken || type == "code_inline" || type == "code_block" || type == "fence"
95
+ }
96
+
97
+ function withoutTrailingNewline(str: string) {
98
+ return str[str.length - 1] == "\n" ? str.slice(0, str.length - 1) : str
99
+ }
100
+
101
+ function noOp() {}
102
+
103
+ function tokenHandlers(schema: Schema, tokens: {[token: string]: ParseSpec}) {
104
+ let handlers: {[token: string]: (stat: MarkdownParseState, token: Token, tokens: Token[], i: number) => void} =
105
+ Object.create(null)
106
+ for (let type in tokens) {
107
+ let spec = tokens[type]
108
+ if (spec.block) {
109
+ let nodeType = schema.nodeType(spec.block)
110
+ if (noCloseToken(spec, type)) {
111
+ handlers[type] = (state, tok, tokens, i) => {
112
+ state.openNode(nodeType, attrs(spec, tok, tokens, i))
113
+ state.addText(withoutTrailingNewline(tok.content))
114
+ state.closeNode()
115
+ }
116
+ } else {
117
+ handlers[type + "_open"] = (state, tok, tokens, i) => state.openNode(nodeType, attrs(spec, tok, tokens, i))
118
+ handlers[type + "_close"] = state => state.closeNode()
119
+ }
120
+ } else if (spec.node) {
121
+ let nodeType = schema.nodeType(spec.node)
122
+ handlers[type] = (state, tok, tokens, i) => state.addNode(nodeType, attrs(spec, tok, tokens, i))
123
+ } else if (spec.mark) {
124
+ let markType = schema.marks[spec.mark]
125
+ if (noCloseToken(spec, type)) {
126
+ handlers[type] = (state, tok, tokens, i) => {
127
+ state.openMark(markType.create(attrs(spec, tok, tokens, i)))
128
+ state.addText(withoutTrailingNewline(tok.content))
129
+ state.closeMark(markType)
130
+ }
131
+ } else {
132
+ handlers[type + "_open"] = (state, tok, tokens, i) => state.openMark(markType.create(attrs(spec, tok, tokens, i)))
133
+ handlers[type + "_close"] = state => state.closeMark(markType)
134
+ }
135
+ } else if (spec.ignore) {
136
+ if (noCloseToken(spec, type)) {
137
+ handlers[type] = noOp
138
+ } else {
139
+ handlers[type + "_open"] = noOp
140
+ handlers[type + "_close"] = noOp
141
+ }
142
+ } else {
143
+ throw new RangeError("Unrecognized parsing spec " + JSON.stringify(spec))
144
+ }
145
+ }
146
+
147
+ handlers.text = (state, tok) => state.addText(tok.content)
148
+ handlers.inline = (state, tok) => state.parseTokens(tok.children!)
149
+ handlers.softbreak = handlers.softbreak || (state => state.addText(" "))
150
+
151
+ return handlers
152
+ }
153
+
154
+ /// Object type used to specify how Markdown tokens should be parsed.
155
+ export interface ParseSpec {
156
+ /// This token maps to a single node, whose type can be looked up
157
+ /// in the schema under the given name. Exactly one of `node`,
158
+ /// `block`, or `mark` must be set.
159
+ node?: string
160
+
161
+ /// This token (unless `noCloseToken` is true) comes in `_open`
162
+ /// and `_close` variants (which are appended to the base token
163
+ /// name provides a the object property), and wraps a block of
164
+ /// content. The block should be wrapped in a node of the type
165
+ /// named to by the property's value. If the token does not have
166
+ /// `_open` or `_close`, use the `noCloseToken` option.
167
+ block?: string
168
+
169
+ /// This token (again, unless `noCloseToken` is true) also comes
170
+ /// in `_open` and `_close` variants, but should add a mark
171
+ /// (named by the value) to its content, rather than wrapping it
172
+ /// in a node.
173
+ mark?: string
174
+
175
+ /// Attributes for the node or mark. When `getAttrs` is provided,
176
+ /// it takes precedence.
177
+ attrs?: Attrs | null
178
+
179
+ /// A function used to compute the attributes for the node or mark
180
+ /// that takes a [markdown-it
181
+ /// token](https://markdown-it.github.io/markdown-it/#Token) and
182
+ /// returns an attribute object.
183
+ getAttrs?: (token: Token, tokenStream: Token[], index: number) => Attrs | null
184
+
185
+ /// Indicates that the [markdown-it
186
+ /// token](https://markdown-it.github.io/markdown-it/#Token) has
187
+ /// no `_open` or `_close` for the nodes. This defaults to `true`
188
+ /// for `code_inline`, `code_block` and `fence`.
189
+ noCloseToken?: boolean
190
+
191
+ /// When true, ignore content for the matched token.
192
+ ignore?: boolean
193
+ }
194
+
195
+ /// A configuration of a Markdown parser. Such a parser uses
196
+ /// [markdown-it](https://github.com/markdown-it/markdown-it) to
197
+ /// tokenize a file, and then runs the custom rules it is given over
198
+ /// the tokens to create a ProseMirror document tree.
199
+ export class MarkdownParser {
200
+ /// @internal
201
+ tokenHandlers: {[token: string]: (stat: MarkdownParseState, token: Token, tokens: Token[], i: number) => void}
202
+
203
+ /// Create a parser with the given configuration. You can configure
204
+ /// the markdown-it parser to parse the dialect you want, and provide
205
+ /// a description of the ProseMirror entities those tokens map to in
206
+ /// the `tokens` object, which maps token names to descriptions of
207
+ /// what to do with them. Such a description is an object, and may
208
+ /// have the following properties:
209
+ constructor(
210
+ /// The parser's document schema.
211
+ readonly schema: Schema,
212
+ /// This parser's markdown-it tokenizer.
213
+ readonly tokenizer: MarkdownIt,
214
+ /// The value of the `tokens` object used to construct this
215
+ /// parser. Can be useful to copy and modify to base other parsers
216
+ /// on.
217
+ readonly tokens: {[name: string]: ParseSpec}
218
+ ) {
219
+ this.tokenHandlers = tokenHandlers(schema, tokens)
220
+ }
221
+
222
+ /// Parse a string as [CommonMark](http://commonmark.org/) markup,
223
+ /// and create a ProseMirror document as prescribed by this parser's
224
+ /// rules.
225
+ ///
226
+ /// The second argument, when given, is passed through to the
227
+ /// [Markdown
228
+ /// parser](https://markdown-it.github.io/markdown-it/#MarkdownIt.parse).
229
+ parse(text: string, markdownEnv: Object = {}) {
230
+ let state = new MarkdownParseState(this.schema, this.tokenHandlers), doc
231
+ state.parseTokens(this.tokenizer.parse(text, markdownEnv))
232
+ do { doc = state.closeNode() } while (state.stack.length)
233
+ return doc || this.schema.topNodeType.createAndFill()!
234
+ }
235
+ }
236
+
237
+ function listIsTight(tokens: readonly Token[], i: number) {
238
+ while (++i < tokens.length)
239
+ if (tokens[i].type != "list_item_open") return tokens[i].hidden
240
+ return false
241
+ }
242
+
243
+ /// A parser parsing unextended [CommonMark](http://commonmark.org/),
244
+ /// without inline HTML, and producing a document in the basic schema.
245
+ export const defaultMarkdownParser = new MarkdownParser(schema, MarkdownIt("commonmark", {html: false}), {
246
+ blockquote: {block: "blockquote"},
247
+ paragraph: {block: "paragraph"},
248
+ list_item: {block: "list_item"},
249
+ bullet_list: {block: "bullet_list", getAttrs: (_, tokens, i) => ({tight: listIsTight(tokens, i)})},
250
+ ordered_list: {block: "ordered_list", getAttrs: (tok, tokens, i) => ({
251
+ order: +tok.attrGet("start")! || 1,
252
+ tight: listIsTight(tokens, i)
253
+ })},
254
+ heading: {block: "heading", getAttrs: tok => ({level: +tok.tag.slice(1)})},
255
+ code_block: {block: "code_block", noCloseToken: true},
256
+ fence: {block: "code_block", getAttrs: tok => ({params: tok.info || ""}), noCloseToken: true},
257
+ hr: {node: "horizontal_rule"},
258
+ image: {node: "image", getAttrs: tok => ({
259
+ src: tok.attrGet("src"),
260
+ title: tok.attrGet("title") || null,
261
+ alt: tok.children![0] && tok.children![0].content || null
262
+ })},
263
+ hardbreak: {node: "hard_break"},
264
+
265
+ em: {mark: "em"},
266
+ strong: {mark: "strong"},
267
+ link: {mark: "link", getAttrs: tok => ({
268
+ href: tok.attrGet("href"),
269
+ title: tok.attrGet("title") || null
270
+ })},
271
+ code_inline: {mark: "code", noCloseToken: true}
272
+ })
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // Defines a parser and serializer for [CommonMark](http://commonmark.org/) text.
2
+
3
+ export {schema} from "./schema"
4
+ export {defaultMarkdownParser, MarkdownParser, ParseSpec} from "./from_markdown"
5
+ export {MarkdownSerializer, defaultMarkdownSerializer, MarkdownSerializerState} from "./to_markdown"
package/src/schema.ts ADDED
@@ -0,0 +1,156 @@
1
+ import {Schema, MarkSpec} from "prosemirror-model"
2
+
3
+ /// Document schema for the data model used by CommonMark.
4
+ export const schema = new Schema({
5
+ nodes: {
6
+ doc: {
7
+ content: "block+"
8
+ },
9
+
10
+ paragraph: {
11
+ content: "inline*",
12
+ group: "block",
13
+ parseDOM: [{tag: "p"}],
14
+ toDOM() { return ["p", 0] }
15
+ },
16
+
17
+ blockquote: {
18
+ content: "block+",
19
+ group: "block",
20
+ parseDOM: [{tag: "blockquote"}],
21
+ toDOM() { return ["blockquote", 0] }
22
+ },
23
+
24
+ horizontal_rule: {
25
+ group: "block",
26
+ parseDOM: [{tag: "hr"}],
27
+ toDOM() { return ["div", ["hr"]] }
28
+ },
29
+
30
+ heading: {
31
+ attrs: {level: {default: 1}},
32
+ content: "(text | image)*",
33
+ group: "block",
34
+ defining: true,
35
+ parseDOM: [{tag: "h1", attrs: {level: 1}},
36
+ {tag: "h2", attrs: {level: 2}},
37
+ {tag: "h3", attrs: {level: 3}},
38
+ {tag: "h4", attrs: {level: 4}},
39
+ {tag: "h5", attrs: {level: 5}},
40
+ {tag: "h6", attrs: {level: 6}}],
41
+ toDOM(node) { return ["h" + node.attrs.level, 0] }
42
+ },
43
+
44
+ code_block: {
45
+ content: "text*",
46
+ group: "block",
47
+ code: true,
48
+ defining: true,
49
+ marks: "",
50
+ attrs: {params: {default: ""}},
51
+ parseDOM: [{tag: "pre", preserveWhitespace: "full", getAttrs: node => (
52
+ {params: (node as HTMLElement).getAttribute("data-params") || ""}
53
+ )}],
54
+ toDOM(node) { return ["pre", node.attrs.params ? {"data-params": node.attrs.params} : {}, ["code", 0]] }
55
+ },
56
+
57
+ ordered_list: {
58
+ content: "list_item+",
59
+ group: "block",
60
+ attrs: {order: {default: 1}, tight: {default: false}},
61
+ parseDOM: [{tag: "ol", getAttrs(dom) {
62
+ return {order: (dom as HTMLElement).hasAttribute("start") ? +(dom as HTMLElement).getAttribute("start")! : 1,
63
+ tight: (dom as HTMLElement).hasAttribute("data-tight")}
64
+ }}],
65
+ toDOM(node) {
66
+ return ["ol", {start: node.attrs.order == 1 ? null : node.attrs.order,
67
+ "data-tight": node.attrs.tight ? "true" : null}, 0]
68
+ }
69
+ },
70
+
71
+ bullet_list: {
72
+ content: "list_item+",
73
+ group: "block",
74
+ attrs: {tight: {default: false}},
75
+ parseDOM: [{tag: "ul", getAttrs: dom => ({tight: (dom as HTMLElement).hasAttribute("data-tight")})}],
76
+ toDOM(node) { return ["ul", {"data-tight": node.attrs.tight ? "true" : null}, 0] }
77
+ },
78
+
79
+ list_item: {
80
+ content: "block+",
81
+ defining: true,
82
+ parseDOM: [{tag: "li"}],
83
+ toDOM() { return ["li", 0] }
84
+ },
85
+
86
+ text: {
87
+ group: "inline"
88
+ },
89
+
90
+ image: {
91
+ inline: true,
92
+ attrs: {
93
+ src: {},
94
+ alt: {default: null},
95
+ title: {default: null}
96
+ },
97
+ group: "inline",
98
+ draggable: true,
99
+ parseDOM: [{tag: "img[src]", getAttrs(dom) {
100
+ return {
101
+ src: (dom as HTMLElement).getAttribute("src"),
102
+ title: (dom as HTMLElement).getAttribute("title"),
103
+ alt: (dom as HTMLElement).getAttribute("alt")
104
+ }
105
+ }}],
106
+ toDOM(node) { return ["img", node.attrs] }
107
+ },
108
+
109
+ hard_break: {
110
+ inline: true,
111
+ group: "inline",
112
+ selectable: false,
113
+ parseDOM: [{tag: "br"}],
114
+ toDOM() { return ["br"] }
115
+ }
116
+ },
117
+
118
+ marks: {
119
+ em: {
120
+ parseDOM: [
121
+ {tag: "i"}, {tag: "em"},
122
+ {style: "font-style=italic"},
123
+ {style: "font-style=normal", clearMark: m => m.type.name == "em"}
124
+ ],
125
+ toDOM() { return ["em"] }
126
+ },
127
+
128
+ strong: {
129
+ parseDOM: [
130
+ {tag: "strong"},
131
+ {tag: "b", getAttrs: node => node.style.fontWeight != "normal" && null},
132
+ {style: "font-weight=400", clearMark: m => m.type.name == "strong"},
133
+ {style: "font-weight", getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}
134
+ ],
135
+ toDOM() { return ["strong"] }
136
+ } as MarkSpec,
137
+
138
+ link: {
139
+ attrs: {
140
+ href: {},
141
+ title: {default: null}
142
+ },
143
+ inclusive: false,
144
+ parseDOM: [{tag: "a[href]", getAttrs(dom) {
145
+ return {href: (dom as HTMLElement).getAttribute("href"), title: dom.getAttribute("title")}
146
+ }}],
147
+ toDOM(node) { return ["a", node.attrs] }
148
+ },
149
+
150
+ code: {
151
+ code: true,
152
+ parseDOM: [{tag: "code"}],
153
+ toDOM() { return ["code"] }
154
+ }
155
+ }
156
+ })