notion_rails 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +1 -1
- data/lib/notion_rails/base_block.rb +98 -0
- data/lib/notion_rails/base_page.rb +52 -0
- data/lib/notion_rails/page.rb +18 -0
- data/lib/notion_rails/renderers.rb +171 -0
- data/lib/notion_rails/service.rb +90 -0
- data/lib/notion_rails/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca08751cb4669bed3cd0ad791b3242ba6c2f873e3272310750beaa940eb17151
|
4
|
+
data.tar.gz: fe85cdedfe168e670b5cd2588cb03d514347408ac24396e1dec60e3df8c2bdf6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '052119acf124d0543367b3bbbc14d762eb08132428f3c1b92242b4069f8f3e8c9665f7fbd1db79f3e5a6915eec853531417853af72096fcd83e18e0fdf39669f'
|
7
|
+
data.tar.gz: '09a7d8478a722c303e6f22782069dbf74ad83cad14a0949f1fe84453ba4e29dc3703a25de7ab31f4943b6258ab3f5fb471b421f638be2b97f8b780bdbc4533f5'
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
TODO: Delete this and the text below, and describe your gem
|
4
4
|
|
5
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/notion_rails`. To experiment with that code, run `bin/console` for an interactive prompt.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module NotionRails
|
2
|
+
class BaseBlock
|
3
|
+
include NotionRails::Renderers
|
4
|
+
|
5
|
+
# TODO: validate object type is block
|
6
|
+
|
7
|
+
attr_reader :id,
|
8
|
+
:created_time,
|
9
|
+
:last_edited_time,
|
10
|
+
:created_by,
|
11
|
+
:last_edited_by,
|
12
|
+
:parent,
|
13
|
+
:archived,
|
14
|
+
:has_children,
|
15
|
+
:children,
|
16
|
+
:siblings,
|
17
|
+
:type,
|
18
|
+
:properties
|
19
|
+
|
20
|
+
attr_accessor :children, :siblings
|
21
|
+
|
22
|
+
BLOCK_TYPES = %w[
|
23
|
+
paragraph
|
24
|
+
heading_1
|
25
|
+
heading_2
|
26
|
+
heading_3
|
27
|
+
bulleted_list_item
|
28
|
+
numbered_list_item
|
29
|
+
quote
|
30
|
+
callout
|
31
|
+
code
|
32
|
+
image
|
33
|
+
video
|
34
|
+
table_of_contents
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
def initialize(data)
|
38
|
+
@id = data['id']
|
39
|
+
@created_time = data['created_time']
|
40
|
+
@last_edited_time = data['last_edited_time']
|
41
|
+
# TODO: handle user object
|
42
|
+
@created_by = data['created_by']
|
43
|
+
@last_edited_by = data['last_edited_by']
|
44
|
+
# TODO: handle page_id type
|
45
|
+
@parent = data['parent']
|
46
|
+
@archived = data['archived']
|
47
|
+
@has_children = data['has_children']
|
48
|
+
@children = []
|
49
|
+
@siblings = []
|
50
|
+
@type = data['type']
|
51
|
+
@properties = data[@type]
|
52
|
+
end
|
53
|
+
|
54
|
+
def render(options = {})
|
55
|
+
case @type
|
56
|
+
when 'paragraph' then render_paragraph(rich_text, class: options[:paragraph])
|
57
|
+
when 'heading_1' then render_heading_1(rich_text, class: options[:heading_1])
|
58
|
+
when 'heading_2' then render_heading_2(rich_text, class: options[:heading_2])
|
59
|
+
when 'heading_3' then render_heading_3(rich_text, class: options[:heading_3])
|
60
|
+
when 'table_of_contents' then render_table_of_contents
|
61
|
+
when 'bulleted_list_item' then render_bulleted_list_item(rich_text, @siblings, @children, class: options[:bulleted_list_item])
|
62
|
+
when 'numbered_list_item' then render_numbered_list_item(rich_text, @siblings, @children, class: options[:numbered_list_item])
|
63
|
+
when 'quote' then render_quote(rich_text, class: options[:quote])
|
64
|
+
when 'callout' then render_callout(rich_text, icon, class: options[:callout])
|
65
|
+
when 'code' then render_code(rich_text, class: "#{options[:code]} language-#{@properties['language']}")
|
66
|
+
when 'image' then render_image(*multi_media)
|
67
|
+
when 'video' then render_video(*multi_media)
|
68
|
+
else
|
69
|
+
'Error'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def rich_text
|
74
|
+
@properties['rich_text'] || []
|
75
|
+
end
|
76
|
+
|
77
|
+
def icon
|
78
|
+
icon = @properties['icon']
|
79
|
+
@properties['icon'][icon['type']] || []
|
80
|
+
end
|
81
|
+
|
82
|
+
def multi_media
|
83
|
+
case @properties['type']
|
84
|
+
when 'file'
|
85
|
+
[@properties.dig('file', 'url'), @properties.dig('file', 'expiry_time'), @properties['caption'], 'file']
|
86
|
+
when 'external'
|
87
|
+
[@properties.dig('external', 'url'), nil, @properties['caption'], 'external']
|
88
|
+
else
|
89
|
+
[nil, nil, @properties['caption'], nil]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def render_table_of_contents
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module NotionRails
|
2
|
+
class BasePage
|
3
|
+
include NotionRails::Renderers
|
4
|
+
# TODO: validate object type is page
|
5
|
+
attr_reader :id,
|
6
|
+
:created_time,
|
7
|
+
:last_edited_time,
|
8
|
+
:created_by,
|
9
|
+
:last_edited_by,
|
10
|
+
:cover,
|
11
|
+
:icon,
|
12
|
+
:parent,
|
13
|
+
:archived,
|
14
|
+
:properties,
|
15
|
+
:tags,
|
16
|
+
:title,
|
17
|
+
:slug,
|
18
|
+
:url
|
19
|
+
|
20
|
+
def initialize(data)
|
21
|
+
@id = data['id']
|
22
|
+
@created_time = data['created_time']
|
23
|
+
@last_edited_time = data['last_edited_time']
|
24
|
+
# TODO: handle user object
|
25
|
+
@created_by = data['created_by']
|
26
|
+
@last_edited_by = data['last_edited_by']
|
27
|
+
# TODO: handle external type
|
28
|
+
@cover = data['cover']
|
29
|
+
# TODO: handle emoji type
|
30
|
+
@icon = data['icon']
|
31
|
+
# TODO: handle database_id type
|
32
|
+
@parent = data['parent']
|
33
|
+
@archived = data['archived']
|
34
|
+
# TODO: handle properties object
|
35
|
+
@properties = data['properties']
|
36
|
+
process_properties
|
37
|
+
@url = data['url']
|
38
|
+
end
|
39
|
+
|
40
|
+
def formatted_title(options = {})
|
41
|
+
render_title(@title, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def process_properties
|
47
|
+
@tags = @properties['tags']
|
48
|
+
@title = @properties.dig('name', 'title')
|
49
|
+
@slug = @properties['slug']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module NotionRails
|
2
|
+
class Page
|
3
|
+
include NotionRails::Renderers
|
4
|
+
|
5
|
+
attr_reader :metadata, :blocks
|
6
|
+
|
7
|
+
delegate :formatted_title, to: :metadata
|
8
|
+
|
9
|
+
def initialize(base_page, base_blocks)
|
10
|
+
@metadata = base_page
|
11
|
+
@blocks = base_blocks
|
12
|
+
end
|
13
|
+
|
14
|
+
def formatted_blocks(options = {})
|
15
|
+
@blocks.map { |block| block.render(options) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module NotionRails
|
2
|
+
module Renderers
|
3
|
+
include ActionView::Helpers::AssetTagHelper
|
4
|
+
include ActionView::Helpers::TagHelper
|
5
|
+
include ActionView::Helpers::UrlHelper
|
6
|
+
include ActionView::Context
|
7
|
+
|
8
|
+
def annotation_to_css_class(annotations)
|
9
|
+
classes = annotations.keys.map do |key|
|
10
|
+
case key
|
11
|
+
when 'strikethrough'
|
12
|
+
'line-through' if annotations[key]
|
13
|
+
when 'bold'
|
14
|
+
'font-bold' if annotations[key]
|
15
|
+
when 'code'
|
16
|
+
'inline-code' if annotations[key]
|
17
|
+
when 'color'
|
18
|
+
"text-#{annotations['color']}-600" if annotations[key] != 'default'
|
19
|
+
else
|
20
|
+
annotations[key] ? key : nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
classes.compact.join(' ')
|
24
|
+
end
|
25
|
+
|
26
|
+
def text_renderer(properties)
|
27
|
+
properties.map do |rich_text|
|
28
|
+
classes = annotation_to_css_class(rich_text['annotations'])
|
29
|
+
if rich_text['href']
|
30
|
+
link_to(
|
31
|
+
rich_text['plain_text'],
|
32
|
+
rich_text['href'],
|
33
|
+
class: "link #{classes}"
|
34
|
+
)
|
35
|
+
elsif classes.present?
|
36
|
+
content_tag(:span, rich_text['plain_text'], class: classes)
|
37
|
+
else
|
38
|
+
rich_text['plain_text']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_title(title, options = {})
|
44
|
+
render_paragraph(title, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_paragraph(rich_text_array, options = {})
|
48
|
+
content_tag(:p, options) do
|
49
|
+
text_renderer(rich_text_array).join('').html_safe
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def render_heading_1(rich_text_array, options = {})
|
54
|
+
content_tag(:h1, class: 'mb-4 mt-6 text-3xl font-semibold', **options) do
|
55
|
+
text_renderer(rich_text_array).join('').html_safe
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def render_heading_2(rich_text_array, options = {})
|
60
|
+
content_tag(:h2, class: 'mb-4 mt-6 text-2xl font-semibold', **options) do
|
61
|
+
text_renderer(rich_text_array).join('').html_safe
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def render_heading_3(rich_text_array, options = {})
|
66
|
+
content_tag(:h3, class: 'mb-2 mt-6 text-xl font-semibold', **options) do
|
67
|
+
text_renderer(rich_text_array).join('').html_safe
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def render_code(rich_text_array, options = {})
|
72
|
+
# TODO: render captions
|
73
|
+
pre_options = options
|
74
|
+
pre_options[:class] = "p-6 rounded #{pre_options[:class]}"
|
75
|
+
content_tag(:div, class: 'mt-4', data: { controller: 'highlight' }) do
|
76
|
+
content_tag(:div, data: { highlight_target: 'source' }) do
|
77
|
+
content_tag(:pre, pre_options) do
|
78
|
+
content_tag(:code, options) do
|
79
|
+
text_renderer(rich_text_array).join('').html_safe
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_bulleted_list_item(rich_text_array, siblings, children, options = {})
|
87
|
+
pre_options = options
|
88
|
+
pre_options[:class] = "list-disc break-words #{pre_options[:class]}"
|
89
|
+
content_tag(:ul, pre_options) do
|
90
|
+
content = content_tag(:li, options) do
|
91
|
+
text_renderer(rich_text_array).join('').html_safe
|
92
|
+
end
|
93
|
+
if children.present?
|
94
|
+
res = children.map do |child|
|
95
|
+
render_bulleted_list_item(child.rich_text, child.siblings, child.children, options)
|
96
|
+
end
|
97
|
+
content += res.join('').html_safe
|
98
|
+
end
|
99
|
+
content.html_safe
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def render_numbered_list_item(rich_text_array, siblings, children, options = {})
|
104
|
+
pre_options = options
|
105
|
+
pre_options[:class] = "list-decimal #{pre_options[:class]}"
|
106
|
+
content_tag(:ol, pre_options) do
|
107
|
+
render_list_items(:numbered_list_item, rich_text_array, siblings, children, options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def render_list_items(type, rich_text_array, siblings, children, options = {})
|
112
|
+
content = content_tag(:li, options) do
|
113
|
+
text_renderer(rich_text_array).join('').html_safe
|
114
|
+
end
|
115
|
+
if children.present?
|
116
|
+
res = children.map do |child|
|
117
|
+
render_numbered_list_item(child.rich_text, child.siblings, child.children)
|
118
|
+
end
|
119
|
+
content += res.join('').html_safe
|
120
|
+
end
|
121
|
+
if siblings.present?
|
122
|
+
content += siblings.map do |sibling|
|
123
|
+
render_list_items(type, sibling.rich_text, sibling.siblings, sibling.children, options)
|
124
|
+
end.join('').html_safe
|
125
|
+
end
|
126
|
+
content.html_safe
|
127
|
+
end
|
128
|
+
|
129
|
+
def render_quote(rich_text_array, options = {})
|
130
|
+
div_options = options.dup
|
131
|
+
pre_options = options.dup
|
132
|
+
div_options[:class] = "mt-4 #{options[:class]}"
|
133
|
+
content_tag(:div, div_options) do
|
134
|
+
pre_options[:class] = "border-l-4 border-black px-5 py-1 #{options[:class]}"
|
135
|
+
content_tag(:cite, pre_options) do
|
136
|
+
text_renderer(rich_text_array).join('').html_safe
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def render_callout(rich_text_array, icon, options = {})
|
142
|
+
pre_options = options
|
143
|
+
pre_options[:class] = "p-4 rounded bg-neutral-200 mt-4 #{pre_options[:class]}"
|
144
|
+
content_tag(:div, pre_options) do
|
145
|
+
content = tag.span(icon, class: 'pr-2')
|
146
|
+
content += text_renderer(rich_text_array).join('').html_safe
|
147
|
+
content
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def render_image(src, expiry_time, caption, type, options = {})
|
152
|
+
content_tag(:figure, options) do
|
153
|
+
content = tag.img(src:, alt: '')
|
154
|
+
content += tag.figcaption(text_renderer(caption).join('').html_safe)
|
155
|
+
content
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def render_video(src, expiry_time, caption, type, options = {})
|
160
|
+
content_tag(:figure, options) do
|
161
|
+
content = if type == 'file'
|
162
|
+
video_tag(src, controls: true)
|
163
|
+
elsif type == 'external'
|
164
|
+
tag.iframe(src:, allowfullscreen: true, class: 'w-full aspect-video')
|
165
|
+
end
|
166
|
+
content += tag.figcaption(text_renderer(caption).join('').html_safe)
|
167
|
+
content
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module NotionRails
|
2
|
+
class Service
|
3
|
+
def initialize
|
4
|
+
@client = Notion::Client.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_articles(tag: nil, slug: nil)
|
8
|
+
query = [
|
9
|
+
{
|
10
|
+
property: 'public',
|
11
|
+
checkbox: {
|
12
|
+
equals: true
|
13
|
+
}
|
14
|
+
},
|
15
|
+
{
|
16
|
+
property: 'slug',
|
17
|
+
rich_text: {
|
18
|
+
is_not_empty: true
|
19
|
+
}
|
20
|
+
}
|
21
|
+
]
|
22
|
+
|
23
|
+
query.push({
|
24
|
+
property: 'slug',
|
25
|
+
rich_text: {
|
26
|
+
equals: slug,
|
27
|
+
}
|
28
|
+
}) if slug
|
29
|
+
|
30
|
+
query.push({
|
31
|
+
property: 'tags',
|
32
|
+
multi_select: {
|
33
|
+
contains: tag,
|
34
|
+
}
|
35
|
+
}) if tag
|
36
|
+
|
37
|
+
pages = @client.database_query(
|
38
|
+
database_id: Rails.application.credentials.notion.database_id,
|
39
|
+
sorts: [
|
40
|
+
{
|
41
|
+
property: 'published',
|
42
|
+
direction: 'descending'
|
43
|
+
}
|
44
|
+
],
|
45
|
+
filter: {
|
46
|
+
'and': query
|
47
|
+
}
|
48
|
+
)
|
49
|
+
pages['results'].map { |page| Notion::BasePage.new(page) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_article(id)
|
53
|
+
base_page = Notion::BasePage.new(@client.page(page_id: id))
|
54
|
+
base_blocks = get_blocks(id)
|
55
|
+
Notion::Page.new(base_page, base_blocks)
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_blocks(id)
|
59
|
+
blocks = @client.block_children(block_id: id)
|
60
|
+
parent_list_block_index = nil
|
61
|
+
results = []
|
62
|
+
blocks['results'].each_with_index do |block, index|
|
63
|
+
base_block = Notion::BaseBlock.new(block)
|
64
|
+
if base_block.has_children
|
65
|
+
base_block.children = get_blocks(base_block.id)
|
66
|
+
end
|
67
|
+
# Notion returns same list items as different blocks so we have to do some processing to have them be related
|
68
|
+
# TODO: Separate this into a function, add support for bulleted items.
|
69
|
+
# Currently bulleted items render fine, but they do it in separate ul blocks
|
70
|
+
# Make them appear in the same ul block as numbered_items appear in the same ol block
|
71
|
+
if %w[numbered_list_item].include? base_block.type
|
72
|
+
siblings = !parent_list_block_index.nil? &&
|
73
|
+
index != parent_list_block_index &&
|
74
|
+
base_block.type == results[parent_list_block_index]&.type &&
|
75
|
+
base_block.parent == results[parent_list_block_index]&.parent
|
76
|
+
if siblings
|
77
|
+
results[parent_list_block_index].siblings << base_block
|
78
|
+
next
|
79
|
+
else
|
80
|
+
parent_list_block_index = results.length
|
81
|
+
end
|
82
|
+
else
|
83
|
+
parent_list_block_index = nil
|
84
|
+
end
|
85
|
+
results << base_block
|
86
|
+
end
|
87
|
+
results
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/notion_rails/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: notion_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillermo Aguirre
|
@@ -34,6 +34,11 @@ files:
|
|
34
34
|
- LICENSE.txt
|
35
35
|
- README.md
|
36
36
|
- lib/notion_rails.rb
|
37
|
+
- lib/notion_rails/base_block.rb
|
38
|
+
- lib/notion_rails/base_page.rb
|
39
|
+
- lib/notion_rails/page.rb
|
40
|
+
- lib/notion_rails/renderers.rb
|
41
|
+
- lib/notion_rails/service.rb
|
37
42
|
- lib/notion_rails/version.rb
|
38
43
|
homepage: https://github.com/guillermoap/notion-rails
|
39
44
|
licenses:
|