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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd982bd457f6bd05562e1e957c146e7680f51665498ae33b5127c68a3a34f157
4
- data.tar.gz: 79a47c55cd34192ec52def80c4bc2a3f81f65476087b1941344cb2075f9072a1
3
+ metadata.gz: ca08751cb4669bed3cd0ad791b3242ba6c2f873e3272310750beaa940eb17151
4
+ data.tar.gz: fe85cdedfe168e670b5cd2588cb03d514347408ac24396e1dec60e3df8c2bdf6
5
5
  SHA512:
6
- metadata.gz: e894d60b980e651fd7c9a91ea48c03852d2f4859a88ea058b5e8926f1fd3b7286f17147db8dcde5057d655af22684cb7ed9bcbcc9edf281765305b8d2f3f0afd
7
- data.tar.gz: 22b978b22ec0d744c99a4323528e8d908526f6213dbf9338869945af25fc8b1c1a9eee22189f7715fbc2e2819dff1c51d12870e2128e90431a15f1b2cef95454
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/notion/rails`. To experiment with that code, run `bin/console` for an interactive prompt.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NotionRails
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
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.0
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: