n8n-nodes-notion-advanced 1.2.26-beta → 1.2.28-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.
@@ -55,6 +55,7 @@ export declare class NotionAITool implements INodeType {
55
55
  static processNestedHtmlInListItem(content: string): string;
56
56
  static convertInlineHtmlToMarkdown(content: string): string;
57
57
  static processNestedList(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[]): void;
58
+ static extractListItems(content: string): string[];
58
59
  static getCalloutEmoji(type: string): string;
59
60
  static getCalloutColor(type: string): string;
60
61
  static parseBasicMarkdown(text: string): IDataObject[];
@@ -837,7 +837,34 @@ class NotionAITool {
837
837
  static processXMLTreeDepthFirst(nodes, blocks, placeholderCounter) {
838
838
  const replacements = new Map();
839
839
  const processNode = (node) => {
840
- // First, process all children depth-first
840
+ // For lists, use the original match content to preserve structure
841
+ // This is the only case where we skip child processing to avoid fragmentation
842
+ if (node.listProcessor && (node.tagName === 'ul' || node.tagName === 'ol')) {
843
+ try {
844
+ // Extract inner content directly from the original match
845
+ const tagName = node.tagName.toLowerCase();
846
+ const openTagRegex = new RegExp(`^<${tagName}[^>]*>`, 'i');
847
+ const closeTagRegex = new RegExp(`</${tagName}>$`, 'i');
848
+ let innerContent = node.match;
849
+ const openMatch = node.match.match(openTagRegex);
850
+ const closeMatch = node.match.match(closeTagRegex);
851
+ if (openMatch && closeMatch) {
852
+ const openTag = openMatch[0];
853
+ const closeTag = closeMatch[0];
854
+ const startIndex = openTag.length;
855
+ const endIndex = node.match.length - closeTag.length;
856
+ innerContent = node.match.substring(startIndex, endIndex);
857
+ }
858
+ // Process the list with complete content
859
+ node.listProcessor(innerContent, blocks);
860
+ return ''; // Remove completely - no placeholder needed
861
+ }
862
+ catch (error) {
863
+ console.warn(`Error processing list node ${node.tagName}:`, error);
864
+ return ''; // Remove even on error to prevent artifacts
865
+ }
866
+ }
867
+ // For non-list nodes, process children first (normal hierarchical processing)
841
868
  for (const child of node.children) {
842
869
  const childReplacement = processNode(child);
843
870
  replacements.set(child.id, childReplacement);
@@ -876,21 +903,16 @@ class NotionAITool {
876
903
  }
877
904
  // Process this node with updated inner content
878
905
  try {
879
- // Handle special list processors
880
- if (node.listProcessor && (node.tagName === 'ul' || node.tagName === 'ol')) {
881
- node.listProcessor(innerContent, blocks);
882
- return `__BLOCK_${placeholderCounter.value++}__`;
883
- }
884
906
  // Use blockCreator to create the block
885
907
  const block = node.processor(...node.groups);
886
908
  if (block) {
887
909
  blocks.push(block);
888
910
  }
889
- return `__BLOCK_${placeholderCounter.value++}__`;
911
+ return ''; // Remove completely - no placeholder needed
890
912
  }
891
913
  catch (error) {
892
914
  console.warn(`Error processing XML node ${node.tagName}:`, error);
893
- return node.match; // Return original if processing fails
915
+ return ''; // Remove even on error to prevent artifacts
894
916
  }
895
917
  };
896
918
  // Process all root nodes
@@ -1203,9 +1225,7 @@ class NotionAITool {
1203
1225
  const replacements = NotionAITool.processXMLTreeDepthFirst(xmlTree, blocks, counterRef);
1204
1226
  // Step 3: Apply hierarchical replacements to content
1205
1227
  processedContent = NotionAITool.applyHierarchicalReplacements(processedContent, xmlTree, replacements);
1206
- // Step 4: Immediately replace all placeholders with empty strings since blocks are already in blocks array
1207
- processedContent = NotionAITool.cleanupAllPlaceholders(processedContent);
1208
- // Step 5: Clean up any remaining HTML tags
1228
+ // Step 4: Clean up any remaining HTML tags
1209
1229
  processedContent = NotionAITool.cleanupRemainingHtml(processedContent);
1210
1230
  if (DEBUG_ORDERING) {
1211
1231
  console.log(`Processed ${xmlTree.length} root XML nodes hierarchically, created ${blocks.length} blocks`);
@@ -1229,11 +1249,11 @@ class NotionAITool {
1229
1249
  if (block) {
1230
1250
  blocks.push(block);
1231
1251
  }
1232
- return `__BLOCK_${placeholderCounter++}__`;
1252
+ return ''; // Remove completely - no placeholder needed
1233
1253
  }
1234
1254
  catch (error) {
1235
1255
  console.warn('Error in fallback processor:', error);
1236
- return match;
1256
+ return ''; // Remove even on error
1237
1257
  }
1238
1258
  },
1239
1259
  groups: match.slice(1)
@@ -1401,61 +1421,28 @@ class NotionAITool {
1401
1421
  if (!processed)
1402
1422
  return '';
1403
1423
  try {
1404
- // Handle multiple segments separated by HTML block elements
1405
- const segments = [];
1406
- // Split by block-level HTML elements like <p>, <div>, etc.
1407
- const blockElements = /<(p|div|h[1-6]|blockquote)\s*[^>]*>.*?<\/\1>/gis;
1408
- let lastIndex = 0;
1409
- let match;
1410
- const blockMatches = [];
1411
- while ((match = blockElements.exec(processed)) !== null) {
1412
- blockMatches.push({
1413
- start: match.index,
1414
- end: match.index + match[0].length,
1415
- content: match[0],
1416
- tag: match[1]
1417
- });
1418
- }
1419
- // Sort matches by position
1420
- blockMatches.sort((a, b) => a.start - b.start);
1421
- // Process text segments between block elements
1422
- blockMatches.forEach((blockMatch, index) => {
1423
- // Add text before this block element
1424
- if (blockMatch.start > lastIndex) {
1425
- const beforeText = processed.substring(lastIndex, blockMatch.start).trim();
1426
- if (beforeText) {
1427
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(beforeText));
1428
- }
1429
- }
1430
- // Process content inside block element
1431
- const innerContent = blockMatch.content.replace(new RegExp(`^<${blockMatch.tag}[^>]*>`, 'i'), '')
1432
- .replace(new RegExp(`</${blockMatch.tag}>$`, 'i'), '')
1433
- .trim();
1434
- if (innerContent) {
1435
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(innerContent));
1436
- }
1437
- lastIndex = blockMatch.end;
1438
- });
1439
- // Add remaining text after last block element
1440
- if (lastIndex < processed.length) {
1441
- const remainingText = processed.substring(lastIndex).trim();
1442
- if (remainingText) {
1443
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(remainingText));
1444
- }
1445
- }
1446
- // If no block elements were found, process the whole content
1447
- if (blockMatches.length === 0) {
1448
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(processed));
1449
- }
1450
- // Join segments with space and clean up
1451
- const result = segments.filter(s => s.trim()).join(' ').trim();
1452
- // Final cleanup of any remaining artifacts
1453
- return result.replace(/\s+/g, ' ').trim();
1424
+ // First, aggressively remove any nested list tags and their content
1425
+ // This prevents XML fragments from appearing in the final content
1426
+ processed = processed.replace(/<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>/gi, '');
1427
+ // Remove any standalone list item tags that might be left behind
1428
+ processed = processed.replace(/<\/?li\s*[^>]*>/gi, '');
1429
+ // Remove any other common list-related fragments
1430
+ processed = processed.replace(/<\/?[uo]l\s*[^>]*>/gi, '');
1431
+ // Simple cleanup - just remove remaining HTML tags and clean whitespace
1432
+ // Avoid convertInlineHtmlToMarkdown to prevent duplication issues
1433
+ const result = processed
1434
+ .replace(/<[^>]*>/g, ' ') // Remove any remaining HTML tags
1435
+ .replace(/\s+/g, ' ') // Clean up whitespace
1436
+ .trim();
1437
+ return result;
1454
1438
  }
1455
1439
  catch (error) {
1456
1440
  console.warn('Error processing nested HTML in list item:', error);
1457
- // Fallback: just remove HTML tags and return text
1458
- return processed.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
1441
+ // Fallback: aggressively remove all HTML tags and return clean text
1442
+ return processed
1443
+ .replace(/<[^>]*>/g, ' ')
1444
+ .replace(/\s+/g, ' ')
1445
+ .trim();
1459
1446
  }
1460
1447
  }
1461
1448
  // Helper function to convert inline HTML to markdown
@@ -1493,49 +1480,36 @@ class NotionAITool {
1493
1480
  // Helper function to process nested lists and flatten them for Notion
1494
1481
  static processNestedList(listContent, listType, blocks) {
1495
1482
  try {
1496
- // More robust list item extraction using regex
1497
- const liRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi;
1498
- let match;
1499
- while ((match = liRegex.exec(listContent)) !== null) {
1500
- let itemContent = match[1].trim();
1501
- if (!itemContent)
1483
+ // More robust list item extraction that handles nested <li> tags properly
1484
+ const listItems = NotionAITool.extractListItems(listContent);
1485
+ for (const itemContent of listItems) {
1486
+ if (!itemContent.trim())
1502
1487
  continue;
1503
1488
  // Check if this item contains nested lists
1504
1489
  const hasNestedList = /<[uo]l\s*[^>]*>/i.test(itemContent);
1505
1490
  if (hasNestedList) {
1506
- // Split content into parts: before nested list, nested list, after nested list
1507
- const parts = itemContent.split(/(<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>)/i);
1508
- // Process the main content (before nested list)
1509
- const mainContent = parts[0] ? parts[0].trim() : '';
1510
- if (mainContent) {
1511
- const cleanContent = NotionAITool.processNestedHtmlInListItem(mainContent);
1512
- if (cleanContent) {
1513
- blocks.push({
1514
- type: listType,
1515
- [listType]: {
1516
- rich_text: NotionAITool.parseBasicMarkdown(cleanContent),
1517
- },
1518
- });
1519
- }
1520
- }
1521
- // Process nested lists
1522
- for (let i = 1; i < parts.length; i += 2) {
1523
- const nestedListHtml = parts[i];
1524
- if (nestedListHtml) {
1525
- const nestedListMatch = nestedListHtml.match(/<([uo]l)\s*[^>]*>([\s\S]*?)<\/\1>/i);
1491
+ // Split content into text parts and nested list parts
1492
+ const parts = itemContent.split(/(<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>)/gi);
1493
+ for (let i = 0; i < parts.length; i++) {
1494
+ const part = parts[i].trim();
1495
+ if (!part)
1496
+ continue;
1497
+ // Check if this part is a nested list
1498
+ const isNestedList = /<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>/gi.test(part);
1499
+ if (isNestedList) {
1500
+ // Process the nested list
1501
+ const nestedListMatch = part.match(/<([uo]l)\s*[^>]*>([\s\S]*?)<\/\1>/i);
1526
1502
  if (nestedListMatch) {
1527
- const [, nestedListTag, nestedContent] = nestedListMatch;
1528
- const nestedListType = nestedListTag === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
1503
+ const [, listTag, innerContent] = nestedListMatch;
1504
+ const nestedListType = listTag === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
1529
1505
  // Recursively process nested list
1530
- NotionAITool.processNestedList(nestedContent, nestedListType, blocks);
1506
+ NotionAITool.processNestedList(innerContent, nestedListType, blocks);
1531
1507
  }
1532
1508
  }
1533
- }
1534
- // Process any content after nested lists
1535
- if (parts.length > 2) {
1536
- const afterContent = parts.slice(2).join('').trim();
1537
- if (afterContent) {
1538
- const cleanContent = NotionAITool.processNestedHtmlInListItem(afterContent);
1509
+ else {
1510
+ // This is text content - clean it and add as a list item
1511
+ // Only process non-empty text parts as separate list items
1512
+ const cleanContent = NotionAITool.processNestedHtmlInListItem(part);
1539
1513
  if (cleanContent) {
1540
1514
  blocks.push({
1541
1515
  type: listType,
@@ -1572,6 +1546,62 @@ class NotionAITool {
1572
1546
  });
1573
1547
  }
1574
1548
  }
1549
+ // Helper function to properly extract list items handling nested <li> tags
1550
+ static extractListItems(content) {
1551
+ const items = [];
1552
+ let currentPos = 0;
1553
+ while (currentPos < content.length) {
1554
+ // Find the next <li> opening tag
1555
+ const liStart = content.indexOf('<li', currentPos);
1556
+ if (liStart === -1)
1557
+ break;
1558
+ // Find the end of the opening tag
1559
+ const openTagEnd = content.indexOf('>', liStart);
1560
+ if (openTagEnd === -1)
1561
+ break;
1562
+ // Now find the matching closing </li> tag accounting for nesting
1563
+ let depth = 1;
1564
+ let pos = openTagEnd + 1;
1565
+ let itemEnd = -1;
1566
+ while (pos < content.length && depth > 0) {
1567
+ const nextLiOpen = content.indexOf('<li', pos);
1568
+ const nextLiClose = content.indexOf('</li>', pos);
1569
+ // If no more closing tags, we're done
1570
+ if (nextLiClose === -1)
1571
+ break;
1572
+ // If there's an opening tag before the next closing tag, increase depth
1573
+ if (nextLiOpen !== -1 && nextLiOpen < nextLiClose) {
1574
+ depth++;
1575
+ pos = nextLiOpen + 3; // Move past '<li'
1576
+ }
1577
+ else {
1578
+ // Found a closing tag
1579
+ depth--;
1580
+ if (depth === 0) {
1581
+ itemEnd = nextLiClose + 5; // Include the '</li>'
1582
+ break;
1583
+ }
1584
+ else {
1585
+ pos = nextLiClose + 5; // Move past '</li>'
1586
+ }
1587
+ }
1588
+ }
1589
+ if (itemEnd !== -1) {
1590
+ // Extract the content between <li...> and </li>
1591
+ const fullMatch = content.substring(liStart, itemEnd);
1592
+ const innerMatch = fullMatch.match(/<li[^>]*>([\s\S]*)<\/li>$/);
1593
+ if (innerMatch) {
1594
+ items.push(innerMatch[1]);
1595
+ }
1596
+ currentPos = itemEnd;
1597
+ }
1598
+ else {
1599
+ // Malformed HTML, skip this tag
1600
+ currentPos = openTagEnd + 1;
1601
+ }
1602
+ }
1603
+ return items;
1604
+ }
1575
1605
  // Helper function to get callout emoji based on type
1576
1606
  static getCalloutEmoji(type) {
1577
1607
  const emojiMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-notion-advanced",
3
- "version": "1.2.26-beta",
3
+ "version": "1.2.28-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": [