notion_to_md 2.5.0 → 3.0.0.beta1

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: 4137f1edccac5aceec8f5195beb513019ec2ad84c52c5495a1d9842098dd2b4d
4
- data.tar.gz: 51a57a1bf294d1c2cf3d0be1e24c693aa633c3c873fe864a9f2864fec6194ecb
3
+ metadata.gz: 2671f75f28373209f1846e06ed528f7adcc9d857da3fde4d1b0a5e43144c5850
4
+ data.tar.gz: 39c65f58ff9432277667c0c5c2597561d1b28d9252a7868ec2b8885d0e94e07a
5
5
  SHA512:
6
- metadata.gz: 32e4a48a21d956bba91ba88d3aa3db0fdb6f7b0f54f55fcd44fdb07dd15842247ab6150b06f0f82a828ef8f0f632d158e746f54007afdd85357c41e75167b203
7
- data.tar.gz: b9aa39eeb9b5cb105a8ebf48ca78fe4e88c0a9a7c013f63dc0057dc3adf0ffcfd0cbd2c72df87b83d5e5eafaaa9605e26a0c44f63852af4be9e87e37f752d6cc
6
+ metadata.gz: 3c1571b5b93eb39bccb66032db2296d84b1d02313a6098e3d0e0f03022bfbea5eb0d46b54ae20b7bed6a9a5d6e79721922623491b5b1b829060e08b09cb67114
7
+ data.tar.gz: 5eea9bf71539d0d6c66880c89a39950c42da4c67767d651b1e218ebb380539b27c5b3ed5f00c18c77a13f547db9edc21f3645bb66e4c3e6204d08b59085da0fa
data/README.md CHANGED
@@ -1,113 +1,122 @@
1
+ <img src="https://ik.imagekit.io/gxidvqvc9/noiton_to_md_logo_white_bg_-OiZSEkqY.png?updatedAt=1756209770491" width="150">
2
+
1
3
  # notion_to_md
2
- Notion Markdown Exporter in Ruby.
3
4
 
4
- The generated document is compliant with the [GitHub Flavored Markdown specification](https://github.github.com/gfm/).
5
+ A Ruby library to export [Notion](https://www.notion.so/) pages and databases to Markdown.
6
+ The output is fully compliant with the [GitHub Flavored Markdown specification](https://github.github.com/gfm/).
7
+
8
+ [![Gem Version](https://badge.fury.io/rb/notion_to_md.svg)](https://badge.fury.io/rb/notion_to_md)
9
+ [![CI](https://github.com/emoriarty/notion_to_md/actions/workflows/ci.yml/badge.svg)](https://github.com/emoriarty/notion_to_md/actions)
10
+
11
+ > [!NOTE]
12
+ > You are reading the documentation for the latest development branch.
13
+ > For the stable **v2.x.x** documentation, see [the v2.x.x branch](https://github.com/emoriarty/notion_to_md/tree/v2.x.x).
5
14
 
6
15
  ## Installation
7
- Use gem to install.
16
+
17
+ Install via RubyGems:
18
+
8
19
  ```bash
9
- $ gem install 'notion_to_md'
20
+ gem install notion_to_md
10
21
  ```
11
22
 
12
- Or add it to the `Gemfile`.
23
+ Or add it to your `Gemfile`:
24
+
13
25
  ```ruby
14
- # Gemfile
15
26
  gem 'notion_to_md'
16
27
  ```
17
28
 
18
- ## Usage
19
- Before using the gem create an integration and generate a secret token. Check [notion getting started guide](https://developers.notion.com/docs/getting-started) to learn more.
20
-
21
- Pass the page id and secret token to the constructor and execute the `convert` method.
29
+ ### Beta version (3.0.0.beta1)
22
30
 
23
- ```ruby
24
- require 'notion_to_md'
31
+ If you want to try the **beta release**, install with the `--pre` flag:
25
32
 
26
- notion_converter = NotionToMd::Converter.new(page_id: 'b91d5...', token: 'secret_...')
27
- md = notion_converter.convert
33
+ ```bash
34
+ gem install notion_to_md --pre
28
35
  ```
29
36
 
30
- Since v2.3 you can also use the convenient `convert` method from the root module.
37
+ Or pin the beta in your Gemfile:
31
38
 
32
39
  ```ruby
33
- md = NotionToMd.convert(page_id: 'b91d5...', token: 'secret_...')
40
+ gem "notion_to_md", "3.0.0.beta1"
34
41
  ```
35
42
 
36
- If the secret token is provided as an environment variable —`NOTION_TOKEN`—, there's no need to pass it as an argument to the constructor.
43
+ ⚠️ This version is under active development. For stable usage, prefer the latest `2.x.x` release.
37
44
 
38
- ```bash
39
- $ export NOTION_TOKEN=<secret_...>
40
- ```
45
+
46
+ ## Quick Start
41
47
 
42
48
  ```ruby
43
- notion_converter = NotionToMd::Converter.new(page_id: 'b91d5...')
44
- md = notion_converter.convert
45
- # or
46
- md = NotionToMd.convert(page_id: 'b91d5...')
49
+ # Convert a Notion page to Markdown
50
+ md = NotionToMd.call(:page, id: 'b91d5...', token: ENV['NOTION_TOKEN'])
51
+ File.write("page.md", md)
47
52
  ```
48
53
 
49
- And that's all. The `md` is a string variable containing the notion document formatted in markdown.
54
+ ## Usage
50
55
 
51
- ### Callable
56
+ Before using the gem, create a Notion integration and obtain a secret token.
57
+ See the [Notion Getting Started Guide](https://developers.notion.com/docs/getting-started) for details.
52
58
 
53
- From version 2.5.0, the `call` method is also available. It's an alias for `convert`.
59
+ ### Pages
54
60
 
55
61
  ```ruby
56
- md = NotionToMd.call(page_id: 'b91d5...')
62
+ md = NotionToMd.call(:page, id: 'b91d5...', token: 'secret_...')
63
+ # or equivalently
64
+ md = NotionToMd.convert(:page, id: 'b91d5...', token: 'secret_...')
57
65
  ```
58
66
 
59
- The `call` method also supports the passing of a block.
67
+ `md` is a string containing the page content in Markdown format.
68
+
69
+ ### Databases
60
70
 
61
71
  ```ruby
62
- NotionToMd.call(page_id: 'b91d5...') { puts _1 }
72
+ mds = NotionToMd.call(:database, id: 'b91d5...')
63
73
  ```
64
74
 
65
- ## Blocks
75
+ `mds` is an array of strings, one per page.
66
76
 
67
- Everything in a notion page body is a [block object](https://developers.notion.com/reference/block#block-object-keys). Therefore, not every type can be mapped to Markdown. Below there's a list of current supported blocks types.
77
+ ### Environment Variables
68
78
 
69
- * `paragraph`
70
- * `heading_1`
71
- * `heading_2`
72
- * `heading_3`
73
- * `bulleted_list_item`
74
- * `numbered_list_item` (supported since v2.3, in previous versions is displayed as `bulleted_list_item`)
75
- * `to_do`
76
- * `image`
77
- * `bookmark`
78
- * `callout`
79
- * `quote`
80
- * `divider`
81
- * `table`
82
- * `embed`
83
- * `code`
84
- * `link_preview`
85
- * `file`
86
- * `pdf`
87
- * `video`
79
+ If your token is stored in `NOTION_TOKEN`, you don’t need to pass it explicitly:
88
80
 
89
- ### Nested blocks
81
+ ```bash
82
+ export NOTION_TOKEN=<secret_...>
83
+ ```
90
84
 
91
- Starting with v2, nested blocks are supported. The permitted blocks to have children are:
85
+ ```ruby
86
+ md = NotionToMd.call(:page, id: 'b91d5...')
87
+ ```
88
+
89
+ ## Supported Blocks
92
90
 
93
- * `paragraph`
94
- * `bulleted_list_item`
95
- * `numbered_list_item`
96
- * `to_do`
91
+ Everything in Notion is a [block object](https://developers.notion.com/reference/block#block-object-keys).
97
92
 
98
- ## Front matter
93
+ | Block type | Nested children supported? |
94
+ |-----------------------------------|----------------------------|
95
+ | paragraph | ✅ |
96
+ | heading_1 / heading_2 / heading_3 | ❌ |
97
+ | bulleted_list_item | ✅ |
98
+ | numbered_list_item | ✅ |
99
+ | to_do | ✅ |
100
+ | image, file, pdf, video | ❌ |
101
+ | bookmark, embed, link_preview | ❌ |
102
+ | callout | ❌ |
103
+ | quote | ❌ |
104
+ | divider | ❌ |
105
+ | table | ❌ |
106
+ | code | ❌ |
107
+ | equation | ❌ |
99
108
 
100
- From version 0.2.0, notion_to_md supports front matter in markdown files.
109
+ ## Front Matter
101
110
 
102
- By default, the front matter section is not included to the document. To do so, provide the `:frontmatter` option set to `true` to `convert` method.
111
+ By default, front matter is **disabled**. Enable it with:
103
112
 
104
113
  ```ruby
105
- NotionToMd::Converter.new(page_id: 'b91d5...').convert(frontmatter: tue)
106
- # or
107
- NotionToMd.convert(page_id: 'b91d5...', frontmatter: true) # Since v2.3
114
+ NotionToMd.call(:page, id: 'b91d5...', frontmatter: true)
108
115
  ```
109
116
 
110
- Default notion [properties](https://developers.notion.com/reference/page#all-pages) are page `id`, `title`, `created_time`, `last_edited_time`, `icon`, `archived` and `cover`.
117
+ ### Examples
118
+
119
+ **Default properties:**
111
120
 
112
121
  ```yml
113
122
  ---
@@ -125,9 +134,7 @@ last_edited_by_object: user
125
134
  ---
126
135
  ```
127
136
 
128
- In addition to default properties, custom properties are also supported.
129
- Custom properties name is [parameterized](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize) and [underscorized](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore) before added to front matter.
130
- For example, two properties named `Multiple Options` and `Tags` will be transformed to `multiple_options` and `tags`, respectively.
137
+ **Custom properties:**
131
138
 
132
139
  ```yml
133
140
  ---
@@ -136,25 +143,30 @@ multiple_options: option1, option2
136
143
  ---
137
144
  ```
138
145
 
139
- The supported property types are:
146
+ Custom property names are [parameterized](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize) and [underscored](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore).
147
+
148
+ Supported property types:
149
+
150
+ - `number`
151
+ - `select`
152
+ - `multi_select`
153
+ - `date`
154
+ - `people`
155
+ - `files`
156
+ - `checkbox`
157
+ - `url`
158
+ - `email`
159
+ - `phone_number`
160
+ - `rich_text` (plain text only)
140
161
 
141
- * `number`
142
- * `select`
143
- * `multi_select`
144
- * `date`
145
- * `people`
146
- * `files`
147
- * `checkbox`
148
- * `url`
149
- * `email`
150
- * `phone_number`
151
- * `rich_text`, supported but in plain text
162
+ > [!NOTE]
163
+ > Advanced types such as `formula`, `relation`, and `rollup` are **not supported**.
164
+ > See the [Notion property value docs](https://developers.notion.com/reference/property-value-object#all-property-values).
152
165
 
153
- Advanced types like `formula`, `relation` and `rollup` are not supported.
166
+ ## Development
154
167
 
155
- Check notion documentation about [property values](https://developers.notion.com/reference/property-value-object#all-property-values) to know more.
168
+ Run the test suite with:
156
169
 
157
- ## Test
158
170
  ```bash
159
171
  rspec
160
172
  ```
@@ -4,37 +4,55 @@ require 'forwardable'
4
4
 
5
5
  class NotionToMd
6
6
  module Blocks
7
+ # === NotionToMd::Blocks::Block
8
+ #
9
+ # Represents a Notion block and provides functionality
10
+ # to convert it (and its children) into Markdown.
11
+ #
12
+ # A block corresponds to a `Notion::Messages::Message`
13
+ # returned by the [notion-ruby-client](https://github.com/orbit-love/notion-ruby-client).
14
+ #
15
+ # @example Convert a block to Markdown
16
+ # block = NotionToMd::Blocks::Block.new(block: notion_message, children: [])
17
+ # puts block.to_md
18
+ #
19
+ # @see NotionToMd::Blocks::Renderer
7
20
  class Block
8
21
  extend Forwardable
9
22
 
10
- attr_reader :block, :children
23
+ # @return [Notion::Messages::Message] The Notion API block data.
24
+ attr_reader :block
11
25
 
26
+ # @return [Array<NotionToMd::Blocks::Block>] The nested child blocks.
27
+ attr_reader :children
28
+
29
+ # Delegate the `#type` method to the underlying `block`.
12
30
  def_delegators :block, :type
13
31
 
14
- # === Parameters:
15
- # block::
16
- # A {Notion::Messages::Message}[https://github.com/orbit-love/notion-ruby-client/blob/main/lib/notion/messages/message.rb] object.
17
- # children::
18
- # An array of NotionToMd::Block::Block objects.
19
- #
20
- # === Returns
21
- # A Block object.
32
+ # Initialize a new block wrapper.
22
33
  #
34
+ # @param block [Notion::Messages::Message]
35
+ # A Notion block message from the Notion API.
36
+ # @param children [Array<NotionToMd::Blocks::Block>]
37
+ # Nested blocks belonging to this block.
23
38
  def initialize(block:, children: [])
24
39
  @block = block
25
40
  @children = children
26
41
  end
27
42
 
28
- # === Parameters:
29
- # tab_width::
30
- # The number of tabs used to indent the block.
43
+ # Convert the block (and its children) into Markdown.
44
+ #
45
+ # Uses {NotionToMd::Blocks::Renderer} to render
46
+ # the Markdown representation of the block.
47
+ #
48
+ # @param tab_width [Integer] The number of tabs used to indent nested blocks. Defaults to 0.
31
49
  #
32
- # === Returns
33
- # The current block (and its children) converted to a markdown string.
50
+ # @return [String, nil] The Markdown string, or `nil` if the block type is unsupported.
34
51
  #
52
+ # @see NotionToMd::Blocks::Renderer
35
53
  def to_md(tab_width: 0)
36
54
  block_type = block.type.to_sym
37
- md = Types.send(block_type, block[block_type]) + newline
55
+ md = Renderer.send(block_type, block[block_type]) + newline
38
56
  md + build_nested_blocks(tab_width + 1)
39
57
  rescue NoMethodError
40
58
  Logger.info("Unsupported block type: #{block_type}")
@@ -43,21 +61,35 @@ class NotionToMd
43
61
 
44
62
  protected
45
63
 
64
+ # @return [String] Two newlines separating blocks.
46
65
  def newline
47
66
  "\n\n"
48
67
  end
49
68
 
69
+ # Build the Markdown for all nested blocks.
70
+ #
71
+ # @param tab_width [Integer] The indentation level.
72
+ # @return [String]
50
73
  def build_nested_blocks(tab_width)
51
74
  mds = markdownify_children(tab_width).compact
52
75
  indent_children(mds, tab_width).join
53
76
  end
54
77
 
78
+ # Render children blocks into Markdown.
79
+ #
80
+ # @param tab_width [Integer] The indentation level.
81
+ # @return [Array<String, nil>] Markdown strings (or nil if unsupported).
55
82
  def markdownify_children(tab_width)
56
83
  children.map do |nested_block|
57
84
  nested_block.to_md(tab_width: tab_width)
58
85
  end
59
86
  end
60
87
 
88
+ # Indent the Markdown output of children by `tab_width`.
89
+ #
90
+ # @param mds [Array<String>] Markdown strings.
91
+ # @param tab_width [Integer] The indentation level.
92
+ # @return [Array<String>] Indented Markdown strings.
61
93
  def indent_children(mds, tab_width)
62
94
  mds.map do |md|
63
95
  "#{"\t" * tab_width}#{md}"
@@ -2,22 +2,59 @@
2
2
 
3
3
  class NotionToMd
4
4
  module Blocks
5
+ # === NotionToMd::Blocks::Factory
6
+ #
7
+ # Factory class for instantiating the correct block wrapper class
8
+ # depending on the Notion block type.
9
+ #
10
+ # This is used by {NotionToMd::Page::Builder} when traversing Notion
11
+ # blocks returned from the API.
12
+ #
13
+ # @example Build a block instance from a Notion API message
14
+ # block = notion_client.block_children(block_id: "xxxx").results.first
15
+ # instance = NotionToMd::Blocks::Factory.build(block: block)
16
+ # instance # => NotionToMd::Blocks::Block subclass
17
+ #
18
+ # @see NotionToMd::Blocks::Block
19
+ # @see NotionToMd::Page::Builder
5
20
  class Factory
6
- def self.build(block:, children: [])
7
- case block.type.to_sym
8
- when :table
9
- TableBlock.new(block: block, children: children)
10
- when :table_row
11
- TableRowBlock.new(block: block, children: children)
12
- when :bulleted_list_item
13
- BulletedListItemBlock.new(block: block, children: children)
14
- when :numbered_list_item
15
- NumberedListItemBlock.new(block: block, children: children)
16
- when :to_do
17
- ToDoListItemBlock.new(block: block, children: children)
18
- else
19
- Blocks::Block.new(block: block, children: children)
21
+ class << self
22
+ # Build the appropriate block wrapper.
23
+ #
24
+ # @param block [Notion::Messages::Message]
25
+ # A Notion block message from the API.
26
+ # @param children [Array<NotionToMd::Blocks::Block>]
27
+ # Optional nested block instances.
28
+ #
29
+ # @return [NotionToMd::Blocks::Block]
30
+ # A block instance of the corresponding subclass:
31
+ #
32
+ # * {TableBlock} for `:table`
33
+ # * {TableRowBlock} for `:table_row`
34
+ # * {BulletedListItemBlock} for `:bulleted_list_item`
35
+ # * {NumberedListItemBlock} for `:numbered_list_item`
36
+ # * {ToDoListItemBlock} for `:to_do`
37
+ # * {NotionToMd::Blocks::Block} for all others
38
+ #
39
+ # @note Custom block classes must implement `#to_md`.
40
+ def call(block:, children: [])
41
+ case block.type.to_sym
42
+ when :table
43
+ TableBlock.new(block: block, children: children)
44
+ when :table_row
45
+ TableRowBlock.new(block: block, children: children)
46
+ when :bulleted_list_item
47
+ BulletedListItemBlock.new(block: block, children: children)
48
+ when :numbered_list_item
49
+ NumberedListItemBlock.new(block: block, children: children)
50
+ when :to_do
51
+ ToDoListItemBlock.new(block: block, children: children)
52
+ else
53
+ Blocks::Block.new(block: block, children: children)
54
+ end
20
55
  end
56
+
57
+ alias build call
21
58
  end
22
59
  end
23
60
  end
@@ -2,62 +2,143 @@
2
2
 
3
3
  class NotionToMd
4
4
  module Blocks
5
+ # === NotionToMd::Blocks::Normalizer
6
+ #
7
+ # Responsible for normalizing sequences of adjacent blocks of the same type
8
+ # into grouped list blocks (e.g., multiple consecutive `bulleted_list_item`
9
+ # blocks into a single `BulletedListBlock`).
10
+ #
11
+ # This ensures Markdown output has proper list structures instead of
12
+ # repeated, ungrouped items.
13
+ #
14
+ # @example Normalize a sequence of blocks
15
+ # blocks = [
16
+ # BulletedListItemBlock.new(block: block1),
17
+ # BulletedListItemBlock.new(block: block2),
18
+ # NotionToMd::Blocks::Block.new(block: block3)
19
+ # ]
20
+ #
21
+ # normalized = NotionToMd::Blocks::Normalizer.normalize(blocks: blocks)
22
+ # # => [BulletedListBlock(children: [..]), Block(..)]
23
+ #
24
+ # @see NotionToMd::Blocks::Block
25
+ # @see NotionToMd::Blocks::Factory
5
26
  class Normalizer
6
- # === Parameters
7
- # blocks::
8
- # An array of NotionToMd::Blocks::Block.
9
- #
10
- def self.normalize(blocks:)
11
- new(blocks: blocks).normalize
27
+ class << self
28
+ # Normalize a list of blocks.
29
+ #
30
+ # @param blocks [Array<NotionToMd::Blocks::Block>]
31
+ # Raw blocks to normalize.
32
+ #
33
+ # @return [Array<NotionToMd::Blocks::Block>]
34
+ # Normalized blocks where consecutive list items are grouped.
35
+ def call(blocks:)
36
+ new(blocks: blocks).call
37
+ end
12
38
  end
13
39
 
40
+ # @return [Array<NotionToMd::Blocks::Block>]
41
+ # The mutable, normalized block list.
14
42
  attr_reader :normalized_blocks
15
43
 
44
+ # Create a new normalizer.
45
+ #
46
+ # @param blocks [Array<NotionToMd::Blocks::Block>]
47
+ # Raw blocks to normalize.
16
48
  def initialize(blocks:)
17
49
  @normalized_blocks = blocks.dup
18
50
  end
19
51
 
20
- def normalize
52
+ # Run normalization for all supported types:
53
+ #
54
+ # * `:bulleted_list_item`
55
+ # * `:numbered_list_item`
56
+ # * `:to_do`
57
+ #
58
+ # @return [Array<NotionToMd::Blocks::Block>]
59
+ # The final normalized block array.
60
+ def call
21
61
  normalize_for :bulleted_list_item
22
62
  normalize_for :numbered_list_item
23
63
  normalize_for :to_do
64
+ normalized_blocks
24
65
  end
25
66
 
67
+ # Normalize consecutive blocks of the given +type+ into a single grouped block,
68
+ # while preserving original order for everything else.
69
+ #
70
+ # The algorithm scans left-to-right, buffering runs of +type+. When a different
71
+ # type appears (or we reach the end), the buffered run is flushed as a grouped
72
+ # block and the non-matching block is appended as-is.
26
73
  def normalize_for(type)
27
74
  new_blocks = []
28
75
 
29
76
  normalized_blocks.each do |block|
30
- if block.type.to_sym == type
77
+ if block_of_type?(block, type)
78
+ # Accumulate consecutive blocks of the target type
31
79
  blocks_to_normalize << block
32
80
  else
33
- # When we encounter a block that is not of the provided type,
34
- # we need to normalize the blocks we've collected so far.
35
- # Then we add the current block to the new blocks array.
36
- # This is because we want to keep the order of the blocks.
37
- new_blocks << new_block_and_reset_blocks_to_normalize(type) unless blocks_to_normalize.empty?
81
+ # A different type breaks the run:
82
+ # 1) flush the buffered run as a single grouped block
83
+ # 2) append the current (non-matching) block
84
+ flush_blocks_to_normalize_into(new_blocks, type)
38
85
  new_blocks << block
39
86
  end
40
87
  end
41
88
 
42
- # If the last block is the provided type, it won't be added to the new blocks array.
43
- # So, we need to normalize the blocks we've collected so far.
44
- new_blocks << new_block_and_reset_blocks_to_normalize(type) unless blocks_to_normalize.empty?
89
+ # If the sequence ended with a run of the target type, it hasn’t been
90
+ # emitted yet—flush it now.
91
+ flush_blocks_to_normalize_into(new_blocks, type)
45
92
 
93
+ # Replace the working set with the newly normalized list
46
94
  normalized_blocks.replace(new_blocks)
47
95
  end
48
96
 
49
97
  private
50
98
 
99
+ # @api private
100
+ # @param block [NotionToMd::Blocks::Block]
101
+ # @param type [Symbol]
102
+ # @return [Boolean] true if the block matches the given type.
103
+ def block_of_type?(block, type)
104
+ block.type.to_sym == type
105
+ end
106
+
107
+ # @api private
108
+ # Flush accumulated blocks to the new array as a grouped block.
109
+ #
110
+ # @param new_blocks [Array<NotionToMd::Blocks::Block>]
111
+ # @param type [Symbol]
112
+ # @return [void]
113
+ def flush_blocks_to_normalize_into(new_blocks, type)
114
+ return if blocks_to_normalize.empty?
115
+
116
+ new_blocks << new_block_and_reset_blocks_to_normalize(type)
117
+ end
118
+
119
+ # @api private
120
+ # Create a grouped block and reset buffer.
121
+ #
122
+ # @param type [Symbol]
123
+ # @return [NotionToMd::Blocks::Block]
51
124
  def new_block_and_reset_blocks_to_normalize(type)
52
125
  new_block = new_block_for(type, blocks_to_normalize)
53
126
  @blocks_to_normalize = []
54
127
  new_block
55
128
  end
56
129
 
130
+ # @api private
131
+ # @return [Array<NotionToMd::Blocks::Block>]
57
132
  def blocks_to_normalize
58
133
  @blocks_to_normalize ||= []
59
134
  end
60
135
 
136
+ # @api private
137
+ # Create a wrapper block (list) depending on type.
138
+ #
139
+ # @param type [Symbol]
140
+ # @param children [Array<NotionToMd::Blocks::Block>]
141
+ # @return [NotionToMd::Blocks::Block]
61
142
  def new_block_for(type, children)
62
143
  case type
63
144
  when :bulleted_list_item
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './bulleted_list_item_block'
4
-
5
3
  class NotionToMd
6
4
  module Blocks
7
5
  class NumberedListItemBlock < BulletedListItemBlock
@@ -15,7 +13,7 @@ class NotionToMd
15
13
  # The current block (and its children) converted to a markdown string.
16
14
  #
17
15
  def to_md(tab_width: 0, index: nil)
18
- md = Types.numbered_list_item(block[block.type.to_sym], index) + newline
16
+ md = Renderer.numbered_list_item(block[block.type.to_sym], index) + newline
19
17
  md + build_nested_blocks(tab_width + 1)
20
18
  rescue NoMethodError
21
19
  Logger.info("Unsupported block type: #{block.type}")
@@ -2,7 +2,23 @@
2
2
 
3
3
  class NotionToMd
4
4
  module Blocks
5
- class Types
5
+ # === NotionToMd::Blocks::Renderer
6
+ #
7
+ # Stateless renderer for individual Notion block payloads into
8
+ # GitHub-Flavored Markdown (GFM). Each public class method expects a
9
+ # simplified block hash (already focused on the inner block content)
10
+ # and returns a Markdown string.
11
+ #
12
+ # This class is used by {NotionToMd::Blocks::Block#to_md}.
13
+ #
14
+ # @example Render a paragraph block
15
+ # md = NotionToMd::Blocks::Renderer.paragraph({ rich_text: [{ type: :text, plain_text: "Hello" }] })
16
+ # # => "Hello"
17
+ #
18
+ # @see NotionToMd::Blocks::Block
19
+ # @see NotionToMd::Blocks::Text
20
+ # @see NotionToMd::Blocks::TextAnnotation
21
+ class Renderer
6
22
  class << self
7
23
  def paragraph(block)
8
24
  return blank if block[:rich_text].empty?
@@ -111,6 +127,10 @@ class NotionToMd
111
127
  file(block)
112
128
  end
113
129
 
130
+ def equation(block)
131
+ "$$#{block['expression']}$$"
132
+ end
133
+
114
134
  private
115
135
 
116
136
  def convert_table_row(cells)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './bulleted_list_item_block'
4
-
5
3
  class NotionToMd
6
4
  module Blocks
7
5
  class ToDoListItemBlock < BulletedListItemBlock