tiptap-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +89 -0
- data/lib/tip_tap/document.rb +47 -0
- data/lib/tip_tap/has_content.rb +49 -0
- data/lib/tip_tap/html_renderable.rb +60 -0
- data/lib/tip_tap/json_renderable.rb +44 -0
- data/lib/tip_tap/node.rb +18 -0
- data/lib/tip_tap/nodes/bullet_list.rb +19 -0
- data/lib/tip_tap/nodes/hard_break.rb +19 -0
- data/lib/tip_tap/nodes/heading.rb +20 -0
- data/lib/tip_tap/nodes/horizontal_rule.rb +19 -0
- data/lib/tip_tap/nodes/image.rb +23 -0
- data/lib/tip_tap/nodes/list_item.rb +19 -0
- data/lib/tip_tap/nodes/ordered_list.rb +23 -0
- data/lib/tip_tap/nodes/paragraph.rb +16 -0
- data/lib/tip_tap/nodes/task_item.rb +23 -0
- data/lib/tip_tap/nodes/task_list.rb +19 -0
- data/lib/tip_tap/nodes/text.rb +75 -0
- data/lib/tip_tap/plain_text_renderable.rb +10 -0
- data/lib/tip_tap/registry.rb +27 -0
- data/lib/tip_tap/version.rb +5 -0
- data/lib/tip_tap.rb +24 -0
- data/lib/tiptap.rb +3 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2347235752700026d263aa5a0616d89f99014c42da52edbf92c30374eb2cfded
|
4
|
+
data.tar.gz: d97eefd5023412988280b6edf6741186f310452ab657a02c62419296de680334
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79f61b79665a0a0d48cc69322c50b24d05a3b330c6f2a6dd8242f31dc628d8f9e1d69db06024b7bb642290c44a001df13379fcc45872a6c0f0f3e28df0bc4a3f
|
7
|
+
data.tar.gz: 4b5ab21878bccf69ccdc8dfdc3fe04bbc5e3a91a65140b6d2b1759bce3ed13a6169fb47d2697415cd5d27261fa27c11f1d40c2e0387a1f0e30cf38fc296244a0
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Chad Wilken
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Tiptap
|
2
|
+
|
3
|
+
A gem for parsing, generating, and rendering TipTap Documents and Nodes using Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
8
|
+
|
9
|
+
$ bundle add tiptap-ruby
|
10
|
+
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
12
|
+
|
13
|
+
$ gem install tiptap-ruby
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Parsing a TipTap Document
|
18
|
+
|
19
|
+
You can parse a TipTap Document so that you can interact with it to do things such as add content (Nodes) or render it as HTML, JSON, or plain text:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
document = TipTap::Document.from_json(tiptap_json)
|
23
|
+
```
|
24
|
+
|
25
|
+
### Generate a New Document
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
document = TipTap::Document.new
|
29
|
+
```
|
30
|
+
|
31
|
+
You can also pass a block and the new Document will be yielded to the block.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
TipTap::Document.new do |document|
|
35
|
+
# Do something with document
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
### Add Content to the Document
|
40
|
+
|
41
|
+
Now that you have a Document you can add content to it.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
document.heading(level: 1) do |heading|
|
45
|
+
heading.text("My Import Document", marks: [{type: "italic"}])
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Until the gem implements all of the node types and the documentation is complete, refer to the `Document` class to see the nodes that can be appended.
|
50
|
+
|
51
|
+
### Generate Output
|
52
|
+
|
53
|
+
Once you have a Document with some content you can render it to HTML, JSON, and plain text.
|
54
|
+
|
55
|
+
#### JSON
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
document.to_json # => { type: 'doc', content: […nodes]}
|
59
|
+
```
|
60
|
+
|
61
|
+
### HTML
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
document.to_html # => <div class="tiptap-document"><h1><em>My Important Document</em></h1></div>
|
65
|
+
```
|
66
|
+
|
67
|
+
### Plain Text
|
68
|
+
|
69
|
+
Rendering to plain text is useful if you want to search the contents of your TipTap content.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
document.to_plain_text # => My Important Document
|
73
|
+
```
|
74
|
+
|
75
|
+
## Development
|
76
|
+
|
77
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
78
|
+
|
79
|
+
To install this gem onto your local machine, run `bundle install`.
|
80
|
+
|
81
|
+
To release a new version, update the version number in `version.rb`, and then run `bin/release` or `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).
|
82
|
+
|
83
|
+
## Contributing
|
84
|
+
|
85
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/chadwilken/tiptap-ruby.
|
86
|
+
|
87
|
+
## License
|
88
|
+
|
89
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/html_renderable"
|
4
|
+
require "tip_tap/json_renderable"
|
5
|
+
require "tip_tap/plain_text_renderable"
|
6
|
+
require "tip_tap/has_content"
|
7
|
+
|
8
|
+
# This is the class that all child nodes will be added to.
|
9
|
+
# This is the root object for TipTap.
|
10
|
+
module TipTap
|
11
|
+
class Document
|
12
|
+
include JsonRenderable
|
13
|
+
include HtmlRenderable
|
14
|
+
include PlainTextRenderable
|
15
|
+
include HasContent
|
16
|
+
|
17
|
+
self.type_name = "doc"
|
18
|
+
self.html_tag = :div
|
19
|
+
self.html_class_name = "tiptap-document"
|
20
|
+
|
21
|
+
def heading(level: 1, &block)
|
22
|
+
raise ArgumentError, "Block required" if block.nil?
|
23
|
+
|
24
|
+
add_content(Nodes::Heading.new(level: level, &block))
|
25
|
+
end
|
26
|
+
|
27
|
+
def paragraph(&block)
|
28
|
+
add_content(Nodes::Paragraph.new(&block))
|
29
|
+
end
|
30
|
+
|
31
|
+
def task_list(&block)
|
32
|
+
raise ArgumentError, "Block required" if block.nil?
|
33
|
+
|
34
|
+
add_content(Nodes::TaskList.new(&block))
|
35
|
+
end
|
36
|
+
|
37
|
+
def bullet_list(&block)
|
38
|
+
raise ArgumentError, "Block required" if block.nil?
|
39
|
+
|
40
|
+
add_content(Nodes::BulletList.new(&block))
|
41
|
+
end
|
42
|
+
|
43
|
+
def image(src:)
|
44
|
+
add_content(Nodes::Image.new(src: src))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module HasContent
|
7
|
+
attr_reader :attrs, :content
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create a new document or node, optionally passing in attributes (attrs) and nodes
|
14
|
+
# @param nodes [Array] An array of nodes to add to this node
|
15
|
+
# @param attributes [Hash] A hash of attributes to add to this node e.g. { 'level' => 1 }
|
16
|
+
def initialize(content = [], **attributes)
|
17
|
+
# This will convert the attrs key to camelcase for example :image_id is converted into 'imageId'
|
18
|
+
@attrs = Hash(attributes).deep_transform_keys { |key| key.to_s.camelcase(:lower) }
|
19
|
+
@content = content
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_node(node_type)
|
24
|
+
content.find { |child| child.is_a?(node_type) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_content(node)
|
28
|
+
@content << node
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Create a new instance from a TipTap JSON object.
|
33
|
+
# All nodes are recursively parsed and converted to Ruby objects
|
34
|
+
# All nodes must be registered in the registry.
|
35
|
+
# @param json [Hash] The JSON object to parse
|
36
|
+
def from_json(json)
|
37
|
+
return new if json.nil?
|
38
|
+
|
39
|
+
json.deep_stringify_keys!
|
40
|
+
|
41
|
+
content = Array(json["content"]).map do |node|
|
42
|
+
TipTap.node_for(node["type"]).from_json(node)
|
43
|
+
end
|
44
|
+
|
45
|
+
new(content, **Hash(json["attrs"]))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module HtmlRenderable
|
7
|
+
include ActionView::Helpers::TextHelper
|
8
|
+
include ActionView::Helpers::AssetTagHelper
|
9
|
+
|
10
|
+
# ActionView::Helpers::TagHelper requires output_buffer accessor
|
11
|
+
# This is included by ActionView::Helpers::TextHelper
|
12
|
+
attr_accessor :output_buffer
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def html_tag=(name_or_proc)
|
20
|
+
@html_tag = name_or_proc
|
21
|
+
end
|
22
|
+
|
23
|
+
def html_tag
|
24
|
+
@html_tag
|
25
|
+
end
|
26
|
+
|
27
|
+
def html_class_name=(name_or_proc)
|
28
|
+
@html_class_name = name_or_proc
|
29
|
+
end
|
30
|
+
|
31
|
+
def html_class_name
|
32
|
+
@html_class_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def html_tag
|
37
|
+
tag = self.class.html_tag
|
38
|
+
case tag
|
39
|
+
when Proc
|
40
|
+
instance_eval(&tag)
|
41
|
+
else
|
42
|
+
tag
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def html_class_name
|
47
|
+
classes = self.class.html_class_name
|
48
|
+
case classes
|
49
|
+
when Proc
|
50
|
+
instance_eval(&classes)
|
51
|
+
else
|
52
|
+
classes
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_html
|
57
|
+
content_tag(html_tag, safe_join(content.map(&:to_html)), class: html_class_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/registry"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module JsonRenderable
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def type_name=(type_name)
|
13
|
+
@type_name = type_name
|
14
|
+
Registry.register(type_name, self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_name
|
18
|
+
@type_name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def type_name
|
23
|
+
self.class.type_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def include_empty_content_in_json?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Generate a JSON object that is useable by the editor
|
31
|
+
def to_json
|
32
|
+
json = {type: type_name}
|
33
|
+
json = json.merge(content: content.map(&:to_json)) if should_include_content?
|
34
|
+
json = json.merge(attrs: attrs.deep_symbolize_keys) if attrs.present?
|
35
|
+
json
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def should_include_content?
|
41
|
+
content.present? || (content.empty? && include_empty_content_in_json?)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/tip_tap/node.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/html_renderable"
|
4
|
+
require "tip_tap/json_renderable"
|
5
|
+
require "tip_tap/plain_text_renderable"
|
6
|
+
require "tip_tap/has_content"
|
7
|
+
|
8
|
+
# This is the base class for all TipTap nodes.
|
9
|
+
# It provides some basic functionality that all nodes need such as
|
10
|
+
# converting to HTML, JSON, and plain text
|
11
|
+
module TipTap
|
12
|
+
class Node
|
13
|
+
include HtmlRenderable
|
14
|
+
include JsonRenderable
|
15
|
+
include PlainTextRenderable
|
16
|
+
include HasContent
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class BulletList < Node
|
8
|
+
self.type_name = "bulletList"
|
9
|
+
self.html_tag = :ul
|
10
|
+
self.html_class_name = "bullet-list"
|
11
|
+
|
12
|
+
def list_item(&block)
|
13
|
+
raise ArgumentError, "Block required" if block.nil?
|
14
|
+
|
15
|
+
add_content(ListItem.new(&block))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class HardBreak < Node
|
8
|
+
self.type_name = "hardBreak"
|
9
|
+
|
10
|
+
def include_empty_content_in_json?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_html
|
15
|
+
tag.br
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class Heading < Node
|
8
|
+
self.type_name = "heading"
|
9
|
+
self.html_tag = proc { "h#{level}" }
|
10
|
+
|
11
|
+
def text(text, marks: [])
|
12
|
+
add_content(Text.new(text, marks: marks))
|
13
|
+
end
|
14
|
+
|
15
|
+
def level
|
16
|
+
attrs["level"]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class HorizontalRule < Node
|
8
|
+
self.type_name = "horizontalRule"
|
9
|
+
|
10
|
+
def include_empty_content_in_json?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_html
|
15
|
+
tag.hr
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class Image < Node
|
8
|
+
self.type_name = "image"
|
9
|
+
|
10
|
+
def include_empty_content_in_json?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_html
|
15
|
+
image_tag(src)
|
16
|
+
end
|
17
|
+
|
18
|
+
def src
|
19
|
+
attrs["src"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class ListItem < Node
|
8
|
+
self.type_name = "listItem"
|
9
|
+
self.html_tag = :li
|
10
|
+
self.html_class_name = "list-item"
|
11
|
+
|
12
|
+
def paragraph(&block)
|
13
|
+
raise ArgumentError, "Block required" if block.nil?
|
14
|
+
|
15
|
+
add_content(Paragraph.new(&block))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class OrderedList < Node
|
8
|
+
self.type_name = "orderedList"
|
9
|
+
self.html_tag = :ol
|
10
|
+
self.html_class_name = "ordered-list"
|
11
|
+
|
12
|
+
def list_item(&block)
|
13
|
+
raise ArgumentError, "Block required" if block.nil?
|
14
|
+
|
15
|
+
add_content(ListItem.new(&block))
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
attrs["start"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class Paragraph < Node
|
8
|
+
self.type_name = "paragraph"
|
9
|
+
self.html_tag = :p
|
10
|
+
|
11
|
+
def text(text, marks: [])
|
12
|
+
add_content(Text.new(text, marks: marks))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class TaskItem < Node
|
8
|
+
self.type_name = "taskItem"
|
9
|
+
self.html_tag = :li
|
10
|
+
self.html_class_name = proc { class_names("task-item", {checked: checked?}) }
|
11
|
+
|
12
|
+
def paragraph(&block)
|
13
|
+
raise ArgumentError, "Block required" if block.nil?
|
14
|
+
|
15
|
+
add_content(Paragraph.new(&block))
|
16
|
+
end
|
17
|
+
|
18
|
+
def checked?
|
19
|
+
attrs["checked"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/node"
|
4
|
+
|
5
|
+
module TipTap
|
6
|
+
module Nodes
|
7
|
+
class TaskList < Node
|
8
|
+
self.type_name = "taskList"
|
9
|
+
self.html_tag = :ul
|
10
|
+
self.html_class_name = "task-list"
|
11
|
+
|
12
|
+
def task_item(checked: false, &block)
|
13
|
+
raise ArgumentError, "Block required" if block.nil?
|
14
|
+
|
15
|
+
add_content(TaskItem.new(checked: checked, &block))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tip_tap/json_renderable"
|
4
|
+
require "tip_tap/html_renderable"
|
5
|
+
|
6
|
+
module TipTap
|
7
|
+
module Nodes
|
8
|
+
class Text
|
9
|
+
include JsonRenderable
|
10
|
+
include HtmlRenderable
|
11
|
+
|
12
|
+
attr_reader :text, :marks
|
13
|
+
|
14
|
+
self.type_name = "text"
|
15
|
+
|
16
|
+
def initialize(text, marks: [])
|
17
|
+
@text = text
|
18
|
+
@marks = marks.map(&:deep_stringify_keys)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.from_json(json)
|
22
|
+
json.deep_stringify_keys!
|
23
|
+
|
24
|
+
new(json["text"], marks: Array(json["marks"]))
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_json
|
28
|
+
{type: type_name, text: text, marks: marks.map(&:deep_symbolize_keys)}.compact_blank
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_html
|
32
|
+
value = text
|
33
|
+
value = content_tag(:u, value) if underline?
|
34
|
+
value = content_tag(:em, value) if italic?
|
35
|
+
value = content_tag(:strong, value) if bold?
|
36
|
+
value = content_tag(:a, value, href: link_href, target: link_target) if link?
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_plain_text
|
41
|
+
text
|
42
|
+
end
|
43
|
+
|
44
|
+
def italic?
|
45
|
+
has_mark_with_type?("italic")
|
46
|
+
end
|
47
|
+
|
48
|
+
def bold?
|
49
|
+
has_mark_with_type?("bold")
|
50
|
+
end
|
51
|
+
|
52
|
+
def underline?
|
53
|
+
has_mark_with_type?("underline")
|
54
|
+
end
|
55
|
+
|
56
|
+
def link?
|
57
|
+
has_mark_with_type?("link")
|
58
|
+
end
|
59
|
+
|
60
|
+
def link_href
|
61
|
+
marks.find { |mark| mark["type"] == "link" }&.dig("attrs", "href")
|
62
|
+
end
|
63
|
+
|
64
|
+
def link_target
|
65
|
+
marks.find { |mark| mark["type"] == "link" }&.dig("attrs", "target")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def has_mark_with_type?(type)
|
71
|
+
marks.any? { |mark| mark["type"] == type }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This is the registry for all the nodes that TipTap knows about.
|
4
|
+
# It's a simple hash that maps TipTap JS node names to Ruby classes.
|
5
|
+
# Registering a new node is as simple as:
|
6
|
+
# TipTap::Registry.register('myNode', MyNode)
|
7
|
+
module TipTap
|
8
|
+
class Registry
|
9
|
+
MissingNodeError = Class.new(StandardError)
|
10
|
+
|
11
|
+
def self.register(name, klass)
|
12
|
+
registry[name.to_s] = klass
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.node_for(name)
|
16
|
+
registry.fetch(name.to_s) { raise MissingNodeError.new("Unknown node type: #{name}") }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.clear
|
20
|
+
@registry = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.registry
|
24
|
+
@registry ||= {}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/tip_tap.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "tip_tap/version"
|
4
|
+
require "tip_tap/registry"
|
5
|
+
require "tip_tap/document"
|
6
|
+
require "tip_tap/nodes/bullet_list"
|
7
|
+
require "tip_tap/nodes/hard_break"
|
8
|
+
require "tip_tap/nodes/heading"
|
9
|
+
require "tip_tap/nodes/horizontal_rule"
|
10
|
+
require "tip_tap/nodes/list_item"
|
11
|
+
require "tip_tap/nodes/ordered_list"
|
12
|
+
require "tip_tap/nodes/paragraph"
|
13
|
+
require "tip_tap/nodes/task_item"
|
14
|
+
require "tip_tap/nodes/task_list"
|
15
|
+
require "tip_tap/nodes/text"
|
16
|
+
require "tip_tap/nodes/image"
|
17
|
+
|
18
|
+
module TipTap
|
19
|
+
class Error < StandardError; end
|
20
|
+
|
21
|
+
def self.node_for(name)
|
22
|
+
Registry.node_for(name)
|
23
|
+
end
|
24
|
+
end
|
data/lib/tiptap.rb
ADDED
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tiptap-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chad Wilken
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: actionview
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.0'
|
41
|
+
description: A gem for parsing, generating, and rendering TipTap Documents and Nodes
|
42
|
+
using Ruby.
|
43
|
+
email:
|
44
|
+
- chad.wilken@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- CHANGELOG.md
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- lib/tip_tap.rb
|
53
|
+
- lib/tip_tap/document.rb
|
54
|
+
- lib/tip_tap/has_content.rb
|
55
|
+
- lib/tip_tap/html_renderable.rb
|
56
|
+
- lib/tip_tap/json_renderable.rb
|
57
|
+
- lib/tip_tap/node.rb
|
58
|
+
- lib/tip_tap/nodes/bullet_list.rb
|
59
|
+
- lib/tip_tap/nodes/hard_break.rb
|
60
|
+
- lib/tip_tap/nodes/heading.rb
|
61
|
+
- lib/tip_tap/nodes/horizontal_rule.rb
|
62
|
+
- lib/tip_tap/nodes/image.rb
|
63
|
+
- lib/tip_tap/nodes/list_item.rb
|
64
|
+
- lib/tip_tap/nodes/ordered_list.rb
|
65
|
+
- lib/tip_tap/nodes/paragraph.rb
|
66
|
+
- lib/tip_tap/nodes/task_item.rb
|
67
|
+
- lib/tip_tap/nodes/task_list.rb
|
68
|
+
- lib/tip_tap/nodes/text.rb
|
69
|
+
- lib/tip_tap/plain_text_renderable.rb
|
70
|
+
- lib/tip_tap/registry.rb
|
71
|
+
- lib/tip_tap/version.rb
|
72
|
+
- lib/tiptap.rb
|
73
|
+
homepage: https://github.com/chadwilken/tiptap-ruby
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata:
|
77
|
+
homepage_uri: https://github.com/chadwilken/tiptap-ruby
|
78
|
+
source_code_uri: https://github.com/chadwilken/tiptap-ruby
|
79
|
+
changelog_uri: https://github.com/chadwilken/tiptap-ruby/blob/master/CHANGELOG.md
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.7.0
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.4.6
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Parse, generate and render TipTap documents in Ruby.
|
99
|
+
test_files: []
|