prosemirror 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1006b6a2ecb95f5a4d603d5d9321bbdab419f26f882365ff14d1692f27970ce7
4
- data.tar.gz: 22734518a2e81ecaba0ff64ed5057f40705dfb55dcd919567673f88e7cbf7e45
3
+ metadata.gz: 9a11c83675790681267f7d5282882f967154892b408d8965e528b1c34d166876
4
+ data.tar.gz: 14ae8422a61cbe0be055d6ef7590a10981014430978089e14b2805ebf3100be2
5
5
  SHA512:
6
- metadata.gz: 0f4b24b76e78e49523f068aa8520304c955428d4ff295c0e0f918dfad07ad0bc5b7ee391b8dacaeabf258f871d99f2dcfba15e4ebad4bec961e97529cdd24f34
7
- data.tar.gz: be218156ef3b417342de7d393d219b5019fe6f270a9c865e5cce9b5f9a2436008f59d5ada72d0111e09152586a06e21a7e98653ce4002923049eb0e70331daa0
6
+ metadata.gz: 02ac22023a65ba9631752cf6c42b9895cfb4be99acc4d2b1922d0a9742e5d61c7d084455b0c6aec2f0c80400531870113b2ceba519f0a7a4af701de7e192c1f0
7
+ data.tar.gz: 8adae01f43c5ea57367db4d77605ceb28b4d606829b905440a716c64dc483c82a105898878349bb79f1fd73df4459651e9172d67546ee1d23968fd8cd3538801
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-10-24
4
+
5
+ - Import Markdown with `Prosemirror.from_markdown` (or `schema.parse_markdown`). Needs the [`commonmarker`](https://github.com/gjtorikian/commonmarker) gem, which isn't installed with this gem. GitHub Flavored Markdown tables, strikethrough and autolinks are supported, and the schema's parse rules apply, so custom types can match Markdown too. Raw HTML goes through HTML import when the herb gem is installed. YAML front matter is dropped.
6
+ - Import plain text with `Prosemirror.from_text` (or `schema.parse_text`): blank lines separate paragraphs, and other line breaks become hard breaks.
7
+ - HTML, Markdown and text import accept nil and binary strings, and replace invalid UTF-8 with U+FFFD, as browsers do. HTML import raised on invalid UTF-8.
8
+
3
9
  ## [0.2.0] - 2026-10-23
4
10
 
5
11
  - Import HTML with `Prosemirror.from_html` (or `schema.parse_html`), as ProseMirror's `DOMParser` does. Needs the [`herb`](https://herb-tools.dev) gem, which isn't installed with this gem.
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # ProseMirror
2
2
 
3
- Render [ProseMirror](https://prosemirror.net) and [Tiptap](https://tiptap.dev) documents to HTML and text, and import HTML into them, in Ruby.
3
+ Render [ProseMirror](https://prosemirror.net) and [Tiptap](https://tiptap.dev) documents to HTML and text, and import HTML, Markdown and text into them, in Ruby.
4
4
 
5
5
  - HTML is built with [Phlex](https://www.phlex.fun) and escaped as it is written.
6
6
  - Attributes are typed with [Literal](https://literal.fun).
7
7
  - Link and image URLs must use an allowed scheme.
8
8
  - No Rails dependency.
9
9
 
10
- This gem replaces [prosemirror_to_html](https://github.com/etaminstudio/prosemirror_to_html) and [html_to_prosemirror](https://github.com/etaminstudio/html_to_prosemirror). Markdown import is planned: see the [roadmap](ROADMAP.md).
10
+ This gem replaces [prosemirror_to_html](https://github.com/etaminstudio/prosemirror_to_html) and [html_to_prosemirror](https://github.com/etaminstudio/html_to_prosemirror). See the [roadmap](ROADMAP.md) for what comes next.
11
11
 
12
12
  ## Installation
13
13
 
@@ -70,6 +70,35 @@ It works like ProseMirror's `DOMParser`, and for the built-in types gives the sa
70
70
  - entities are decoded, and ERB tags (`<%= … %>`) are kept as text;
71
71
  - invalid attributes are fixed and reported, as in [Invalid documents](#invalid-documents).
72
72
 
73
+ ## Markdown import
74
+
75
+ Markdown import uses [commonmarker](https://github.com/gjtorikian/commonmarker)'s parser, written in Rust. Add it to your Gemfile:
76
+
77
+ ```sh
78
+ bundle add commonmarker
79
+ ```
80
+
81
+ ```ruby
82
+ doc = Prosemirror.from_markdown("## Hello\n\nSome **bold** text.")
83
+ ```
84
+
85
+ It reads GitHub Flavored Markdown: tables, strikethrough and autolinks. Each Markdown node becomes the element commonmarker renders for it, and the schema's parse rules apply as in HTML import, so custom types can match Markdown too: a type that matches `img` also gets Markdown images. For the built-in types, it gives the same JSON as rendering the Markdown to HTML and importing it in Tiptap, except that:
86
+
87
+ - code blocks lose their final newline, as in prosemirror-markdown;
88
+ - task list items (`- [ ] todo`) stay as list items starting with `[ ] ` or `[x] `.
89
+
90
+ Raw HTML in Markdown goes through HTML import when the herb gem is installed, and stays as text otherwise.
91
+
92
+ YAML front matter, between `---` lines at the start, is dropped. Footnotes aren't supported yet: `[^1]: Note` reads as a link definition, as in CommonMark. Markdown nested more than 1,000 levels deep raises `Prosemirror::Error`.
93
+
94
+ ## Text import
95
+
96
+ ```ruby
97
+ doc = Prosemirror.from_text("First line\nsecond line\n\nNew paragraph")
98
+ ```
99
+
100
+ Blank lines separate paragraphs, and other line breaks become hard breaks. Spaces are kept.
101
+
73
102
  ## Schemas
74
103
 
75
104
  Two schemas are included:
@@ -163,7 +192,15 @@ Prosemirror::Schema.tiptap(
163
192
 
164
193
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests, and `rake` to also run [Standard](https://github.com/standardrb/standard). You can also run `bin/console` for an interactive prompt that will allow you to experiment.
165
194
 
166
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
195
+ To install this gem onto your local machine, run `bundle exec rake install`.
196
+
197
+ To release a new version, update the version number in `version.rb` and the changelog, and merge them into main. Then tag the version and push the tag:
198
+
199
+ ```sh
200
+ git tag v0.3.0 && git push origin v0.3.0
201
+ ```
202
+
203
+ The [Push gem](.github/workflows/push_gem.yml) workflow publishes the gem to [rubygems.org](https://rubygems.org) with [trusted publishing](https://guides.rubygems.org/trusted-publishing/), after approval in the `release` environment.
167
204
 
168
205
  ## Contributing
169
206
 
data/ROADMAP.md CHANGED
@@ -169,11 +169,11 @@ They differ in more than names:
169
169
  - Old HTML mapped to the schema: `<b>` and `<i>` to `bold` and `italic`, `<strike>` to `strike`, `<tt>` to `code`. `<font>` and `<center>` are unwrapped.
170
170
  - Tests: HTML from Tiptap gives the same JSON as Tiptap.
171
171
 
172
- ### 0.3: Markdown and text import
172
+ ### 0.3: Markdown and text import (released)
173
173
 
174
- - `Prosemirror.from_markdown`, built from [`commonmarker`](https://github.com/gjtorikian/commonmarker)'s syntax tree, without going through HTML (like `prosemirror-markdown`). commonmarker's parser is written in Rust.
175
- - GitHub Flavored Markdown: tables, strikethrough, autolinks, task lists.
176
- - Custom node types can define their Markdown mapping.
174
+ - `Prosemirror.from_markdown`, built from [`commonmarker`](https://github.com/gjtorikian/commonmarker)'s syntax tree. commonmarker's parser is written in Rust. Each Markdown node becomes the element commonmarker renders for it, without going through an HTML string, and HTML import's parse rules build the document.
175
+ - GitHub Flavored Markdown: tables, strikethrough, autolinks. Task list items stay as text until task list nodes (0.6).
176
+ - Custom node types match Markdown with their parse rules.
177
177
  - Raw HTML in Markdown goes through the HTML importer if Herb is installed.
178
178
  - `Prosemirror.from_text`: blank lines make paragraphs, line breaks make hard breaks.
179
179
 
data/TYPES.md CHANGED
@@ -26,6 +26,7 @@ A type is a [Phlex](https://www.phlex.fun) component, and its attributes are [Li
26
26
  - [Attributes](#attributes)
27
27
  - [Rendering](#rendering)
28
28
  - [Parse rules](#parse-rules)
29
+ - [Markdown](#markdown)
29
30
  - [Subclassing built-in types](#subclassing-built-in-types)
30
31
 
31
32
  ## Names
@@ -186,6 +187,22 @@ The element passed to rules is a `Prosemirror::HTML::Element`, with lowercase na
186
187
  | `matches?("p.lead")` | Whether the element matches a selector. |
187
188
  | `parent` | The parent element. |
188
189
 
190
+ ## Markdown
191
+
192
+ Markdown import builds the elements commonmarker renders for each Markdown node, and applies the same parse rules. To match Markdown, a type matches these elements:
193
+
194
+ | Markdown | Element |
195
+ |---|---|
196
+ | Paragraph, heading, blockquote, thematic break | `p`, `h1`…`h6`, `blockquote`, `hr` |
197
+ | Bullet list, ordered list, list item | `ul`, `ol` (with `start` when it isn't 1), `li` |
198
+ | Code block | `pre` holding a `code` with a `language-…` class from the info string |
199
+ | Table | `table`, `tr`, `th` for the first row, `td`, with a `text-align` style for aligned columns |
200
+ | Emphasis, strong, strikethrough, inline code | `em`, `strong`, `s`, `code` |
201
+ | Link, image | `a` with `href` and `title`, `img` with `src`, `alt` and `title` |
202
+ | Hard line break | `br` |
203
+
204
+ Raw HTML in Markdown goes through HTML import when the herb gem is installed, and stays as text otherwise.
205
+
189
206
  ## Subclassing built-in types
190
207
 
191
208
  A subclass keeps the name, options, attributes and parse rules of its parent, and can change any of them. Register it with `Schema#with`, which replaces the built-in type with the same name:
@@ -22,11 +22,19 @@ module Prosemirror
22
22
  raise Error, 'HTML import needs the herb gem: add `gem "herb"` to your Gemfile'
23
23
  end
24
24
 
25
+ # Whether the herb gem is installed.
26
+ def self.herb?
27
+ load_herb
28
+ true
29
+ rescue Error
30
+ false
31
+ end
32
+
25
33
  # A root Element holding the parsed content.
26
34
  def call(html)
27
35
  self.class.load_herb
28
36
  root = Element.new("#document")
29
- Splitter.new(PIECE_SIZE).call(html.to_s).each do |piece|
37
+ Splitter.new(PIECE_SIZE).call(UTF8.string(html)).each do |piece|
30
38
  # Without `timeout: 0`, Herb stops after a second and returns what it parsed so far.
31
39
  build(Herb.parse(piece, track_locations: false, strict: false, timeout: 0).value.children, root)
32
40
  end
@@ -41,8 +41,13 @@ module Prosemirror
41
41
 
42
42
  # The document parsed from the HTML, as its top node.
43
43
  def parse(html)
44
+ parse_element(HTML::Builder.new.call(html))
45
+ end
46
+
47
+ # The document for an Element's content, like the one HTML::Builder gives.
48
+ def parse_element(root)
44
49
  context = Context.new(self)
45
- context.add_all(HTML::Builder.new.call(html), [])
50
+ context.add_all(root, [])
46
51
  context.finish
47
52
  end
48
53
 
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosemirror
4
+ module Markdown
5
+ # Builds the Elements and Texts of HTML import from Markdown, with commonmarker's syntax tree
6
+ # (https://github.com/gjtorikian/commonmarker), so that the schema's parse rules apply to Markdown too.
7
+ # Each Markdown node becomes the element commonmarker renders for it, without going through HTML.
8
+ #
9
+ # - GitHub Flavored Markdown: tables, strikethrough and autolinks. Task list items stay as text (`[ ] `).
10
+ # - Raw HTML can span several Markdown blocks, like `<del>` around a paragraph: with the herb gem, a
11
+ # document holding raw HTML is rendered to HTML by commonmarker and imported. Without it, raw HTML
12
+ # stays as text.
13
+ # - Code blocks lose their final newline, as in prosemirror-markdown.
14
+ # - YAML front matter, between `---` lines at the start, is dropped.
15
+ class Builder
16
+ MAX_DEPTH = HTML::Builder::MAX_DEPTH
17
+ EXTENSIONS = {
18
+ strikethrough: true, table: true, autolink: true, tasklist: false, shortcodes: false, header_ids: nil,
19
+ front_matter_delimiter: "---"
20
+ }.freeze
21
+ RENDER_OPTIONS = {extension: EXTENSIONS, render: {unsafe: true, hardbreaks: false, github_pre_lang: false}}.freeze
22
+ TAGS = {
23
+ paragraph: "p", block_quote: "blockquote", item: "li", thematic_break: "hr", table_row: "tr",
24
+ emph: "em", strong: "strong", strikethrough: "s", linebreak: "br"
25
+ }.freeze
26
+ # A table's delimiter row, like `| :-- | --: |`, at the end of its line.
27
+ DELIMITER_ROW = /\|?(?:\s*:?-+:?\s*\|)*\s*:?-+:?\s*\|?\s*\z/
28
+ # The bytes commonmarker escapes in URLs, as comrak's `escape_href`: all but these, and `%` when it
29
+ # doesn't start an escape. `&` and `'` are kept: comrak writes them as HTML references.
30
+ URL_UNSAFE = %r{%(?!\h\h)|[^-_.+!*(),#@?=;:/$~A-Za-z0-9&'%]}n
31
+ # An IPv6 host at the start of a URL, like `http://[::1]`, kept as it is.
32
+ IPV6_HOST = %r{\A[A-Za-z][A-Za-z0-9+.-]*://\[[\h:.]+\]}
33
+
34
+ def self.load_commonmarker
35
+ require "commonmarker"
36
+ rescue LoadError
37
+ raise Error, 'Markdown import needs the commonmarker gem: add `gem "commonmarker"` to your Gemfile'
38
+ end
39
+
40
+ # A root Element holding the content.
41
+ def call(markdown)
42
+ self.class.load_commonmarker
43
+ markdown = UTF8.string(markdown)
44
+ document = parse(markdown)
45
+ return from_html(document) if raw_html?(document) && HTML::Builder.herb?
46
+
47
+ @lines = markdown.lines
48
+ root = HTML::Element.new("#document")
49
+ build(document, root)
50
+ root
51
+ end
52
+
53
+ private
54
+
55
+ def parse(markdown)
56
+ Commonmarker.parse(markdown, options: {extension: EXTENSIONS})
57
+ rescue Commonmarker::NestingDepthExceededError => error
58
+ raise Error, "Markdown nested too deeply: #{error.message}"
59
+ end
60
+
61
+ # Walks the tree without recursion. Each item of the stack is a node, the element to add it to, and
62
+ # the depth of that element. Elements nested deeper than MAX_DEPTH are flattened.
63
+ def build(document, root)
64
+ stack = document.to_a.reverse.map { |node| [node, root, 0] }
65
+ until stack.empty?
66
+ node, target, depth = stack.pop
67
+ element = add(node, target)
68
+ next unless element
69
+
70
+ if depth >= MAX_DEPTH
71
+ element = target
72
+ else
73
+ target.append(element)
74
+ end
75
+ node.to_a.reverse_each { |child| stack << [child, element, element.equal?(target) ? depth : depth + 1] }
76
+ end
77
+ end
78
+
79
+ # Adds a node to the target. Returns the element to add its children to, when they need adding.
80
+ def add(node, target)
81
+ case node.type
82
+ when *TAGS.keys then HTML::Element.new(TAGS[node.type])
83
+ when :heading then HTML::Element.new("h#{node.header_level}")
84
+ when :list then list(node)
85
+ when :code_block then code_block(node, target)
86
+ when :table then table(node)
87
+ when :table_cell then table_cell(node)
88
+ when :link then HTML::Element.new("a", {"href" => escape_url(node.url), "title" => presence(node.title)}.compact)
89
+ when :image then image(node, target)
90
+ when :code then text(target, node.string_content, "code")
91
+ when :text then text(target, node.string_content)
92
+ when :softbreak then text(target, "\n")
93
+ when :html_block then text(target.append(HTML::Element.new("p")), node.literal.chomp)
94
+ when :html_inline then text(target, node.literal)
95
+ when :frontmatter then nil
96
+ else HTML::Element.new("span") # Unwrapped by HTML import, like escaped characters (`\*`).
97
+ end
98
+ end
99
+
100
+ def list(node)
101
+ return HTML::Element.new("ul") unless node.list_type == :ordered
102
+
103
+ HTML::Element.new("ol", (node.list_start == 1) ? {} : {"start" => node.list_start.to_s})
104
+ end
105
+
106
+ def code_block(node, target)
107
+ language = node.fence_info.to_s.split.first
108
+ code = HTML::Element.new("code", language ? {"class" => "language-#{language}"} : {})
109
+ code.append(HTML::Text.new(node.string_content.chomp))
110
+ target.append(HTML::Element.new("pre")).append(code)
111
+ nil
112
+ end
113
+
114
+ def table(node)
115
+ @alignments = alignments(node)
116
+ @row = 0
117
+ HTML::Element.new("table")
118
+ end
119
+
120
+ # The first row holds header cells.
121
+ def table_cell(node)
122
+ index = cell_index(node)
123
+ @row += 1 if index.zero?
124
+ align = @alignments[index]
125
+ HTML::Element.new((@row == 1) ? "th" : "td", align ? {"style" => "text-align: #{align}"} : {})
126
+ end
127
+
128
+ def cell_index(node)
129
+ index = 0
130
+ index += 1 while (node = node.previous_sibling)
131
+ index
132
+ end
133
+
134
+ # commonmarker doesn't give the alignments of a table's columns: they are read from its delimiter row,
135
+ # the line after its first line.
136
+ def alignments(node)
137
+ row = @lines[node.source_position[:start_line]].to_s.chomp[DELIMITER_ROW].to_s
138
+ row.strip.delete_prefix("|").delete_suffix("|").split("|").map do |cell|
139
+ cell = cell.strip
140
+ if cell.start_with?(":") && cell.end_with?(":") then "center"
141
+ elsif cell.end_with?(":") then "right"
142
+ elsif cell.start_with?(":") then "left"
143
+ end
144
+ end
145
+ end
146
+
147
+ def image(node, target)
148
+ attributes = {"src" => escape_url(node.url), "alt" => plain_text(node), "title" => presence(node.title)}.compact
149
+ target.append(HTML::Element.new("img", attributes))
150
+ nil
151
+ end
152
+
153
+ def text(target, value, tag = nil)
154
+ target = target.append(HTML::Element.new(tag)) if tag
155
+ target.append(HTML::Text.new(value))
156
+ nil
157
+ end
158
+
159
+ def raw_html?(document)
160
+ document.walk { |node| return true if node.type == :html_block || node.type == :html_inline }
161
+ false
162
+ end
163
+
164
+ def from_html(document)
165
+ root = HTML::Builder.new.call(document.to_html(options: RENDER_OPTIONS, plugins: {syntax_highlighter: nil}))
166
+ chomp_code_blocks(root)
167
+ root
168
+ end
169
+
170
+ # The code blocks commonmarker renders, `<pre><code>`, not the `<pre>` elements of raw HTML.
171
+ def chomp_code_blocks(element)
172
+ element.children.each do |child|
173
+ next unless child.element?
174
+ next chomp_code_blocks(child) unless child.name == "pre"
175
+
176
+ code = child.children.first
177
+ next unless code&.element? && code.name == "code" && child.children.size == 1
178
+
179
+ last = code.children.last
180
+ code.children[-1] = HTML::Text.new(last.value.chomp).tap { |text| text.parent = code } if last.is_a?(HTML::Text)
181
+ end
182
+ end
183
+
184
+ # The URL as commonmarker renders it, with the characters URLs can't hold percent-encoded.
185
+ def escape_url(url)
186
+ host = url[IPV6_HOST].to_s
187
+ rest = url.delete_prefix(host).b.gsub(URL_UNSAFE) { |byte| byte.ord.zero? ? "%EF%BF%BD" : format("%%%02X", byte.ord) }
188
+ host + rest.force_encoding(Encoding::UTF_8)
189
+ end
190
+
191
+ def plain_text(node)
192
+ node.to_a.map { |child| %i[text code].include?(child.type) ? child.string_content : plain_text(child) }.join
193
+ end
194
+
195
+ def presence(value)
196
+ value unless value.nil? || value.empty?
197
+ end
198
+ end
199
+ end
200
+ end
@@ -117,6 +117,27 @@ module Prosemirror
117
117
  Document.new(HTMLParser.new(self).parse(html), schema: self)
118
118
  end
119
119
 
120
+ # Build a document from Markdown, with the parse rules of the schema's types. Needs the `commonmarker` gem.
121
+ def parse_markdown(markdown)
122
+ Document.new(HTMLParser.new(self).parse_element(Markdown::Builder.new.call(markdown)), schema: self)
123
+ end
124
+
125
+ # Build a document from plain text: blank lines separate paragraphs, and other line breaks become the
126
+ # schema's line break node, like `hard_break`, or spaces without one. Spaces are kept.
127
+ def parse_text(text)
128
+ paragraph = node_type(:paragraph) || @nodes.find { |type| textblock?(type) }
129
+ linebreak = @nodes.find(&:linebreak_replacement?)
130
+ separator = linebreak ? {"type" => linebreak.type_name.to_s} : {"type" => "text", "text" => " "}
131
+ blocks = UTF8.string(text).gsub(/\r\n?/, "\n").split(/\n[ \t]*\n/).map { |block| block.gsub(/\A\n+|\n+\z/, "") }
132
+ content = blocks.reject { |block| block.strip.empty? }.map do |block|
133
+ inline = block.split("\n").each_with_index.flat_map do |line, index|
134
+ [(separator if index.positive?), ({"type" => "text", "text" => line} unless line.empty?)].compact
135
+ end
136
+ {"type" => paragraph.type_name.to_s, "content" => inline}
137
+ end
138
+ parse({"type" => @top_node.to_s, "content" => content.empty? ? [{"type" => paragraph.type_name.to_s}] : content})
139
+ end
140
+
120
141
  # The type name as written in JSON.
121
142
  def json_type_name(name)
122
143
  (@case == :camel) ? Naming.camel(name) : name.to_s
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosemirror
4
+ # Imported HTML, Markdown and text, as valid UTF-8.
5
+ module UTF8
6
+ module_function
7
+
8
+ # The value as a UTF-8 String: nil becomes empty, other encodings are read as UTF-8 (a binary string
9
+ # from a file or an HTTP body), and invalid bytes become U+FFFD, as browsers do.
10
+ def string(value)
11
+ string = value.to_s
12
+ string = string.dup.force_encoding(Encoding::UTF_8) unless string.encoding == Encoding::UTF_8
13
+ string.valid_encoding? ? string : string.scrub
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prosemirror
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/prosemirror.rb CHANGED
@@ -10,7 +10,8 @@ require "zeitwerk"
10
10
 
11
11
  loader = Zeitwerk::Loader.for_gem
12
12
  loader.inflector.inflect(
13
- "html" => "HTML", "html_parser" => "HTMLParser", "html_renderer" => "HTMLRenderer", "json_parser" => "JSONParser"
13
+ "html" => "HTML", "html_parser" => "HTMLParser", "html_renderer" => "HTMLRenderer", "json_parser" => "JSONParser",
14
+ "utf8" => "UTF8"
14
15
  )
15
16
  loader.setup
16
17
 
@@ -45,5 +46,15 @@ module Prosemirror
45
46
  def from_html(html, schema: self.schema)
46
47
  schema.parse_html(html)
47
48
  end
49
+
50
+ # Build a document from Markdown, with the schema's parse rules. Needs the `commonmarker` gem.
51
+ def from_markdown(markdown, schema: self.schema)
52
+ schema.parse_markdown(markdown)
53
+ end
54
+
55
+ # Build a document from plain text: blank lines separate paragraphs, other line breaks become hard breaks.
56
+ def from_text(text, schema: self.schema)
57
+ schema.parse_text(text)
58
+ end
48
59
  end
49
60
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prosemirror
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans Lemuet
@@ -82,6 +82,7 @@ files:
82
82
  - lib/prosemirror/json_parser.rb
83
83
  - lib/prosemirror/mark.rb
84
84
  - lib/prosemirror/mark_type.rb
85
+ - lib/prosemirror/markdown/builder.rb
85
86
  - lib/prosemirror/marks/basic/code.rb
86
87
  - lib/prosemirror/marks/basic/em.rb
87
88
  - lib/prosemirror/marks/basic/link.rb
@@ -122,6 +123,7 @@ files:
122
123
  - lib/prosemirror/schema.rb
123
124
  - lib/prosemirror/text_serializer.rb
124
125
  - lib/prosemirror/type_definition.rb
126
+ - lib/prosemirror/utf8.rb
125
127
  - lib/prosemirror/version.rb
126
128
  - sig/prosemirror.rbs
127
129
  homepage: https://github.com/etaminstudio/prosemirror