notion_rails 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: