synotion 0.1.0 → 0.2.0

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: e245b18b4c8a491475b7a561d079ca31a5ccaeeb451fea9ab27e7cba62a768ee
4
- data.tar.gz: 68e471af7154e8c488d983898bda7ad580a2adb64d599f167342bc47f6bc570a
3
+ metadata.gz: 6072eda9ba4254047b5c306cc68ee3bc5ca3c8612fd1f12217490d421845f385
4
+ data.tar.gz: 1e13fbcdcd01cb50c8e3495eb83353eac569b792d40b839f8054865a685c967c
5
5
  SHA512:
6
- metadata.gz: 6a1a6879d09e30060b8b6c37d5d8a49cbfa52c60f3dec80199d30fd042c4d803c8bdcdc951837879d0fa4052eb41d61b4ed082b3c903f9e742f950792593f411
7
- data.tar.gz: f1dc5845d7ca55c1ca7ea92367b709631837b0b60e985edf2f2420939ac0c3474afbcea57e7cf40a9ad45315f2115cdfd3e0596690d8b594c19a87ecc65324bf
6
+ metadata.gz: 1781fb13cd1323a299806b8fbf9098f695e5850cca0c5689c26f3d0d148be0f1d01a4fa9be58c5be5abd583a093d9ca52a909783f03cc44df32f5d572cfc8025
7
+ data.tar.gz: 0007ce05df18d685e5fea89cedc55a94b639f585153647569020f0cb4e0c33fc5113679b65ff5cc5595bc082f2cd78f5763a4143cf7164b8d4127f508dfe726c
data/CHANGELOG.md CHANGED
@@ -7,5 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [0.2.0] - 2025-12-16
11
+
12
+ - Add support for tables and inline links in Markdown to Notion conversion.
13
+
14
+ ## [0.1.0] - 2025-12-15
15
+
10
16
  - Initial release
11
17
 
data/README.md CHANGED
@@ -111,7 +111,7 @@ unique_property: source_file
111
111
 
112
112
  ### Supported Markdown Elements
113
113
 
114
- - Headings (H1, H2, H3)
114
+ - Headings (H1, H2, H3+)
115
115
  - Paragraphs
116
116
  - Code blocks with syntax highlighting
117
117
  - Bulleted lists
@@ -119,6 +119,8 @@ unique_property: source_file
119
119
  - Quotes
120
120
  - Dividers
121
121
  - Todo items / Checkboxes
122
+ - Tables (converted to Notion table blocks)
123
+ - Links (external URLs converted to clickable links)
122
124
  - YAML front matter
123
125
 
124
126
  ## Notion Setup
@@ -78,10 +78,10 @@ module Synotion
78
78
  cursor = nil
79
79
 
80
80
  loop do
81
- response = api_client.block_children(
82
- block_id: page_id,
83
- start_cursor: cursor
84
- )
81
+ params = { block_id: page_id }
82
+ params[:start_cursor] = cursor if cursor
83
+
84
+ response = api_client.block_children(**params)
85
85
 
86
86
  blocks.concat(response.results)
87
87
 
@@ -30,6 +30,10 @@ module Synotion
30
30
  code_block, lines_consumed = parse_code_block(lines[i..-1])
31
31
  blocks << code_block if code_block
32
32
  i += lines_consumed
33
+ elsif line.start_with?('|') && is_table?(lines[i..-1])
34
+ table_block, lines_consumed = parse_table(lines[i..-1])
35
+ blocks << table_block if table_block
36
+ i += lines_consumed
33
37
  elsif line.match?(/^\s*[-*]\s+\[[ x]\]/)
34
38
  blocks << parse_todo(line)
35
39
  i += 1
@@ -96,7 +100,7 @@ module Synotion
96
100
  {
97
101
  type: heading_type,
98
102
  heading_type => {
99
- rich_text: [{ type: 'text', text: { content: text.strip } }]
103
+ rich_text: parse_inline_formatting(text.strip)
100
104
  }
101
105
  }
102
106
  end
@@ -150,7 +154,7 @@ module Synotion
150
154
  items << {
151
155
  type: type.to_s,
152
156
  type => {
153
- rich_text: [{ type: 'text', text: { content: text } }]
157
+ rich_text: parse_inline_formatting(text)
154
158
  }
155
159
  }
156
160
  i += 1
@@ -165,7 +169,7 @@ module Synotion
165
169
  {
166
170
  type: 'quote',
167
171
  quote: {
168
- rich_text: [{ type: 'text', text: { content: text.strip } }]
172
+ rich_text: parse_inline_formatting(text.strip)
169
173
  }
170
174
  }
171
175
  end
@@ -178,7 +182,7 @@ module Synotion
178
182
  {
179
183
  type: 'to_do',
180
184
  to_do: {
181
- rich_text: [{ type: 'text', text: { content: text } }],
185
+ rich_text: parse_inline_formatting(text),
182
186
  checked: checked
183
187
  }
184
188
  }
@@ -197,7 +201,7 @@ module Synotion
197
201
  i += 1
198
202
  end
199
203
 
200
- return [nil, i] if paragraph_lines.empty?
204
+ return [nil, [i, 1].max] if paragraph_lines.empty?
201
205
 
202
206
  text = paragraph_lines.join(' ').strip
203
207
  rich_text = parse_inline_formatting(text)
@@ -216,10 +220,117 @@ module Synotion
216
220
  segments = []
217
221
  current_pos = 0
218
222
 
219
- [{
220
- type: 'text',
221
- text: { content: text }
222
- }]
223
+ while current_pos < text.length
224
+ link_match = text[current_pos..-1].match(/\[([^\]]+)\]\(([^\)]+)\)/)
225
+
226
+ if link_match
227
+ pre_text = text[current_pos...(current_pos + link_match.begin(0))]
228
+ if pre_text.length > 0
229
+ segments << {
230
+ type: 'text',
231
+ text: { content: pre_text }
232
+ }
233
+ end
234
+
235
+ link_text = link_match[1]
236
+ link_url = link_match[2].strip
237
+
238
+ if link_url.match?(/^https?:\/\//)
239
+ segments << {
240
+ type: 'text',
241
+ text: {
242
+ content: link_text,
243
+ link: { url: link_url }
244
+ }
245
+ }
246
+ elsif link_url.start_with?('#')
247
+ segments << {
248
+ type: 'text',
249
+ text: { content: link_text }
250
+ }
251
+ else
252
+ segments << {
253
+ type: 'text',
254
+ text: { content: link_text }
255
+ }
256
+ end
257
+
258
+ current_pos += link_match.begin(0) + link_match[0].length
259
+ else
260
+ remaining_text = text[current_pos..-1]
261
+ if remaining_text.length > 0
262
+ segments << {
263
+ type: 'text',
264
+ text: { content: remaining_text }
265
+ }
266
+ end
267
+ break
268
+ end
269
+ end
270
+
271
+ segments.empty? ? [{ type: 'text', text: { content: text } }] : segments
272
+ end
273
+
274
+ def is_table?(lines)
275
+ return false if lines.length < 2
276
+ return false unless lines[0].start_with?('|')
277
+ lines[1].match?(/^\|[\s:|-]+\|/)
278
+ end
279
+
280
+ def parse_table(lines)
281
+ table_lines = []
282
+ i = 0
283
+
284
+ while i < lines.length && lines[i].start_with?('|')
285
+ table_lines << lines[i]
286
+ i += 1
287
+ end
288
+
289
+ return [nil, 1] if table_lines.empty?
290
+
291
+ rows = table_lines.map { |line| parse_table_row(line) }
292
+ rows = rows.reject { |row| row.all? { |cell| cell.strip.match?(/^[-:|\s]*$/) } }
293
+
294
+ return [nil, i] if rows.empty?
295
+
296
+ table_width = rows.first.length
297
+
298
+ normalized_rows = rows.map do |row_cells|
299
+ normalized_cells = row_cells.dup
300
+ while normalized_cells.length < table_width
301
+ normalized_cells << ''
302
+ end
303
+ normalized_cells = normalized_cells[0...table_width] if normalized_cells.length > table_width
304
+ normalized_cells
305
+ end
306
+
307
+ table_rows = normalized_rows.map do |row_cells|
308
+ {
309
+ type: 'table_row',
310
+ table_row: {
311
+ cells: row_cells.map { |cell| [{ type: 'text', text: { content: cell.strip } }] }
312
+ }
313
+ }
314
+ end
315
+
316
+ block = {
317
+ type: 'table',
318
+ table: {
319
+ table_width: table_width,
320
+ has_column_header: true,
321
+ has_row_header: false,
322
+ children: table_rows
323
+ }
324
+ }
325
+
326
+ [block, i]
327
+ end
328
+
329
+ def parse_table_row(line)
330
+ cells = line.split('|').map(&:strip)
331
+ cells = cells[1..-1] if cells.first.empty?
332
+ cells = cells[0..-2] if cells.last.empty?
333
+ cells
223
334
  end
224
335
  end
225
336
  end
@@ -1,3 +1,3 @@
1
1
  module Synotion
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synotion
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada