Skip to content

Commit 83e2be0

Browse files
author
Greg MacWilliam
authored
HTML Renderer (#6)
build out HTML renderer.
1 parent 6803e7e commit 83e2be0

File tree

7 files changed

+694
-50
lines changed

7 files changed

+694
-50
lines changed

README.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,152 @@ Retain operations have a Number `retain` key defined representing the number of
9999
{ retain: 5, attributes: { bold: null } }
100100
```
101101

102+
## HTML Formatting
103+
104+
Rich-text deltas may be formatted as HTML by calling `delta.to_html`. The rendered markup will be generated based on formatting rules configured for the `RichText` module.
105+
106+
### Inline formats
107+
108+
Inline formatting rules are used to build tags for the flow of content elements.
109+
110+
```ruby
111+
# Config:
112+
RichText.configure do |c|
113+
c.html_default_block_format = 'p'
114+
c.html_inline_formats = {
115+
bold: { tag: 'strong' },
116+
italic: { tag: 'em' },
117+
br: { tag: 'br' },
118+
link: { tag: 'a', apply: ->(el, op, ctx){ el[:href] = op.attributes[:link] } }
119+
}
120+
end
121+
122+
# Delta:
123+
[
124+
{ insert: "a man," },
125+
{ insert: "\n", attributes: { br: true } },
126+
{ insert: "a plan", attributes: { bold: true, italic: true } },
127+
{ insert: "\n" },
128+
{ insert: "panama\n", attributes: { link: 'https://visitpanama.com' } }
129+
]
130+
131+
# HTML result:
132+
%(
133+
<p>a man,<br><strong><em>a plan</em></strong></p>
134+
<p><a href="https://visitpanama.com">panama</a></p>
135+
)
136+
```
137+
138+
Each newline (`"\n"`) character denotes a block separation, at which time the inline flow will be wrapped in a block tag specified by `html_default_block_format`. An inline element's block wrapper maybe customized with the `block_format` setting, or omitted with the `omit_block` setting. For soft or visible line breaks such as `br` or `hr` tags, you may assign them inline formats to render them as content flow.
139+
140+
```ruby
141+
# Config:
142+
RichText.configure do |c|
143+
c.html_default_block_format = 'p'
144+
c.html_inline_formats = {
145+
hr: { tag: 'hr', omit_block: true },
146+
code: { tag: 'code', block_format: 'div' }
147+
}
148+
end
149+
150+
# Delta:
151+
[
152+
{ insert: "sample code" },
153+
{ insert: "\n", attributes: { hr: true } },
154+
{ insert: "published = true", attributes: { code: true } },
155+
{ insert: "\n" }
156+
]
157+
158+
# HTML result:
159+
%(
160+
<p>sample code</p>
161+
<hr>
162+
<div><code>published = true</code></div>
163+
)
164+
```
165+
166+
### Block formats
167+
168+
Block tags are wrapped around a flow of elements whenever a newline is encountered (unless it has an inline format). Block formats should always apply to newline (`"\n"`) inserts.
169+
170+
```ruby
171+
RichText.configure do |c|
172+
c.html_block_formats = {
173+
firstheader: { tag: 'h1' },
174+
bullet: { tag: 'li', parent: 'ul' },
175+
id: { apply: ->(el, op, ctx){ el[:id] = op.attributes[:id] } }
176+
}
177+
end
178+
179+
# Delta:
180+
[
181+
{ insert: "Blocks are fun" },
182+
{ insert: "\n", attributes: { firstheader: true, id: 'blockfun' } },
183+
{ insert: "item 1" },
184+
{ insert: "\n", attributes: { bullet: true } },
185+
{ insert: "item 2" },
186+
{ insert: "\n", attributes: { bullet: true } }
187+
]
188+
189+
# HTML result:
190+
%(
191+
<h1 id="blockfun">Blocks are fun</h1>
192+
<ul>
193+
<li>item 1</li>
194+
<li>item 2</li>
195+
</ul>
196+
)
197+
```
198+
199+
Block tags may define a `parent` tag, or an array of parents. When a block has a parent, its full parent tree is constructed and/or merged with a compatible node tree that preceeds it.
200+
201+
### Formatting lambdas
202+
203+
Use `tag` and `apply` lambdas to customize tag structures.
204+
205+
```ruby
206+
# Config:
207+
RichText.configure do |c|
208+
c.html_default_block_format = 'p'
209+
c.html_inline_formats = {
210+
image: {
211+
omit_block: true,
212+
tag: ->(el, op, ctx){
213+
el.name = 'figure'
214+
el.add_child(%(<img src="#{ op.value[:image][:src] }">))
215+
el.add_child(%(<figcaption>#{ op.value[:image][:caption] }</figcaption>))
216+
el
217+
}
218+
},
219+
link: {
220+
tag: 'a',
221+
apply: ->(el, op, ctx){ el[:href] = op.attributes[:link] }
222+
}
223+
}
224+
end
225+
226+
# Delta:
227+
[
228+
{ insert: { image: { src: 'https://placekitten.com/100/100', caption: 'cute' } } },
229+
{ insert: "\n" },
230+
{ insert: "more kittens", attributes: { link: 'https://placekitten.com' } },
231+
{ insert: "\n" }
232+
]
233+
234+
# HTML result:
235+
%(
236+
<figure>
237+
<img src="https://placekitten.com/100/100">
238+
<figcaption>cute</figcaption>
239+
</figure>
240+
<p><a href="https://placekitten.com">more kittens</a></p>
241+
)
242+
```
243+
244+
A `tag` lambda is called once when an element is created. The tag lambda returns a customized node structure, or nil to render nothing. An `apply` lambda is called for each formatting rule applied to an element. An apply lambda does not return a value.
245+
246+
**Both `tag` and `apply` receive the same arguments:**
247+
248+
- `el`: the [Nokogiri::XML::Node](https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Node) instance being rendered. For `tag` lambdas, this will be a new `span`.
249+
- `op`: the `RichText::Op` instance being rendered. You may references its `attributes` and `value`.
250+
- `ctx`: an optional context object passed via `delta.to_html(context: obj)`. Providing a render context allows data to be shared across all formatting functions.

lib/rich-text.rb

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,24 @@ def self.configure
1414
require 'rich-text/html'
1515

1616
RichText.configure do |c|
17-
c.safe_mode = true
18-
c.html_default_block_tag = 'p'
19-
c.html_block_tags = {
20-
firstheader: 'h1',
21-
secondheader: 'h2',
22-
thirdheader: 'h3',
23-
list: ->(content, value) { %(<ol><li>#{value}</li></ol>) },
24-
bullet: ->(content, value) { %(<ul><li>#{value}</li></ul>) }
17+
18+
c.html_inline_formats = {
19+
bold: { tag: 'strong' },
20+
br: { tag: 'br' },
21+
hr: { tag: 'hr', block_format: false },
22+
italic: { tag: 'em' },
23+
link: { tag: 'a', apply: ->(el, op, ctx){ el[:href] = op.attributes[:link] } }
2524
}
26-
c.html_inline_tags = {
27-
bold: 'strong',
28-
italic: 'em',
29-
link: ->(content, value) { %(<a href="#{value}">#{content}</a>) }
25+
26+
c.html_block_formats = {
27+
firstheader: { tag: 'h1' },
28+
secondheader: { tag: 'h2' },
29+
thirdheader: { tag: 'h3' },
30+
bullet: { tag: 'li', parent: 'ul' },
31+
list: { tag: 'li', parent: 'ol' },
32+
id: { apply: ->(el, op, ctx){ el[:id] = op.attributes[:id] } }
3033
}
34+
35+
c.html_default_block_format = 'p'
36+
c.safe_mode = true
3137
end

lib/rich-text/config.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module RichText
22
# @api private
33
class Config
4-
attr_accessor :safe_mode, :html_default_block_tag, :html_block_tags, :html_inline_tags
4+
attr_accessor :safe_mode, :html_inline_formats, :html_block_formats, :html_default_block_format
55
end
66
end

0 commit comments

Comments
 (0)