n8n-nodes-notion-advanced 1.2.14-beta → 1.2.15-beta

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.
@@ -48,6 +48,7 @@ export declare class NotionAITool implements INodeType {
48
48
  static processXmlTags(content: string, blocks: IDataObject[]): string;
49
49
  static cleanupRemainingHtml(content: string, placeholderPrefix?: string): string;
50
50
  static processNestedHtmlInListItem(content: string): string;
51
+ static convertInlineHtmlToMarkdown(content: string): string;
51
52
  static processNestedList(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[]): void;
52
53
  static getCalloutEmoji(type: string): string;
53
54
  static getCalloutColor(type: string): string;
@@ -765,22 +765,37 @@ class NotionAITool {
765
765
  replacements.set(child.id, childReplacement);
766
766
  }
767
767
  // Extract inner content (content between opening and closing tags)
768
- let innerContent = node.innerContent;
769
- // Extract content between opening and closing tags
770
- const openTagMatch = node.match.match(/^<[^>]+>/);
771
- const closeTagMatch = node.match.match(/<\/[^>]+>$/);
772
- if (openTagMatch && closeTagMatch) {
773
- const openTag = openTagMatch[0];
774
- const closeTag = closeTagMatch[0];
775
- const startIndex = node.match.indexOf(openTag) + openTag.length;
776
- const endIndex = node.match.lastIndexOf(closeTag);
777
- innerContent = node.match.substring(startIndex, endIndex);
768
+ let innerContent = '';
769
+ try {
770
+ // More robust inner content extraction
771
+ const tagName = node.tagName.toLowerCase();
772
+ const openTagRegex = new RegExp(`^<${tagName}[^>]*>`, 'i');
773
+ const closeTagRegex = new RegExp(`</${tagName}>$`, 'i');
774
+ const openMatch = node.match.match(openTagRegex);
775
+ const closeMatch = node.match.match(closeTagRegex);
776
+ if (openMatch && closeMatch) {
777
+ const openTag = openMatch[0];
778
+ const closeTag = closeMatch[0];
779
+ const startIndex = openTag.length;
780
+ const endIndex = node.match.length - closeTag.length;
781
+ innerContent = node.match.substring(startIndex, endIndex);
782
+ }
783
+ else {
784
+ // Fallback for self-closing or malformed tags
785
+ innerContent = node.match.replace(/^<[^>]*>/, '').replace(/<\/[^>]*>$/, '');
786
+ }
778
787
  // Replace child nodes in inner content with their processed content
779
788
  for (const child of node.children) {
780
789
  const childReplacement = replacements.get(child.id) || '';
781
- innerContent = innerContent.replace(child.match, childReplacement);
790
+ if (childReplacement !== undefined && innerContent.includes(child.match)) {
791
+ innerContent = innerContent.replace(child.match, childReplacement);
792
+ }
782
793
  }
783
794
  }
795
+ catch (error) {
796
+ console.warn(`Error extracting inner content for ${node.tagName}:`, error);
797
+ innerContent = node.match;
798
+ }
784
799
  // Process this node with updated inner content
785
800
  try {
786
801
  // Handle special list processors
@@ -1005,10 +1020,12 @@ class NotionAITool {
1005
1020
  {
1006
1021
  regex: /<p>(.*?)<\/p>/gis,
1007
1022
  blockCreator: (content) => {
1023
+ // First convert HTML tags to markdown, then parse to rich text
1024
+ const markdownContent = NotionAITool.convertInlineHtmlToMarkdown(content.trim());
1008
1025
  return {
1009
1026
  type: 'paragraph',
1010
1027
  paragraph: {
1011
- rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
1028
+ rich_text: NotionAITool.parseBasicMarkdown(markdownContent),
1012
1029
  },
1013
1030
  };
1014
1031
  }
@@ -1065,10 +1082,12 @@ class NotionAITool {
1065
1082
  regex: /<li\s*[^>]*>(.*?)<\/li>/gis,
1066
1083
  blockCreator: (content) => {
1067
1084
  if (content.trim()) {
1085
+ // Convert HTML to markdown first, then parse to rich text
1086
+ const markdownContent = NotionAITool.convertInlineHtmlToMarkdown(content.trim());
1068
1087
  return {
1069
1088
  type: 'bulleted_list_item',
1070
1089
  bulleted_list_item: {
1071
- rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
1090
+ rich_text: NotionAITool.parseBasicMarkdown(markdownContent),
1072
1091
  },
1073
1092
  };
1074
1093
  }
@@ -1185,14 +1204,27 @@ class NotionAITool {
1185
1204
  let cleaned = content;
1186
1205
  // Remove XML_BLOCK placeholder artifacts (support both old and new format)
1187
1206
  if (placeholderPrefix) {
1188
- const placeholderRegex = new RegExp(`${placeholderPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\d+__`, 'g');
1189
- cleaned = cleaned.replace(placeholderRegex, '');
1190
- }
1191
- else {
1192
- // Fallback for backward compatibility
1193
- cleaned = cleaned.replace(/__XML_BLOCK_\d+__/g, '');
1194
- cleaned = cleaned.replace(/__XML_[a-f0-9]{8}_\d+__/g, '');
1207
+ // More aggressive placeholder cleanup with multiple patterns
1208
+ const placeholderPatterns = [
1209
+ new RegExp(`${placeholderPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\d+__`, 'g'),
1210
+ new RegExp(`_${placeholderPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\d+_`, 'g'),
1211
+ new RegExp(`${placeholderPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\d+`, 'g')
1212
+ ];
1213
+ placeholderPatterns.forEach(pattern => {
1214
+ cleaned = cleaned.replace(pattern, '');
1215
+ });
1195
1216
  }
1217
+ // Comprehensive fallback cleanup for all possible placeholder formats
1218
+ const fallbackPatterns = [
1219
+ /__XML_BLOCK_\d+__/g,
1220
+ /__XML_[a-f0-9]{8}_\d+__/g,
1221
+ /_XML_[a-f0-9]{8}_\d+_/g,
1222
+ /__XML_[a-f0-9-]+_\d+__/g,
1223
+ /_XML_[a-f0-9-]+_\d+_/g
1224
+ ];
1225
+ fallbackPatterns.forEach(pattern => {
1226
+ cleaned = cleaned.replace(pattern, '');
1227
+ });
1196
1228
  // Remove common HTML tags that might be left behind
1197
1229
  const htmlTagsToRemove = [
1198
1230
  /<\/?ul\s*[^>]*>/gi,
@@ -1220,16 +1252,88 @@ class NotionAITool {
1220
1252
  cleaned = cleaned.replace(/^\s*[\r\n]/gm, '');
1221
1253
  // Remove multiple consecutive line breaks
1222
1254
  cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
1223
- // Remove lines that contain only XML_BLOCK artifacts
1224
- cleaned = cleaned.replace(/^.*__XML_BLOCK_\d+__.*$/gm, '');
1225
- cleaned = cleaned.replace(/^.*__XML_[a-f0-9]{8}_\d+__.*$/gm, '');
1255
+ // Remove lines that contain only XML_BLOCK artifacts (more patterns)
1256
+ const artifactPatterns = [
1257
+ /^.*__XML_BLOCK_\d+__.*$/gm,
1258
+ /^.*__XML_[a-f0-9]{8}_\d+__.*$/gm,
1259
+ /^.*_XML_[a-f0-9]{8}_\d+_.*$/gm,
1260
+ /^.*__XML_[a-f0-9-]+_\d+__.*$/gm,
1261
+ /^.*_XML_[a-f0-9-]+_\d+_.*$/gm
1262
+ ];
1263
+ artifactPatterns.forEach(pattern => {
1264
+ cleaned = cleaned.replace(pattern, '');
1265
+ });
1266
+ // Final cleanup of any remaining isolated placeholder patterns
1267
+ cleaned = cleaned.replace(/\b_+XML_[a-f0-9-]+_\d+_*\b/g, '');
1268
+ cleaned = cleaned.replace(/\b_*XML_[a-f0-9-]+_\d+_+\b/g, '');
1226
1269
  return cleaned.trim();
1227
1270
  }
1228
1271
  // Helper function to process nested HTML elements in list items
1229
1272
  static processNestedHtmlInListItem(content) {
1273
+ let processed = content.trim();
1274
+ if (!processed)
1275
+ return '';
1276
+ try {
1277
+ // Handle multiple segments separated by HTML block elements
1278
+ const segments = [];
1279
+ // Split by block-level HTML elements like <p>, <div>, etc.
1280
+ const blockElements = /<(p|div|h[1-6]|blockquote)\s*[^>]*>.*?<\/\1>/gis;
1281
+ let lastIndex = 0;
1282
+ let match;
1283
+ const blockMatches = [];
1284
+ while ((match = blockElements.exec(processed)) !== null) {
1285
+ blockMatches.push({
1286
+ start: match.index,
1287
+ end: match.index + match[0].length,
1288
+ content: match[0],
1289
+ tag: match[1]
1290
+ });
1291
+ }
1292
+ // Sort matches by position
1293
+ blockMatches.sort((a, b) => a.start - b.start);
1294
+ // Process text segments between block elements
1295
+ blockMatches.forEach((blockMatch, index) => {
1296
+ // Add text before this block element
1297
+ if (blockMatch.start > lastIndex) {
1298
+ const beforeText = processed.substring(lastIndex, blockMatch.start).trim();
1299
+ if (beforeText) {
1300
+ segments.push(NotionAITool.convertInlineHtmlToMarkdown(beforeText));
1301
+ }
1302
+ }
1303
+ // Process content inside block element
1304
+ const innerContent = blockMatch.content.replace(new RegExp(`^<${blockMatch.tag}[^>]*>`, 'i'), '')
1305
+ .replace(new RegExp(`</${blockMatch.tag}>$`, 'i'), '')
1306
+ .trim();
1307
+ if (innerContent) {
1308
+ segments.push(NotionAITool.convertInlineHtmlToMarkdown(innerContent));
1309
+ }
1310
+ lastIndex = blockMatch.end;
1311
+ });
1312
+ // Add remaining text after last block element
1313
+ if (lastIndex < processed.length) {
1314
+ const remainingText = processed.substring(lastIndex).trim();
1315
+ if (remainingText) {
1316
+ segments.push(NotionAITool.convertInlineHtmlToMarkdown(remainingText));
1317
+ }
1318
+ }
1319
+ // If no block elements were found, process the whole content
1320
+ if (blockMatches.length === 0) {
1321
+ segments.push(NotionAITool.convertInlineHtmlToMarkdown(processed));
1322
+ }
1323
+ // Join segments with space and clean up
1324
+ const result = segments.filter(s => s.trim()).join(' ').trim();
1325
+ // Final cleanup of any remaining artifacts
1326
+ return result.replace(/\s+/g, ' ').trim();
1327
+ }
1328
+ catch (error) {
1329
+ console.warn('Error processing nested HTML in list item:', error);
1330
+ // Fallback: just remove HTML tags and return text
1331
+ return processed.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
1332
+ }
1333
+ }
1334
+ // Helper function to convert inline HTML to markdown
1335
+ static convertInlineHtmlToMarkdown(content) {
1230
1336
  let processed = content;
1231
- // First, remove wrapping <p> tags (common in nested content)
1232
- processed = processed.replace(/^<p\s*[^>]*>(.*?)<\/p>$/gis, '$1');
1233
1337
  // Convert HTML formatting tags to markdown equivalents
1234
1338
  const htmlToMarkdown = [
1235
1339
  { regex: /<strong\s*[^>]*>(.*?)<\/strong>/gis, replacement: '**$1**' },
@@ -1249,9 +1353,7 @@ class NotionAITool {
1249
1353
  });
1250
1354
  // Remove any remaining HTML tags that we don't handle
1251
1355
  const tagsToRemove = [
1252
- /<\/?div\s*[^>]*>/gi,
1253
1356
  /<\/?span\s*[^>]*>/gi,
1254
- /<\/?p\s*[^>]*>/gi,
1255
1357
  /<br\s*\/?>/gi,
1256
1358
  ];
1257
1359
  tagsToRemove.forEach(regex => {
@@ -1263,56 +1365,64 @@ class NotionAITool {
1263
1365
  }
1264
1366
  // Helper function to process nested lists and flatten them for Notion
1265
1367
  static processNestedList(listContent, listType, blocks) {
1266
- // Extract top-level list items using a more careful approach
1267
- const items = [];
1268
- let currentPos = 0;
1269
- while (currentPos < listContent.length) {
1270
- const liStart = listContent.indexOf('<li', currentPos);
1271
- if (liStart === -1)
1272
- break;
1273
- const liEndTag = listContent.indexOf('>', liStart);
1274
- if (liEndTag === -1)
1275
- break;
1276
- // Find the matching closing </li> tag, accounting for nested content
1277
- let depth = 1;
1278
- let searchPos = liEndTag + 1;
1279
- let liEnd = -1;
1280
- while (searchPos < listContent.length && depth > 0) {
1281
- const nextLiStart = listContent.indexOf('<li', searchPos);
1282
- const nextLiEnd = listContent.indexOf('</li>', searchPos);
1283
- if (nextLiEnd === -1)
1284
- break;
1285
- if (nextLiStart !== -1 && nextLiStart < nextLiEnd) {
1286
- depth++;
1287
- searchPos = nextLiStart + 3;
1288
- }
1289
- else {
1290
- depth--;
1291
- if (depth === 0) {
1292
- liEnd = nextLiEnd;
1368
+ try {
1369
+ // More robust list item extraction using regex
1370
+ const liRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi;
1371
+ let match;
1372
+ while ((match = liRegex.exec(listContent)) !== null) {
1373
+ let itemContent = match[1].trim();
1374
+ if (!itemContent)
1375
+ continue;
1376
+ // Check if this item contains nested lists
1377
+ const hasNestedList = /<[uo]l\s*[^>]*>/i.test(itemContent);
1378
+ if (hasNestedList) {
1379
+ // Split content into parts: before nested list, nested list, after nested list
1380
+ const parts = itemContent.split(/(<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>)/i);
1381
+ // Process the main content (before nested list)
1382
+ const mainContent = parts[0] ? parts[0].trim() : '';
1383
+ if (mainContent) {
1384
+ const cleanContent = NotionAITool.processNestedHtmlInListItem(mainContent);
1385
+ if (cleanContent) {
1386
+ blocks.push({
1387
+ type: listType,
1388
+ [listType]: {
1389
+ rich_text: NotionAITool.parseBasicMarkdown(cleanContent),
1390
+ },
1391
+ });
1392
+ }
1393
+ }
1394
+ // Process nested lists
1395
+ for (let i = 1; i < parts.length; i += 2) {
1396
+ const nestedListHtml = parts[i];
1397
+ if (nestedListHtml) {
1398
+ const nestedListMatch = nestedListHtml.match(/<([uo]l)\s*[^>]*>([\s\S]*?)<\/\1>/i);
1399
+ if (nestedListMatch) {
1400
+ const [, nestedListTag, nestedContent] = nestedListMatch;
1401
+ const nestedListType = nestedListTag === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
1402
+ // Recursively process nested list
1403
+ NotionAITool.processNestedList(nestedContent, nestedListType, blocks);
1404
+ }
1405
+ }
1406
+ }
1407
+ // Process any content after nested lists
1408
+ if (parts.length > 2) {
1409
+ const afterContent = parts.slice(2).join('').trim();
1410
+ if (afterContent) {
1411
+ const cleanContent = NotionAITool.processNestedHtmlInListItem(afterContent);
1412
+ if (cleanContent) {
1413
+ blocks.push({
1414
+ type: listType,
1415
+ [listType]: {
1416
+ rich_text: NotionAITool.parseBasicMarkdown(cleanContent),
1417
+ },
1418
+ });
1419
+ }
1420
+ }
1293
1421
  }
1294
- searchPos = nextLiEnd + 5;
1295
1422
  }
1296
- }
1297
- if (liEnd === -1)
1298
- break;
1299
- // Extract the full <li>...</li> content
1300
- const fullItem = listContent.substring(liStart, liEnd + 5);
1301
- items.push(fullItem);
1302
- currentPos = liEnd + 5;
1303
- }
1304
- // Process each top-level item
1305
- items.forEach(item => {
1306
- // Remove the outer <li> tags
1307
- let itemContent = item.replace(/^<li[^>]*>/, '').replace(/<\/li>$/, '').trim();
1308
- // Check if this item contains nested lists
1309
- const hasNestedList = /<[uo]l\s*[^>]*>/i.test(itemContent);
1310
- if (hasNestedList) {
1311
- // Extract the text before the nested list
1312
- const beforeNestedList = itemContent.replace(/<[uo]l\s*[^>]*>.*$/is, '').trim();
1313
- if (beforeNestedList) {
1314
- // Clean up and add the main item
1315
- const cleanContent = NotionAITool.processNestedHtmlInListItem(beforeNestedList);
1423
+ else {
1424
+ // Simple item without nested lists
1425
+ const cleanContent = NotionAITool.processNestedHtmlInListItem(itemContent);
1316
1426
  if (cleanContent) {
1317
1427
  blocks.push({
1318
1428
  type: listType,
@@ -1322,28 +1432,18 @@ class NotionAITool {
1322
1432
  });
1323
1433
  }
1324
1434
  }
1325
- // Extract and process nested lists
1326
- const nestedListMatch = itemContent.match(/<([uo]l)\s*[^>]*>(.*?)<\/\1>/is);
1327
- if (nestedListMatch) {
1328
- const [, nestedListTag, nestedContent] = nestedListMatch;
1329
- const nestedListType = nestedListTag === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
1330
- // Recursively process nested list
1331
- NotionAITool.processNestedList(nestedContent, nestedListType, blocks);
1332
- }
1333
- }
1334
- else {
1335
- // Simple item without nested lists
1336
- const cleanContent = NotionAITool.processNestedHtmlInListItem(itemContent);
1337
- if (cleanContent) {
1338
- blocks.push({
1339
- type: listType,
1340
- [listType]: {
1341
- rich_text: NotionAITool.parseBasicMarkdown(cleanContent),
1342
- },
1343
- });
1344
- }
1345
1435
  }
1346
- });
1436
+ }
1437
+ catch (error) {
1438
+ console.warn('Error processing nested list:', error);
1439
+ // Fallback: create a simple text block with the content
1440
+ blocks.push({
1441
+ type: 'paragraph',
1442
+ paragraph: {
1443
+ rich_text: [{ type: 'text', text: { content: 'Error processing list content' } }],
1444
+ },
1445
+ });
1446
+ }
1347
1447
  }
1348
1448
  // Helper function to get callout emoji based on type
1349
1449
  static getCalloutEmoji(type) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-notion-advanced",
3
- "version": "1.2.14-beta",
3
+ "version": "1.2.15-beta",
4
4
  "description": "Advanced n8n Notion nodes: Full-featured workflow node + AI Agent Tool for intelligent Notion automation with 25+ block types (BETA)",
5
5
  "scripts": {},
6
6
  "files": [