n8n-nodes-notion-advanced 1.2.31-beta → 1.2.33-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.
@@ -66,6 +66,10 @@ export declare class NotionAITool implements INodeType {
66
66
  static processNestedHtmlInListItem(content: string): string;
67
67
  static convertInlineHtmlToMarkdown(content: string): string;
68
68
  static buildListHierarchy(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', childHierarchyNodes: HierarchyNode[]): HierarchyNode | null;
69
+ static getListItemPositions(content: string): Array<{
70
+ start: number;
71
+ end: number;
72
+ }>;
69
73
  static mapChildBlocksToListItems(listContent: string, childBlocks: IDataObject[], childNodes: XMLNode[]): Map<number, IDataObject[]>;
70
74
  static processNestedListWithChildBlocks(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[], listItemChildBlocks: Map<number, IDataObject[]>): void;
71
75
  static processNestedList(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[], childNodes?: XMLNode[]): void;
@@ -872,11 +872,12 @@ class NotionAITool {
872
872
  }
873
873
  // For list processors, handle them specially
874
874
  if (xmlNode.listProcessor && (xmlNode.tagName === 'ul' || xmlNode.tagName === 'ol')) {
875
- // Extract inner content
875
+ // Extract inner content and calculate offset
876
876
  const tagName = xmlNode.tagName.toLowerCase();
877
877
  const openTagRegex = new RegExp(`^<${tagName}[^>]*>`, 'i');
878
878
  const closeTagRegex = new RegExp(`</${tagName}>$`, 'i');
879
879
  let innerContent = xmlNode.match;
880
+ let contentStartOffset = 0;
880
881
  const openMatch = xmlNode.match.match(openTagRegex);
881
882
  const closeMatch = xmlNode.match.match(closeTagRegex);
882
883
  if (openMatch && closeMatch) {
@@ -885,9 +886,23 @@ class NotionAITool {
885
886
  const startIndex = openTag.length;
886
887
  const endIndex = xmlNode.match.length - closeTag.length;
887
888
  innerContent = xmlNode.match.substring(startIndex, endIndex);
889
+ contentStartOffset = xmlNode.start + startIndex; // Absolute position where list content starts
888
890
  }
891
+ // Adjust child hierarchy node positions to be relative to list content
892
+ const adjustedChildNodes = childHierarchyNodes.map(child => {
893
+ var _a;
894
+ return ({
895
+ ...child,
896
+ metadata: {
897
+ ...child.metadata,
898
+ sourcePosition: ((_a = child.metadata) === null || _a === void 0 ? void 0 : _a.sourcePosition) !== undefined
899
+ ? child.metadata.sourcePosition - contentStartOffset
900
+ : undefined
901
+ }
902
+ });
903
+ });
889
904
  // Build hierarchy structure for the list
890
- const listHierarchy = NotionAITool.buildListHierarchy(innerContent, xmlNode.tagName === 'ul' ? 'bulleted_list_item' : 'numbered_list_item', childHierarchyNodes);
905
+ const listHierarchy = NotionAITool.buildListHierarchy(innerContent, xmlNode.tagName === 'ul' ? 'bulleted_list_item' : 'numbered_list_item', adjustedChildNodes);
891
906
  return listHierarchy;
892
907
  }
893
908
  // For regular nodes, create block and attach children
@@ -1472,21 +1487,27 @@ class NotionAITool {
1472
1487
  processed = processed.replace(/<\/?li\s*[^>]*>/gi, '');
1473
1488
  // Remove any other common list-related fragments
1474
1489
  processed = processed.replace(/<\/?[uo]l\s*[^>]*>/gi, '');
1475
- // Simple cleanup - just remove remaining HTML tags and clean whitespace
1476
- // Avoid convertInlineHtmlToMarkdown to prevent duplication issues
1490
+ // Convert inline HTML formatting to markdown BEFORE removing tags
1491
+ processed = NotionAITool.convertInlineHtmlToMarkdown(processed);
1492
+ // Final cleanup of any remaining unhandled tags
1477
1493
  const result = processed
1478
- .replace(/<[^>]*>/g, ' ') // Remove any remaining HTML tags
1479
1494
  .replace(/\s+/g, ' ') // Clean up whitespace
1480
1495
  .trim();
1481
1496
  return result;
1482
1497
  }
1483
1498
  catch (error) {
1484
1499
  console.warn('Error processing nested HTML in list item:', error);
1485
- // Fallback: aggressively remove all HTML tags and return clean text
1486
- return processed
1487
- .replace(/<[^>]*>/g, ' ')
1488
- .replace(/\s+/g, ' ')
1489
- .trim();
1500
+ // Fallback: convert inline HTML then remove any remaining tags
1501
+ try {
1502
+ const fallback = NotionAITool.convertInlineHtmlToMarkdown(processed);
1503
+ return fallback.replace(/\s+/g, ' ').trim();
1504
+ }
1505
+ catch (fallbackError) {
1506
+ return processed
1507
+ .replace(/<[^>]*>/g, ' ')
1508
+ .replace(/\s+/g, ' ')
1509
+ .trim();
1510
+ }
1490
1511
  }
1491
1512
  }
1492
1513
  // Helper function to convert inline HTML to markdown
@@ -1523,12 +1544,13 @@ class NotionAITool {
1523
1544
  }
1524
1545
  // Build hierarchy structure for lists using HierarchyNode approach
1525
1546
  static buildListHierarchy(listContent, listType, childHierarchyNodes) {
1547
+ var _a;
1526
1548
  try {
1527
1549
  // Process each <li> element and build hierarchy
1528
1550
  const listItems = NotionAITool.extractListItemsWithBranching(listContent);
1529
1551
  const listItemHierarchyNodes = [];
1530
- // Map child hierarchy nodes to list items based on position
1531
- let childNodeIndex = 0;
1552
+ // Map child hierarchy nodes to list items based on actual position in source XML
1553
+ const listItemPositions = NotionAITool.getListItemPositions(listContent);
1532
1554
  for (let i = 0; i < listItems.length; i++) {
1533
1555
  const item = listItems[i];
1534
1556
  if (!item.text && !item.children.length)
@@ -1547,16 +1569,19 @@ class NotionAITool {
1547
1569
  listItemBlock[listType].rich_text = NotionAITool.parseBasicMarkdown(cleanText);
1548
1570
  }
1549
1571
  }
1550
- // Collect child hierarchy nodes for this list item
1572
+ // Collect child hierarchy nodes for this list item based on position mapping
1551
1573
  const itemChildNodes = [];
1552
- // Add child hierarchy nodes that belong to this list item
1553
- // For now, distribute them evenly - this could be improved with position mapping
1554
- const childrenPerItem = Math.floor(childHierarchyNodes.length / listItems.length);
1555
- const startIndex = i * childrenPerItem;
1556
- const endIndex = i === listItems.length - 1 ? childHierarchyNodes.length : startIndex + childrenPerItem;
1557
- for (let j = startIndex; j < endIndex; j++) {
1558
- if (j < childHierarchyNodes.length) {
1559
- itemChildNodes.push(childHierarchyNodes[j]);
1574
+ // Map child hierarchy nodes that belong to this specific list item
1575
+ if (i < listItemPositions.length) {
1576
+ const currentItemStart = listItemPositions[i].start;
1577
+ const currentItemEnd = listItemPositions[i].end;
1578
+ for (const childNode of childHierarchyNodes) {
1579
+ const childPosition = (_a = childNode.metadata) === null || _a === void 0 ? void 0 : _a.sourcePosition;
1580
+ if (childPosition !== undefined &&
1581
+ childPosition >= currentItemStart &&
1582
+ childPosition < currentItemEnd) {
1583
+ itemChildNodes.push(childNode);
1584
+ }
1560
1585
  }
1561
1586
  }
1562
1587
  // Process nested list children (traditional nested lists)
@@ -1608,6 +1633,55 @@ class NotionAITool {
1608
1633
  };
1609
1634
  }
1610
1635
  }
1636
+ // Helper function to get position ranges for each list item in the content
1637
+ static getListItemPositions(content) {
1638
+ const positions = [];
1639
+ let pos = 0;
1640
+ while (pos < content.length) {
1641
+ // Find next <li> tag
1642
+ const liStart = content.indexOf('<li', pos);
1643
+ if (liStart === -1)
1644
+ break;
1645
+ const liOpenEnd = content.indexOf('>', liStart);
1646
+ if (liOpenEnd === -1)
1647
+ break;
1648
+ // Find the matching </li> using proper depth tracking
1649
+ let depth = 0;
1650
+ let searchPos = liOpenEnd + 1;
1651
+ let liEnd = -1;
1652
+ while (searchPos < content.length) {
1653
+ const nextLiOpen = content.indexOf('<li', searchPos);
1654
+ const nextLiClose = content.indexOf('</li>', searchPos);
1655
+ if (nextLiClose === -1)
1656
+ break;
1657
+ if (nextLiOpen !== -1 && nextLiOpen < nextLiClose) {
1658
+ depth++;
1659
+ searchPos = nextLiOpen + 3;
1660
+ }
1661
+ else {
1662
+ if (depth === 0) {
1663
+ liEnd = nextLiClose + 5; // Include the '</li>'
1664
+ break;
1665
+ }
1666
+ else {
1667
+ depth--;
1668
+ searchPos = nextLiClose + 5;
1669
+ }
1670
+ }
1671
+ }
1672
+ if (liEnd !== -1) {
1673
+ positions.push({
1674
+ start: liStart,
1675
+ end: liEnd
1676
+ });
1677
+ pos = liEnd;
1678
+ }
1679
+ else {
1680
+ pos = liOpenEnd + 1;
1681
+ }
1682
+ }
1683
+ return positions;
1684
+ }
1611
1685
  // Helper function to map child blocks to specific list items (legacy support)
1612
1686
  static mapChildBlocksToListItems(listContent, childBlocks, childNodes) {
1613
1687
  const listItemChildBlocks = new Map();
@@ -1756,13 +1830,30 @@ class NotionAITool {
1756
1830
  continue;
1757
1831
  }
1758
1832
  const item = { text: '', children: [] };
1759
- // Process the content to separate text from nested lists
1833
+ // STEP 1: Remove child block XML from content to get clean parent text
1834
+ let parentTextContent = fullItemContent;
1835
+ // List of XML tags that create child blocks (these should be removed from parent text)
1836
+ const childBlockTags = [
1837
+ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
1838
+ 'blockquote', 'quote', 'callout', 'code', 'pre',
1839
+ 'todo', 'toggle', 'image', 'embed', 'bookmark',
1840
+ 'equation', 'divider'
1841
+ ];
1842
+ // Remove child block XML from parent text
1843
+ childBlockTags.forEach(tag => {
1844
+ // Remove both self-closing and paired tags
1845
+ const selfClosingRegex = new RegExp(`<${tag}[^>]*\\/>`, 'gis');
1846
+ const pairedRegex = new RegExp(`<${tag}[^>]*>.*?<\\/${tag}>`, 'gis');
1847
+ parentTextContent = parentTextContent.replace(pairedRegex, '');
1848
+ parentTextContent = parentTextContent.replace(selfClosingRegex, '');
1849
+ });
1850
+ // STEP 2: Process the content to separate remaining text from nested lists
1760
1851
  let contentPos = 0;
1761
1852
  let textParts = [];
1762
- while (contentPos < fullItemContent.length) {
1853
+ while (contentPos < parentTextContent.length) {
1763
1854
  // Look for the next nested list (ul or ol)
1764
- const nextUlStart = fullItemContent.indexOf('<ul', contentPos);
1765
- const nextOlStart = fullItemContent.indexOf('<ol', contentPos);
1855
+ const nextUlStart = parentTextContent.indexOf('<ul', contentPos);
1856
+ const nextOlStart = parentTextContent.indexOf('<ol', contentPos);
1766
1857
  let nextListStart = -1;
1767
1858
  let listType = '';
1768
1859
  if (nextUlStart !== -1 && (nextOlStart === -1 || nextUlStart < nextOlStart)) {
@@ -1775,22 +1866,22 @@ class NotionAITool {
1775
1866
  }
1776
1867
  if (nextListStart === -1) {
1777
1868
  // No more nested lists - add remaining text
1778
- const remainingText = fullItemContent.substring(contentPos);
1869
+ const remainingText = parentTextContent.substring(contentPos);
1779
1870
  if (remainingText.trim()) {
1780
1871
  textParts.push(remainingText);
1781
1872
  }
1782
1873
  break;
1783
1874
  }
1784
1875
  // Add text before the nested list
1785
- const textBefore = fullItemContent.substring(contentPos, nextListStart);
1876
+ const textBefore = parentTextContent.substring(contentPos, nextListStart);
1786
1877
  if (textBefore.trim()) {
1787
1878
  textParts.push(textBefore);
1788
1879
  }
1789
1880
  // Find the end of this nested list
1790
- const listOpenEnd = fullItemContent.indexOf('>', nextListStart);
1881
+ const listOpenEnd = parentTextContent.indexOf('>', nextListStart);
1791
1882
  if (listOpenEnd === -1) {
1792
1883
  // Malformed list tag
1793
- textParts.push(fullItemContent.substring(contentPos));
1884
+ textParts.push(parentTextContent.substring(contentPos));
1794
1885
  break;
1795
1886
  }
1796
1887
  // Track depth to find the matching closing tag
@@ -1799,9 +1890,9 @@ class NotionAITool {
1799
1890
  let listEnd = -1;
1800
1891
  const openTag = `<${listType}`;
1801
1892
  const closeTag = `</${listType}>`;
1802
- while (listSearchPos < fullItemContent.length && listDepth > 0) {
1803
- const nextListOpen = fullItemContent.indexOf(openTag, listSearchPos);
1804
- const nextListClose = fullItemContent.indexOf(closeTag, listSearchPos);
1893
+ while (listSearchPos < parentTextContent.length && listDepth > 0) {
1894
+ const nextListOpen = parentTextContent.indexOf(openTag, listSearchPos);
1895
+ const nextListClose = parentTextContent.indexOf(closeTag, listSearchPos);
1805
1896
  if (nextListClose === -1)
1806
1897
  break;
1807
1898
  if (nextListOpen !== -1 && nextListOpen < nextListClose) {
@@ -1818,7 +1909,8 @@ class NotionAITool {
1818
1909
  }
1819
1910
  }
1820
1911
  if (listEnd !== -1) {
1821
- // Extract the content between <ul>/<ol> and </ul>/<ol>
1912
+ // Extract the content between <ul>/<ol> and </ul>/<ol> from ORIGINAL content
1913
+ // (not parentTextContent which has child blocks removed)
1822
1914
  const listContent = fullItemContent.substring(listOpenEnd + 1, listEnd - closeTag.length);
1823
1915
  item.children.push({
1824
1916
  type: listType,
@@ -1828,7 +1920,7 @@ class NotionAITool {
1828
1920
  }
1829
1921
  else {
1830
1922
  // Malformed nested list - treat remaining as text
1831
- textParts.push(fullItemContent.substring(contentPos));
1923
+ textParts.push(parentTextContent.substring(contentPos));
1832
1924
  break;
1833
1925
  }
1834
1926
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-notion-advanced",
3
- "version": "1.2.31-beta",
3
+ "version": "1.2.33-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": [