n8n-nodes-notion-advanced 1.2.27-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,11 +903,6 @@ 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 ''; // Remove completely - no placeholder needed
883
- }
884
906
  // Use blockCreator to create the block
885
907
  const block = node.processor(...node.groups);
886
908
  if (block) {
@@ -1399,61 +1421,28 @@ class NotionAITool {
1399
1421
  if (!processed)
1400
1422
  return '';
1401
1423
  try {
1402
- // Handle multiple segments separated by HTML block elements
1403
- const segments = [];
1404
- // Split by block-level HTML elements like <p>, <div>, etc.
1405
- const blockElements = /<(p|div|h[1-6]|blockquote)\s*[^>]*>.*?<\/\1>/gis;
1406
- let lastIndex = 0;
1407
- let match;
1408
- const blockMatches = [];
1409
- while ((match = blockElements.exec(processed)) !== null) {
1410
- blockMatches.push({
1411
- start: match.index,
1412
- end: match.index + match[0].length,
1413
- content: match[0],
1414
- tag: match[1]
1415
- });
1416
- }
1417
- // Sort matches by position
1418
- blockMatches.sort((a, b) => a.start - b.start);
1419
- // Process text segments between block elements
1420
- blockMatches.forEach((blockMatch, index) => {
1421
- // Add text before this block element
1422
- if (blockMatch.start > lastIndex) {
1423
- const beforeText = processed.substring(lastIndex, blockMatch.start).trim();
1424
- if (beforeText) {
1425
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(beforeText));
1426
- }
1427
- }
1428
- // Process content inside block element
1429
- const innerContent = blockMatch.content.replace(new RegExp(`^<${blockMatch.tag}[^>]*>`, 'i'), '')
1430
- .replace(new RegExp(`</${blockMatch.tag}>$`, 'i'), '')
1431
- .trim();
1432
- if (innerContent) {
1433
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(innerContent));
1434
- }
1435
- lastIndex = blockMatch.end;
1436
- });
1437
- // Add remaining text after last block element
1438
- if (lastIndex < processed.length) {
1439
- const remainingText = processed.substring(lastIndex).trim();
1440
- if (remainingText) {
1441
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(remainingText));
1442
- }
1443
- }
1444
- // If no block elements were found, process the whole content
1445
- if (blockMatches.length === 0) {
1446
- segments.push(NotionAITool.convertInlineHtmlToMarkdown(processed));
1447
- }
1448
- // Join segments with space and clean up
1449
- const result = segments.filter(s => s.trim()).join(' ').trim();
1450
- // Final cleanup of any remaining artifacts
1451
- 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;
1452
1438
  }
1453
1439
  catch (error) {
1454
1440
  console.warn('Error processing nested HTML in list item:', error);
1455
- // Fallback: just remove HTML tags and return text
1456
- 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();
1457
1446
  }
1458
1447
  }
1459
1448
  // Helper function to convert inline HTML to markdown
@@ -1491,49 +1480,36 @@ class NotionAITool {
1491
1480
  // Helper function to process nested lists and flatten them for Notion
1492
1481
  static processNestedList(listContent, listType, blocks) {
1493
1482
  try {
1494
- // More robust list item extraction using regex
1495
- const liRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi;
1496
- let match;
1497
- while ((match = liRegex.exec(listContent)) !== null) {
1498
- let itemContent = match[1].trim();
1499
- 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())
1500
1487
  continue;
1501
1488
  // Check if this item contains nested lists
1502
1489
  const hasNestedList = /<[uo]l\s*[^>]*>/i.test(itemContent);
1503
1490
  if (hasNestedList) {
1504
- // Split content into parts: before nested list, nested list, after nested list
1505
- const parts = itemContent.split(/(<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>)/i);
1506
- // Process the main content (before nested list)
1507
- const mainContent = parts[0] ? parts[0].trim() : '';
1508
- if (mainContent) {
1509
- const cleanContent = NotionAITool.processNestedHtmlInListItem(mainContent);
1510
- if (cleanContent) {
1511
- blocks.push({
1512
- type: listType,
1513
- [listType]: {
1514
- rich_text: NotionAITool.parseBasicMarkdown(cleanContent),
1515
- },
1516
- });
1517
- }
1518
- }
1519
- // Process nested lists
1520
- for (let i = 1; i < parts.length; i += 2) {
1521
- const nestedListHtml = parts[i];
1522
- if (nestedListHtml) {
1523
- 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);
1524
1502
  if (nestedListMatch) {
1525
- const [, nestedListTag, nestedContent] = nestedListMatch;
1526
- 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';
1527
1505
  // Recursively process nested list
1528
- NotionAITool.processNestedList(nestedContent, nestedListType, blocks);
1506
+ NotionAITool.processNestedList(innerContent, nestedListType, blocks);
1529
1507
  }
1530
1508
  }
1531
- }
1532
- // Process any content after nested lists
1533
- if (parts.length > 2) {
1534
- const afterContent = parts.slice(2).join('').trim();
1535
- if (afterContent) {
1536
- 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);
1537
1513
  if (cleanContent) {
1538
1514
  blocks.push({
1539
1515
  type: listType,
@@ -1570,6 +1546,62 @@ class NotionAITool {
1570
1546
  });
1571
1547
  }
1572
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
+ }
1573
1605
  // Helper function to get callout emoji based on type
1574
1606
  static getCalloutEmoji(type) {
1575
1607
  const emojiMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-notion-advanced",
3
- "version": "1.2.27-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": [