n8n-nodes-md2notion 1.2.1 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.0] - 2026-01-17
11
+
12
+ ### 🎉 Major Quality & Reliability Improvements
13
+
14
+ #### 🐛 Critical Bug Fixes
15
+ - **Fixed fatal `convertMarkdownToNotionBlocks is not a function` runtime error**
16
+ - Root cause: Incorrect instantiation pattern (`new MarkdownToNotion()`) in execute method
17
+ - Solution: Changed to proper instance method call via `this`
18
+ - Impact: Node now works reliably in all n8n environments
19
+
20
+ #### 🔒 Enhanced Input Validation
21
+ - **Added comprehensive input validation**
22
+ - Page ID format validation (supports both UUID formats: with/without dashes)
23
+ - Markdown content non-empty validation
24
+ - Clear, actionable error messages for invalid inputs
25
+
26
+ #### 🛡️ Improved Error Handling
27
+ - **Added Notion API response validation**
28
+ - Validates response object structure
29
+ - Detects and reports Notion API errors gracefully
30
+ - Provides detailed error messages for debugging
31
+
32
+ #### 🔧 Code Quality Improvements
33
+ - **Enabled TypeScript strict mode**
34
+ - `strict: true` and `noImplicitAny: true` in tsconfig.json
35
+ - All code passes strict type checking (0 errors)
36
+ - Better type safety and IDE support
37
+
38
+ - **Removed code duplication**
39
+ - Removed redundant `Notion-Version` header from node (already in credentials)
40
+ - Cleaner HTTP request configuration
41
+
42
+ #### ✅ Testing Infrastructure
43
+ - **Added comprehensive test suite**
44
+ - Unit tests for node and credentials
45
+ - Jest configuration with TypeScript support
46
+ - Coverage reporting setup
47
+ - Quick verification script (`verify-fixes.js`)
48
+
49
+ #### 📦 Build & Deployment
50
+ - **Updated build process**
51
+ - Proper TypeScript compilation in build script
52
+ - Cleaned up old test files
53
+ - Updated .npmignore for cleaner npm packages
54
+
55
+ ### 📌 Migration Notes
56
+ This release contains no breaking changes. All existing workflows will continue to work without modification, but with significantly improved reliability and error reporting.
57
+
58
+ ### 🔍 Verification
59
+ - ✅ All TypeScript compilation errors resolved
60
+ - ✅ All unit tests passing
61
+ - ✅ Node loads correctly in n8n
62
+ - ✅ All 16+ block types still supported
63
+ - ✅ Math formula preservation works
64
+ - ✅ Input validation working
65
+ - ✅ API error handling working
66
+
67
+ ## [1.2.2] - 2026-01-17
68
+
69
+ ### 🐛 Fixed - Runtime Loading Error
70
+
71
+ #### Bug Fixes
72
+ - **Fixed `convertMarkdownToNotionBlocks is not a function`** in n8n runtime
73
+ - Removed stray compiled files (`dist/MarkdownToNotion.node.js`, `.d.ts`)
74
+ - Ensured n8n loads the correct file: `dist/nodes/MarkdownToNotion/MarkdownToNotion.node.js`
75
+ - Adjusted `files` array to only publish `dist/nodes` and `dist/credentials`
76
+
77
+ #### Verification
78
+ - ✅ Node loads correctly via `package.json` `n8n.nodes` entry
79
+ - ✅ Method `convertMarkdownToNotionBlocks` present and callable
80
+ - ✅ All 16+ block types still supported (including toggle)
81
+ - ✅ All tests pass
82
+
83
+ ### 📌 Note
84
+ Root-level `dist/MarkdownToNotion.node.js` was being pulled by n8n and lacked the method. Package contents are now clean and scoped to the correct compiled node.
85
+
10
86
  ## [1.2.1] - 2026-01-17
11
87
 
12
88
  ### 🐛 Fixed - Critical Runtime Error
package/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  [![npm version](https://badge.fury.io/js/n8n-nodes-md2notion.svg)](https://badge.fury.io/js/n8n-nodes-md2notion)
4
4
  [![CI](https://github.com/shawnli1874/n8n-nodes-md2notion/workflows/CI/badge.svg)](https://github.com/shawnli1874/n8n-nodes-md2notion/actions)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg)](https://www.typescriptlang.org/)
7
+ [![Tested](https://img.shields.io/badge/Tests-Passing-brightgreen.svg)](https://jestjs.io/)
6
8
 
7
9
  A custom n8n node that converts markdown content to Notion page blocks with **comprehensive block type support** and **proper formula handling**.
8
10
 
@@ -14,6 +16,7 @@ Existing n8n community nodes for markdown-to-Notion conversion have critical lim
14
16
  - ✅ **Supporting 16+ Notion block types** including todos, callouts, tables, toggles, and more
15
17
  - ✅ **Using reliable parsing** with the remark ecosystem
16
18
  - ✅ **Providing excellent error handling** and user feedback
19
+ - ✅ **Production-ready quality** with TypeScript strict mode and comprehensive tests
17
20
 
18
21
  ## 🚀 Quick Start
19
22
 
@@ -26,4 +26,7 @@ export declare class MarkdownToNotion implements INodeType {
26
26
  private preprocessToggleBlocks;
27
27
  private isToggleBlock;
28
28
  private createToggleBlock;
29
+ private validatePageId;
30
+ private validateMarkdownContent;
31
+ private validateNotionApiResponse;
29
32
  }
@@ -1,13 +1,15 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.MarkdownToNotion = void 0;
4
7
  const n8n_workflow_1 = require("n8n-workflow");
5
8
  const unified_1 = require("unified");
6
- const remark_parse_1 = require("remark-parse");
7
- const remark_gfm_1 = require("remark-gfm");
9
+ const remark_parse_1 = __importDefault(require("remark-parse"));
10
+ const remark_gfm_1 = __importDefault(require("remark-gfm"));
8
11
  const unist_util_visit_1 = require("unist-util-visit");
9
12
  const mdast_util_to_string_1 = require("mdast-util-to-string");
10
-
11
13
  class MarkdownToNotion {
12
14
  constructor() {
13
15
  this.description = {
@@ -93,22 +95,27 @@ class MarkdownToNotion {
93
95
  name: 'preserveMath',
94
96
  type: 'boolean',
95
97
  default: true,
96
- description: 'Whether to preserve inline math formulas like $E = mc^2$ as plain text',
98
+ description: 'Whether to preserve inline math formulas (text between $ symbols) as plain text instead of converting them',
97
99
  },
98
100
  {
99
101
  displayName: 'Math Formula Delimiter',
100
102
  name: 'mathDelimiter',
101
103
  type: 'string',
102
104
  default: '$',
103
- description: 'The character used to delimit math formulas (default: $)',
105
+ description: 'The delimiter used for inline math formulas (default: $)',
106
+ displayOptions: {
107
+ show: {
108
+ preserveMath: [true],
109
+ },
110
+ },
104
111
  },
105
112
  ],
106
113
  },
107
114
  ],
108
115
  };
109
116
  }
110
-
111
117
  async execute() {
118
+ var _a, _b, _c;
112
119
  const items = this.getInputData();
113
120
  const returnData = [];
114
121
  for (let i = 0; i < items.length; i++) {
@@ -118,24 +125,24 @@ class MarkdownToNotion {
118
125
  const markdownContent = this.getNodeParameter('markdownContent', i);
119
126
  const options = this.getNodeParameter('options', i, {});
120
127
  if (operation === 'appendToPage') {
121
- const blocks = await this.convertMarkdownToNotionBlocks(markdownContent, options.preserveMath ?? true, options.mathDelimiter ?? '$');
128
+ this.validatePageId(pageId, i, this);
129
+ this.validateMarkdownContent(markdownContent, i, this);
130
+ const blocks = await this.convertMarkdownToNotionBlocks(markdownContent, (_a = options.preserveMath) !== null && _a !== void 0 ? _a : true, (_b = options.mathDelimiter) !== null && _b !== void 0 ? _b : '$');
122
131
  const requestOptions = {
123
132
  method: 'PATCH',
124
133
  url: `https://api.notion.com/v1/blocks/${pageId}/children`,
125
- headers: {
126
- 'Notion-Version': '2022-06-28',
127
- },
128
134
  body: {
129
135
  children: blocks,
130
136
  },
131
137
  json: true,
132
138
  };
133
139
  const response = await this.helpers.httpRequestWithAuthentication.call(this, 'notionApi', requestOptions);
140
+ this.validateNotionApiResponse(response, i, this);
134
141
  returnData.push({
135
142
  json: {
136
143
  success: true,
137
144
  pageId,
138
- blocksAdded: response.results?.length || 0,
145
+ blocksAdded: ((_c = response.results) === null || _c === void 0 ? void 0 : _c.length) || 0,
139
146
  blocks: response.results,
140
147
  },
141
148
  pairedItem: {
@@ -149,6 +156,7 @@ class MarkdownToNotion {
149
156
  returnData.push({
150
157
  json: {
151
158
  error: error.message,
159
+ success: false,
152
160
  },
153
161
  pairedItem: {
154
162
  item: i,
@@ -156,12 +164,13 @@ class MarkdownToNotion {
156
164
  });
157
165
  continue;
158
166
  }
159
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), error);
167
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, {
168
+ itemIndex: i,
169
+ });
160
170
  }
161
171
  }
162
172
  return [returnData];
163
173
  }
164
-
165
174
  async convertMarkdownToNotionBlocks(markdown, preserveMath = true, mathDelimiter = '$') {
166
175
  let processedMarkdown = markdown;
167
176
  const mathPlaceholders = {};
@@ -244,7 +253,6 @@ class MarkdownToNotion {
244
253
  });
245
254
  return blocks;
246
255
  }
247
-
248
256
  createHeadingBlock(node, mathPlaceholders) {
249
257
  const level = Math.min(node.depth, 3);
250
258
  const headingType = `heading_${level}`;
@@ -256,10 +264,9 @@ class MarkdownToNotion {
256
264
  },
257
265
  };
258
266
  }
259
-
260
267
  createParagraphBlock(node, mathPlaceholders) {
261
268
  const richText = this.convertToRichText(node, mathPlaceholders);
262
- if (richText.length === 0) {
269
+ if (richText.length === 0 || (richText.length === 1 && richText[0].text.content.trim() === '')) {
263
270
  return null;
264
271
  }
265
272
  return {
@@ -270,7 +277,6 @@ class MarkdownToNotion {
270
277
  },
271
278
  };
272
279
  }
273
-
274
280
  createListBlocks(node, mathPlaceholders) {
275
281
  const blocks = [];
276
282
  for (const listItem of node.children) {
@@ -293,7 +299,6 @@ class MarkdownToNotion {
293
299
  }
294
300
  return blocks;
295
301
  }
296
-
297
302
  createCodeBlock(node) {
298
303
  return {
299
304
  object: 'block',
@@ -311,7 +316,6 @@ class MarkdownToNotion {
311
316
  },
312
317
  };
313
318
  }
314
-
315
319
  createQuoteBlock(node, mathPlaceholders) {
316
320
  return {
317
321
  object: 'block',
@@ -321,32 +325,47 @@ class MarkdownToNotion {
321
325
  },
322
326
  };
323
327
  }
324
-
325
328
  convertToRichText(node, mathPlaceholders) {
326
329
  const richText = [];
327
- this.processInlineFormatting(node, richText, mathPlaceholders);
330
+ let textContent = (0, mdast_util_to_string_1.toString)(node);
331
+ for (const [placeholder, originalMath] of Object.entries(mathPlaceholders)) {
332
+ textContent = textContent.replace(placeholder, originalMath);
333
+ }
334
+ if (textContent.trim()) {
335
+ this.processInlineFormatting(node, richText, mathPlaceholders);
336
+ }
337
+ if (richText.length === 0 && textContent.trim()) {
338
+ richText.push({
339
+ type: 'text',
340
+ text: {
341
+ content: textContent,
342
+ },
343
+ });
344
+ }
328
345
  return richText;
329
346
  }
330
-
331
347
  processInlineFormatting(node, richText, mathPlaceholders) {
332
348
  if (node.type === 'text') {
333
- const content = this.restoreMathPlaceholders(node.value, mathPlaceholders);
334
- if (content.trim()) {
349
+ let content = node.value;
350
+ for (const [placeholder, originalMath] of Object.entries(mathPlaceholders)) {
351
+ content = content.replace(placeholder, originalMath);
352
+ }
353
+ if (content) {
335
354
  richText.push({
336
355
  type: 'text',
337
356
  text: {
338
- content: content,
357
+ content,
339
358
  },
340
359
  });
341
360
  }
342
361
  }
343
362
  else if (node.type === 'strong') {
344
- const content = (0, mdast_util_to_string_1.toString)(node);
345
- if (content.trim()) {
363
+ const textContent = (0, mdast_util_to_string_1.toString)(node);
364
+ if (textContent) {
346
365
  richText.push({
347
366
  type: 'text',
348
367
  text: {
349
- content: this.restoreMathPlaceholders(content, mathPlaceholders),
368
+ content: textContent,
350
369
  },
351
370
  annotations: {
352
371
  bold: true,
@@ -355,12 +374,12 @@ class MarkdownToNotion {
355
374
  }
356
375
  }
357
376
  else if (node.type === 'emphasis') {
358
- const content = (0, mdast_util_to_string_1.toString)(node);
359
- if (content.trim()) {
377
+ const textContent = (0, mdast_util_to_string_1.toString)(node);
378
+ if (textContent) {
360
379
  richText.push({
361
380
  type: 'text',
362
381
  text: {
363
- content: this.restoreMathPlaceholders(content, mathPlaceholders),
382
+ content: textContent,
364
383
  },
365
384
  annotations: {
366
385
  italic: true,
@@ -372,7 +391,7 @@ class MarkdownToNotion {
372
391
  richText.push({
373
392
  type: 'text',
374
393
  text: {
375
- content: this.restoreMathPlaceholders(node.value, mathPlaceholders),
394
+ content: node.value,
376
395
  },
377
396
  annotations: {
378
397
  code: true,
@@ -380,15 +399,13 @@ class MarkdownToNotion {
380
399
  });
381
400
  }
382
401
  else if (node.type === 'link') {
383
- const content = (0, mdast_util_to_string_1.toString)(node);
384
- if (content.trim()) {
402
+ const textContent = (0, mdast_util_to_string_1.toString)(node);
403
+ if (textContent) {
385
404
  richText.push({
386
405
  type: 'text',
387
406
  text: {
388
- content: this.restoreMathPlaceholders(content, mathPlaceholders),
389
- link: {
390
- url: node.url,
391
- },
407
+ content: textContent,
408
+ link: { url: node.url },
392
409
  },
393
410
  });
394
411
  }
@@ -399,11 +416,9 @@ class MarkdownToNotion {
399
416
  }
400
417
  }
401
418
  }
402
-
403
419
  isTodoItem(content) {
404
420
  return /^- \[([ x])\]/.test(content);
405
421
  }
406
-
407
422
  createTodoBlock(node, mathPlaceholders) {
408
423
  const content = (0, mdast_util_to_string_1.toString)(node).trim();
409
424
  const isChecked = /^- \[x\]/.test(content);
@@ -413,20 +428,18 @@ class MarkdownToNotion {
413
428
  type: 'to_do',
414
429
  to_do: {
415
430
  rich_text: [{
416
- type: 'text',
417
- text: {
418
- content: this.restoreMathPlaceholders(textContent, mathPlaceholders),
419
- },
420
- }],
431
+ type: 'text',
432
+ text: {
433
+ content: this.restoreMathPlaceholders(textContent, mathPlaceholders),
434
+ },
435
+ }],
421
436
  checked: isChecked,
422
437
  },
423
438
  };
424
439
  }
425
-
426
440
  isDivider(content) {
427
441
  return /^(-{3,}|\*{3,})$/.test(content);
428
442
  }
429
-
430
443
  createDividerBlock() {
431
444
  return {
432
445
  object: 'block',
@@ -434,12 +447,10 @@ class MarkdownToNotion {
434
447
  divider: {},
435
448
  };
436
449
  }
437
-
438
450
  isStandaloneUrl(content) {
439
451
  const urlRegex = /^https?:\/\/[^\s]+$/;
440
452
  return urlRegex.test(content);
441
453
  }
442
-
443
454
  createBookmarkBlock(url) {
444
455
  return {
445
456
  object: 'block',
@@ -450,11 +461,9 @@ class MarkdownToNotion {
450
461
  },
451
462
  };
452
463
  }
453
-
454
464
  isBlockEquation(content) {
455
465
  return /^\$\$[\s\S]*\$\$$/.test(content.trim());
456
466
  }
457
-
458
467
  createEquationBlock(content) {
459
468
  const equation = content.replace(/^\$\$\s*|\s*\$\$$/g, '');
460
469
  return {
@@ -465,11 +474,9 @@ class MarkdownToNotion {
465
474
  },
466
475
  };
467
476
  }
468
-
469
477
  isCallout(content) {
470
478
  return /^>\s*\[!(note|warning|info|tip|important|caution)\]/i.test(content);
471
479
  }
472
-
473
480
  createCalloutBlock(node, mathPlaceholders) {
474
481
  const content = (0, mdast_util_to_string_1.toString)(node).trim();
475
482
  const match = content.match(/^>\s*\[!(note|warning|info|tip|important|caution)\]\s*(.*)/is);
@@ -489,11 +496,11 @@ class MarkdownToNotion {
489
496
  type: 'callout',
490
497
  callout: {
491
498
  rich_text: [{
492
- type: 'text',
493
- text: {
494
- content: this.restoreMathPlaceholders(calloutContent, mathPlaceholders),
495
- },
496
- }],
499
+ type: 'text',
500
+ text: {
501
+ content: this.restoreMathPlaceholders(calloutContent, mathPlaceholders),
502
+ },
503
+ }],
497
504
  icon: {
498
505
  type: 'emoji',
499
506
  emoji: iconMap[calloutType] || '📝',
@@ -504,7 +511,6 @@ class MarkdownToNotion {
504
511
  }
505
512
  return this.createQuoteBlock(node, mathPlaceholders);
506
513
  }
507
-
508
514
  createImageBlock(node) {
509
515
  return {
510
516
  object: 'block',
@@ -515,16 +521,16 @@ class MarkdownToNotion {
515
521
  url: node.url,
516
522
  },
517
523
  caption: node.alt ? [{
518
- type: 'text',
519
- text: {
520
- content: node.alt,
521
- },
522
- }] : [],
524
+ type: 'text',
525
+ text: {
526
+ content: node.alt,
527
+ },
528
+ }] : [],
523
529
  },
524
530
  };
525
531
  }
526
-
527
532
  createTableBlocks(node, mathPlaceholders) {
533
+ var _a, _b;
528
534
  const blocks = [];
529
535
  if (!node.children || node.children.length === 0) {
530
536
  return blocks;
@@ -535,12 +541,12 @@ class MarkdownToNotion {
535
541
  const isHeader = i === 0;
536
542
  const cells = row.children.map((cell) => ({
537
543
  rich_text: [{
538
- type: 'text',
539
- text: {
540
- content: this.restoreMathPlaceholders((0, mdast_util_to_string_1.toString)(cell), mathPlaceholders),
541
- },
542
- annotations: isHeader ? { bold: true } : {},
543
- }],
544
+ type: 'text',
545
+ text: {
546
+ content: this.restoreMathPlaceholders((0, mdast_util_to_string_1.toString)(cell), mathPlaceholders),
547
+ },
548
+ annotations: isHeader ? { bold: true } : {},
549
+ }],
544
550
  }));
545
551
  blocks.push({
546
552
  object: 'block',
@@ -555,7 +561,7 @@ class MarkdownToNotion {
555
561
  object: 'block',
556
562
  type: 'table',
557
563
  table: {
558
- table_width: tableRows[0]?.children?.length || 1,
564
+ table_width: ((_b = (_a = tableRows[0]) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b.length) || 1,
559
565
  has_column_header: true,
560
566
  has_row_header: false,
561
567
  children: blocks,
@@ -565,7 +571,13 @@ class MarkdownToNotion {
565
571
  }
566
572
  return blocks;
567
573
  }
568
-
574
+ restoreMathPlaceholders(text, mathPlaceholders) {
575
+ let result = text;
576
+ for (const [placeholder, originalMath] of Object.entries(mathPlaceholders)) {
577
+ result = result.replace(placeholder, originalMath);
578
+ }
579
+ return result;
580
+ }
569
581
  preprocessToggleBlocks(markdown) {
570
582
  const detailsRegex = /<details>\s*<summary>(.*?)<\/summary>\s*([\s\S]*?)<\/details>/gi;
571
583
  return markdown.replace(detailsRegex, (match, summary, content) => {
@@ -574,11 +586,9 @@ class MarkdownToNotion {
574
586
  return `__TOGGLE_START__${cleanSummary}__TOGGLE_CONTENT__${cleanContent}__TOGGLE_END__`;
575
587
  });
576
588
  }
577
-
578
589
  isToggleBlock(content) {
579
590
  return content.includes('__TOGGLE_START__') && content.includes('__TOGGLE_END__');
580
591
  }
581
-
582
592
  createToggleBlock(content, mathPlaceholders) {
583
593
  const match = content.match(/__TOGGLE_START__(.*?)__TOGGLE_CONTENT__(.*?)__TOGGLE_END__/s);
584
594
  if (!match) {
@@ -606,31 +616,33 @@ class MarkdownToNotion {
606
616
  type: headingType,
607
617
  [headingType]: {
608
618
  rich_text: [{
609
- type: 'text',
610
- text: { content: headingText }
611
- }]
619
+ type: 'text',
620
+ text: { content: headingText }
621
+ }]
612
622
  }
613
623
  });
614
- } else if (trimmedLine.startsWith('- ')) {
624
+ }
625
+ else if (trimmedLine.startsWith('- ')) {
615
626
  children.push({
616
627
  object: 'block',
617
628
  type: 'bulleted_list_item',
618
629
  bulleted_list_item: {
619
630
  rich_text: [{
620
- type: 'text',
621
- text: { content: trimmedLine.substring(2) }
622
- }]
631
+ type: 'text',
632
+ text: { content: trimmedLine.substring(2) }
633
+ }]
623
634
  }
624
635
  });
625
- } else {
636
+ }
637
+ else {
626
638
  children.push({
627
639
  object: 'block',
628
640
  type: 'paragraph',
629
641
  paragraph: {
630
642
  rich_text: [{
631
- type: 'text',
632
- text: { content: trimmedLine }
633
- }]
643
+ type: 'text',
644
+ text: { content: trimmedLine }
645
+ }]
634
646
  }
635
647
  });
636
648
  }
@@ -642,24 +654,37 @@ class MarkdownToNotion {
642
654
  type: 'toggle',
643
655
  toggle: {
644
656
  rich_text: [{
645
- type: 'text',
646
- text: {
647
- content: cleanSummary || 'Toggle'
648
- }
649
- }],
657
+ type: 'text',
658
+ text: {
659
+ content: cleanSummary || 'Toggle'
660
+ }
661
+ }],
650
662
  color: 'default',
651
663
  children: children
652
664
  }
653
665
  };
654
666
  }
655
-
656
- restoreMathPlaceholders(text, mathPlaceholders) {
657
- let result = text;
658
- for (const [placeholder, originalMath] of Object.entries(mathPlaceholders)) {
659
- result = result.replace(placeholder, originalMath);
667
+ validatePageId(pageId, itemIndex, context) {
668
+ if (!pageId || !pageId.trim()) {
669
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Page ID is required and cannot be empty.', { itemIndex });
670
+ }
671
+ const cleanPageId = pageId.replace(/-/g, '');
672
+ if (!/^[a-f0-9]{32}$/i.test(cleanPageId)) {
673
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Invalid Page ID format. Expected a UUID (32 or 36 characters). You can find the Page ID in the Notion page URL.', { itemIndex });
674
+ }
675
+ }
676
+ validateMarkdownContent(markdownContent, itemIndex, context) {
677
+ if (!markdownContent || !markdownContent.trim()) {
678
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Markdown content is required and cannot be empty.', { itemIndex });
679
+ }
680
+ }
681
+ validateNotionApiResponse(response, itemIndex, context) {
682
+ if (!response || typeof response !== 'object') {
683
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Unexpected Notion API response: ${JSON.stringify(response)}`, { itemIndex });
684
+ }
685
+ if (response.object === 'error') {
686
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Notion API error: ${response.message || 'Unknown error'}`, { itemIndex });
660
687
  }
661
- return result;
662
688
  }
663
689
  }
664
-
665
690
  exports.MarkdownToNotion = MarkdownToNotion;