n8n-nodes-notion-advanced 1.2.30-beta → 1.2.32-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.
@@ -22,6 +22,15 @@ interface XMLNode {
22
22
  replacement?: string;
23
23
  listProcessor?: (content: string, blocks: IDataObject[]) => void;
24
24
  }
25
+ interface HierarchyNode {
26
+ block: IDataObject;
27
+ children: HierarchyNode[];
28
+ metadata?: {
29
+ sourcePosition?: number;
30
+ xmlNodeId?: string;
31
+ tagName?: string;
32
+ };
33
+ }
25
34
  export declare class NotionAITool implements INodeType {
26
35
  description: INodeTypeDescription;
27
36
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
@@ -43,6 +52,8 @@ export declare class NotionAITool implements INodeType {
43
52
  }[]): string;
44
53
  static getUtf8BytePosition(str: string, charIndex: number): number;
45
54
  static buildXMLTree(content: string, tagProcessors: any[]): XMLNode[];
55
+ static xmlTreeToHierarchy(nodes: XMLNode[]): HierarchyNode[];
56
+ static hierarchyToNotionBlocks(hierarchy: HierarchyNode[]): IDataObject[];
46
57
  static processXMLTreeDepthFirst(nodes: XMLNode[], blocks: IDataObject[], placeholderCounter: {
47
58
  value: number;
48
59
  }): Map<string, string>;
@@ -54,7 +65,14 @@ export declare class NotionAITool implements INodeType {
54
65
  static cleanupRemainingHtml(content: string, placeholderPrefix?: string): string;
55
66
  static processNestedHtmlInListItem(content: string): string;
56
67
  static convertInlineHtmlToMarkdown(content: string): string;
57
- static processNestedList(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[]): void;
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
+ }>;
73
+ static mapChildBlocksToListItems(listContent: string, childBlocks: IDataObject[], childNodes: XMLNode[]): Map<number, IDataObject[]>;
74
+ static processNestedListWithChildBlocks(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[], listItemChildBlocks: Map<number, IDataObject[]>): void;
75
+ static processNestedList(listContent: string, listType: 'bulleted_list_item' | 'numbered_list_item', blocks: IDataObject[], childNodes?: XMLNode[]): void;
58
76
  static extractListItemsWithBranching(content: string): Array<{
59
77
  text: string;
60
78
  children: Array<{
@@ -749,67 +749,55 @@ class NotionAITool {
749
749
  static buildXMLTree(content, tagProcessors) {
750
750
  var _a;
751
751
  const allMatches = [];
752
- // Step 1: Use depth-aware parsing for each tag processor
752
+ // Step 1: Use regex-based parsing to properly extract capture groups, then enhance with depth-aware structure
753
753
  tagProcessors.forEach(({ regex, blockCreator, listProcessor }) => {
754
754
  var _a;
755
- const tagPattern = (_a = regex.source.match(/<(\w+)/)) === null || _a === void 0 ? void 0 : _a[1];
756
- if (!tagPattern)
757
- return;
758
- // Find all opening tags of this type
759
- let pos = 0;
760
- while (pos < content.length) {
761
- const openTagStart = content.indexOf(`<${tagPattern}`, pos);
762
- if (openTagStart === -1)
763
- break;
764
- const openTagEnd = content.indexOf('>', openTagStart);
765
- if (openTagEnd === -1)
766
- break;
767
- // Find matching closing tag using depth tracking
768
- let depth = 1;
769
- let searchPos = openTagEnd + 1;
770
- let closeTagStart = -1;
771
- const openPattern = `<${tagPattern}`;
772
- const closePattern = `</${tagPattern}>`;
773
- while (searchPos < content.length && depth > 0) {
774
- const nextOpen = content.indexOf(openPattern, searchPos);
775
- const nextClose = content.indexOf(closePattern, searchPos);
776
- if (nextClose === -1)
777
- break;
778
- if (nextOpen !== -1 && nextOpen < nextClose) {
779
- // Found nested opening tag
780
- depth++;
781
- searchPos = nextOpen + openPattern.length;
755
+ const globalRegex = new RegExp(regex.source, 'gis');
756
+ let match;
757
+ while ((match = globalRegex.exec(content)) !== null) {
758
+ const fullMatch = match[0];
759
+ const matchStart = match.index;
760
+ const matchEnd = match.index + fullMatch.length;
761
+ // Extract tag name for identification
762
+ const tagPattern = ((_a = regex.source.match(/<(\w+)/)) === null || _a === void 0 ? void 0 : _a[1]) || 'unknown';
763
+ // Extract inner content (between opening and closing tags)
764
+ let innerContent = '';
765
+ try {
766
+ const openTagRegex = new RegExp(`^<${tagPattern}[^>]*>`, 'i');
767
+ const closeTagRegex = new RegExp(`</${tagPattern}>$`, 'i');
768
+ const openMatch = fullMatch.match(openTagRegex);
769
+ const closeMatch = fullMatch.match(closeTagRegex);
770
+ if (openMatch && closeMatch) {
771
+ const openTag = openMatch[0];
772
+ const closeTag = closeMatch[0];
773
+ const startIndex = openTag.length;
774
+ const endIndex = fullMatch.length - closeTag.length;
775
+ innerContent = fullMatch.substring(startIndex, endIndex);
782
776
  }
783
777
  else {
784
- // Found closing tag
785
- depth--;
786
- if (depth === 0) {
787
- closeTagStart = nextClose;
788
- break;
789
- }
790
- searchPos = nextClose + closePattern.length;
778
+ // Fallback for self-closing or malformed tags
779
+ innerContent = fullMatch.replace(/^<[^>]*>/, '').replace(/<\/[^>]*>$/, '');
791
780
  }
792
781
  }
793
- if (closeTagStart !== -1) {
794
- const fullMatch = content.substring(openTagStart, closeTagStart + closePattern.length);
795
- const innerContent = content.substring(openTagEnd + 1, closeTagStart);
796
- const xmlNode = {
797
- id: `${tagPattern}_${openTagStart}_${Date.now()}_${Math.random()}`,
798
- tagName: tagPattern,
799
- start: openTagStart,
800
- end: closeTagStart + closePattern.length,
801
- match: fullMatch,
802
- processor: blockCreator,
803
- groups: [innerContent], // For list processors, group[0] is the inner content
804
- children: [],
805
- depth: 0,
806
- innerContent,
807
- replacement: undefined,
808
- listProcessor
809
- };
810
- allMatches.push(xmlNode);
782
+ catch (error) {
783
+ console.warn(`Error extracting inner content for ${tagPattern}:`, error);
784
+ innerContent = fullMatch;
811
785
  }
812
- pos = openTagEnd + 1;
786
+ const xmlNode = {
787
+ id: `${tagPattern}_${matchStart}_${Date.now()}_${Math.random()}`,
788
+ tagName: tagPattern,
789
+ start: matchStart,
790
+ end: matchEnd,
791
+ match: fullMatch,
792
+ processor: blockCreator,
793
+ groups: match.slice(1), // Proper regex capture groups (excluding full match)
794
+ children: [],
795
+ depth: 0,
796
+ innerContent,
797
+ replacement: undefined,
798
+ listProcessor
799
+ };
800
+ allMatches.push(xmlNode);
813
801
  }
814
802
  });
815
803
  // Step 2: Catch ANY remaining XML/HTML tags that weren't processed by specific processors
@@ -869,92 +857,141 @@ class NotionAITool {
869
857
  }
870
858
  return rootNodes;
871
859
  }
872
- // Process XML tree depth-first (children before parents)
873
- static processXMLTreeDepthFirst(nodes, blocks, placeholderCounter) {
874
- const replacements = new Map();
875
- const processNode = (node) => {
876
- // For lists, use the original match content to preserve structure
877
- // This is the only case where we skip child processing to avoid fragmentation
878
- if (node.listProcessor && (node.tagName === 'ul' || node.tagName === 'ol')) {
879
- try {
880
- // Extract inner content directly from the original match
881
- const tagName = node.tagName.toLowerCase();
860
+ // Convert XML tree to HierarchyNode structure for cleaner processing
861
+ static xmlTreeToHierarchy(nodes) {
862
+ const hierarchyNodes = [];
863
+ const processNode = (xmlNode) => {
864
+ try {
865
+ // Process children first
866
+ const childHierarchyNodes = [];
867
+ for (const child of xmlNode.children) {
868
+ const childHierarchy = processNode(child);
869
+ if (childHierarchy) {
870
+ childHierarchyNodes.push(childHierarchy);
871
+ }
872
+ }
873
+ // For list processors, handle them specially
874
+ if (xmlNode.listProcessor && (xmlNode.tagName === 'ul' || xmlNode.tagName === 'ol')) {
875
+ // Extract inner content and calculate offset
876
+ const tagName = xmlNode.tagName.toLowerCase();
882
877
  const openTagRegex = new RegExp(`^<${tagName}[^>]*>`, 'i');
883
878
  const closeTagRegex = new RegExp(`</${tagName}>$`, 'i');
884
- let innerContent = node.match;
885
- const openMatch = node.match.match(openTagRegex);
886
- const closeMatch = node.match.match(closeTagRegex);
879
+ let innerContent = xmlNode.match;
880
+ let contentStartOffset = 0;
881
+ const openMatch = xmlNode.match.match(openTagRegex);
882
+ const closeMatch = xmlNode.match.match(closeTagRegex);
887
883
  if (openMatch && closeMatch) {
888
884
  const openTag = openMatch[0];
889
885
  const closeTag = closeMatch[0];
890
886
  const startIndex = openTag.length;
891
- const endIndex = node.match.length - closeTag.length;
892
- innerContent = node.match.substring(startIndex, endIndex);
887
+ const endIndex = xmlNode.match.length - closeTag.length;
888
+ innerContent = xmlNode.match.substring(startIndex, endIndex);
889
+ contentStartOffset = xmlNode.start + startIndex; // Absolute position where list content starts
893
890
  }
894
- // Process the list with complete content
895
- node.listProcessor(innerContent, blocks);
896
- return ''; // Remove completely - no placeholder needed
897
- }
898
- catch (error) {
899
- console.warn(`Error processing list node ${node.tagName}:`, error);
900
- return ''; // Remove even on error to prevent artifacts
901
- }
902
- }
903
- // For non-list nodes, process children first (normal hierarchical processing)
904
- for (const child of node.children) {
905
- const childReplacement = processNode(child);
906
- replacements.set(child.id, childReplacement);
907
- }
908
- // Extract inner content (content between opening and closing tags)
909
- let innerContent = '';
910
- try {
911
- // More robust inner content extraction
912
- const tagName = node.tagName.toLowerCase();
913
- const openTagRegex = new RegExp(`^<${tagName}[^>]*>`, 'i');
914
- const closeTagRegex = new RegExp(`</${tagName}>$`, 'i');
915
- const openMatch = node.match.match(openTagRegex);
916
- const closeMatch = node.match.match(closeTagRegex);
917
- if (openMatch && closeMatch) {
918
- const openTag = openMatch[0];
919
- const closeTag = closeMatch[0];
920
- const startIndex = openTag.length;
921
- const endIndex = node.match.length - closeTag.length;
922
- innerContent = node.match.substring(startIndex, endIndex);
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
+ });
904
+ // Build hierarchy structure for the list
905
+ const listHierarchy = NotionAITool.buildListHierarchy(innerContent, xmlNode.tagName === 'ul' ? 'bulleted_list_item' : 'numbered_list_item', adjustedChildNodes);
906
+ return listHierarchy;
923
907
  }
924
- else {
925
- // Fallback for self-closing or malformed tags
926
- innerContent = node.match.replace(/^<[^>]*>/, '').replace(/<\/[^>]*>$/, '');
927
- }
928
- // Replace child nodes in inner content with their processed content
929
- for (const child of node.children) {
930
- const childReplacement = replacements.get(child.id) || '';
931
- if (childReplacement !== undefined && innerContent.includes(child.match)) {
932
- innerContent = innerContent.replace(child.match, childReplacement);
908
+ // For regular nodes, create block and attach children
909
+ const block = xmlNode.processor(...xmlNode.groups);
910
+ if (!block)
911
+ return null;
912
+ const hierarchyNode = {
913
+ block,
914
+ children: childHierarchyNodes,
915
+ metadata: {
916
+ sourcePosition: xmlNode.start,
917
+ xmlNodeId: xmlNode.id,
918
+ tagName: xmlNode.tagName
933
919
  }
934
- }
935
- }
936
- catch (error) {
937
- console.warn(`Error extracting inner content for ${node.tagName}:`, error);
938
- innerContent = node.match;
939
- }
940
- // Process this node with updated inner content
941
- try {
942
- // Use blockCreator to create the block
943
- const block = node.processor(...node.groups);
944
- if (block) {
945
- blocks.push(block);
946
- }
947
- return ''; // Remove completely - no placeholder needed
920
+ };
921
+ return hierarchyNode;
948
922
  }
949
923
  catch (error) {
950
- console.warn(`Error processing XML node ${node.tagName}:`, error);
951
- return ''; // Remove even on error to prevent artifacts
924
+ console.warn(`Error processing XML node ${xmlNode.tagName}:`, error);
925
+ return null;
952
926
  }
953
927
  };
954
928
  // Process all root nodes
955
929
  for (const rootNode of nodes) {
956
- const replacement = processNode(rootNode);
957
- replacements.set(rootNode.id, replacement);
930
+ const hierarchyNode = processNode(rootNode);
931
+ if (hierarchyNode) {
932
+ if (hierarchyNode.block) {
933
+ hierarchyNodes.push(hierarchyNode);
934
+ }
935
+ else if (hierarchyNode.children.length > 0) {
936
+ // If it's a list container, add its children directly
937
+ hierarchyNodes.push(...hierarchyNode.children);
938
+ }
939
+ }
940
+ }
941
+ return hierarchyNodes;
942
+ }
943
+ // Convert HierarchyNode structure to final Notion blocks
944
+ static hierarchyToNotionBlocks(hierarchy) {
945
+ return hierarchy.map(node => {
946
+ const block = { ...node.block };
947
+ if (node.children.length > 0) {
948
+ const blockData = block[block.type];
949
+ if (blockData && typeof blockData === 'object') {
950
+ // Check if this block type can have children
951
+ const childSupportingTypes = ['bulleted_list_item', 'numbered_list_item', 'toggle', 'quote', 'callout'];
952
+ if (childSupportingTypes.includes(block.type)) {
953
+ blockData.children = NotionAITool.hierarchyToNotionBlocks(node.children);
954
+ }
955
+ }
956
+ }
957
+ return block;
958
+ });
959
+ }
960
+ // Process XML tree using the new hierarchy system
961
+ static processXMLTreeDepthFirst(nodes, blocks, placeholderCounter) {
962
+ const replacements = new Map();
963
+ try {
964
+ // Convert XML tree to hierarchy structure
965
+ const hierarchy = NotionAITool.xmlTreeToHierarchy(nodes);
966
+ // Convert hierarchy to final Notion blocks
967
+ const finalBlocks = NotionAITool.hierarchyToNotionBlocks(hierarchy);
968
+ // Add all blocks to the output
969
+ blocks.push(...finalBlocks);
970
+ // Mark all nodes as processed (empty replacement)
971
+ const markProcessed = (nodeList) => {
972
+ nodeList.forEach(node => {
973
+ replacements.set(node.id, '');
974
+ markProcessed(node.children);
975
+ });
976
+ };
977
+ markProcessed(nodes);
978
+ }
979
+ catch (error) {
980
+ console.warn('Error in hierarchy processing, falling back to legacy processing:', error);
981
+ // Fallback to simple processing if hierarchy fails
982
+ nodes.forEach(node => {
983
+ try {
984
+ const block = node.processor(...node.groups);
985
+ if (block) {
986
+ blocks.push(block);
987
+ }
988
+ replacements.set(node.id, '');
989
+ }
990
+ catch (nodeError) {
991
+ console.warn(`Error processing fallback node ${node.tagName}:`, nodeError);
992
+ replacements.set(node.id, '');
993
+ }
994
+ });
958
995
  }
959
996
  return replacements;
960
997
  }
@@ -1499,13 +1536,17 @@ class NotionAITool {
1499
1536
  processed = processed.replace(/\s+/g, ' ').trim();
1500
1537
  return processed;
1501
1538
  }
1502
- // Helper function to process lists using branch-based approach
1503
- // Each <ul> and <ol> represents a new branch that contains children
1504
- static processNestedList(listContent, listType, blocks) {
1539
+ // Build hierarchy structure for lists using HierarchyNode approach
1540
+ static buildListHierarchy(listContent, listType, childHierarchyNodes) {
1541
+ var _a;
1505
1542
  try {
1506
- // Process each <li> element as a potential branch point
1543
+ // Process each <li> element and build hierarchy
1507
1544
  const listItems = NotionAITool.extractListItemsWithBranching(listContent);
1508
- for (const item of listItems) {
1545
+ const listItemHierarchyNodes = [];
1546
+ // Map child hierarchy nodes to list items based on actual position in source XML
1547
+ const listItemPositions = NotionAITool.getListItemPositions(listContent);
1548
+ for (let i = 0; i < listItems.length; i++) {
1549
+ const item = listItems[i];
1509
1550
  if (!item.text && !item.children.length)
1510
1551
  continue;
1511
1552
  // Create list item block
@@ -1522,17 +1563,191 @@ class NotionAITool {
1522
1563
  listItemBlock[listType].rich_text = NotionAITool.parseBasicMarkdown(cleanText);
1523
1564
  }
1524
1565
  }
1525
- // Process child branches and add them as nested children
1566
+ // Collect child hierarchy nodes for this list item based on position mapping
1567
+ const itemChildNodes = [];
1568
+ // Map child hierarchy nodes that belong to this specific list item
1569
+ if (i < listItemPositions.length) {
1570
+ const currentItemStart = listItemPositions[i].start;
1571
+ const currentItemEnd = listItemPositions[i].end;
1572
+ for (const childNode of childHierarchyNodes) {
1573
+ const childPosition = (_a = childNode.metadata) === null || _a === void 0 ? void 0 : _a.sourcePosition;
1574
+ if (childPosition !== undefined &&
1575
+ childPosition >= currentItemStart &&
1576
+ childPosition < currentItemEnd) {
1577
+ itemChildNodes.push(childNode);
1578
+ }
1579
+ }
1580
+ }
1581
+ // Process nested list children (traditional nested lists)
1526
1582
  if (item.children.length > 0) {
1527
- const childBlocks = [];
1528
1583
  for (const child of item.children) {
1529
1584
  const childListType = child.type === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
1530
- NotionAITool.processNestedList(child.content, childListType, childBlocks);
1585
+ const childListHierarchy = NotionAITool.buildListHierarchy(child.content, childListType, []);
1586
+ if (childListHierarchy && childListHierarchy.children) {
1587
+ itemChildNodes.push(...childListHierarchy.children);
1588
+ }
1531
1589
  }
1532
- // Add children to the parent block
1533
- if (childBlocks.length > 0) {
1534
- listItemBlock[listType].children = childBlocks;
1590
+ }
1591
+ // Create hierarchy node for this list item
1592
+ const listItemHierarchyNode = {
1593
+ block: listItemBlock,
1594
+ children: itemChildNodes,
1595
+ metadata: {
1596
+ sourcePosition: i,
1597
+ tagName: listType
1535
1598
  }
1599
+ };
1600
+ // Only add if it has content or children
1601
+ const listData = listItemBlock[listType];
1602
+ if ((listData.rich_text && listData.rich_text.length > 0) || itemChildNodes.length > 0) {
1603
+ listItemHierarchyNodes.push(listItemHierarchyNode);
1604
+ }
1605
+ }
1606
+ // Return a container that holds all list items
1607
+ return {
1608
+ block: null, // No container block needed
1609
+ children: listItemHierarchyNodes,
1610
+ metadata: {
1611
+ tagName: listType === 'bulleted_list_item' ? 'ul' : 'ol'
1612
+ }
1613
+ };
1614
+ }
1615
+ catch (error) {
1616
+ console.warn('Error building list hierarchy:', error);
1617
+ // Fallback: create a simple text block
1618
+ return {
1619
+ block: {
1620
+ type: 'paragraph',
1621
+ paragraph: {
1622
+ rich_text: [{ type: 'text', text: { content: 'Error processing list content' } }],
1623
+ },
1624
+ },
1625
+ children: [],
1626
+ metadata: { tagName: 'paragraph' }
1627
+ };
1628
+ }
1629
+ }
1630
+ // Helper function to get position ranges for each list item in the content
1631
+ static getListItemPositions(content) {
1632
+ const positions = [];
1633
+ let pos = 0;
1634
+ while (pos < content.length) {
1635
+ // Find next <li> tag
1636
+ const liStart = content.indexOf('<li', pos);
1637
+ if (liStart === -1)
1638
+ break;
1639
+ const liOpenEnd = content.indexOf('>', liStart);
1640
+ if (liOpenEnd === -1)
1641
+ break;
1642
+ // Find the matching </li> using proper depth tracking
1643
+ let depth = 0;
1644
+ let searchPos = liOpenEnd + 1;
1645
+ let liEnd = -1;
1646
+ while (searchPos < content.length) {
1647
+ const nextLiOpen = content.indexOf('<li', searchPos);
1648
+ const nextLiClose = content.indexOf('</li>', searchPos);
1649
+ if (nextLiClose === -1)
1650
+ break;
1651
+ if (nextLiOpen !== -1 && nextLiOpen < nextLiClose) {
1652
+ depth++;
1653
+ searchPos = nextLiOpen + 3;
1654
+ }
1655
+ else {
1656
+ if (depth === 0) {
1657
+ liEnd = nextLiClose + 5; // Include the '</li>'
1658
+ break;
1659
+ }
1660
+ else {
1661
+ depth--;
1662
+ searchPos = nextLiClose + 5;
1663
+ }
1664
+ }
1665
+ }
1666
+ if (liEnd !== -1) {
1667
+ positions.push({
1668
+ start: liStart,
1669
+ end: liEnd
1670
+ });
1671
+ pos = liEnd;
1672
+ }
1673
+ else {
1674
+ pos = liOpenEnd + 1;
1675
+ }
1676
+ }
1677
+ return positions;
1678
+ }
1679
+ // Helper function to map child blocks to specific list items (legacy support)
1680
+ static mapChildBlocksToListItems(listContent, childBlocks, childNodes) {
1681
+ const listItemChildBlocks = new Map();
1682
+ // Find all <li> positions in the content
1683
+ const liPositions = [];
1684
+ let pos = 0;
1685
+ while (pos < listContent.length) {
1686
+ const liStart = listContent.indexOf('<li', pos);
1687
+ if (liStart === -1)
1688
+ break;
1689
+ liPositions.push(liStart);
1690
+ pos = liStart + 3;
1691
+ }
1692
+ // Map child nodes to their corresponding list items
1693
+ childNodes.forEach((childNode, index) => {
1694
+ // Find which <li> this child node belongs to
1695
+ let liIndex = -1;
1696
+ for (let i = 0; i < liPositions.length; i++) {
1697
+ const liStart = liPositions[i];
1698
+ const nextLiStart = i + 1 < liPositions.length ? liPositions[i + 1] : listContent.length;
1699
+ if (childNode.start >= liStart && childNode.start < nextLiStart) {
1700
+ liIndex = i;
1701
+ break;
1702
+ }
1703
+ }
1704
+ if (liIndex >= 0 && index < childBlocks.length) {
1705
+ if (!listItemChildBlocks.has(liIndex)) {
1706
+ listItemChildBlocks.set(liIndex, []);
1707
+ }
1708
+ listItemChildBlocks.get(liIndex).push(childBlocks[index]);
1709
+ }
1710
+ });
1711
+ return listItemChildBlocks;
1712
+ }
1713
+ // Enhanced list processor that handles child blocks (legacy support)
1714
+ static processNestedListWithChildBlocks(listContent, listType, blocks, listItemChildBlocks) {
1715
+ try {
1716
+ // Process each <li> element as a potential branch point
1717
+ const listItems = NotionAITool.extractListItemsWithBranching(listContent);
1718
+ for (let i = 0; i < listItems.length; i++) {
1719
+ const item = listItems[i];
1720
+ if (!item.text && !item.children.length)
1721
+ continue;
1722
+ // Create list item block
1723
+ const listItemBlock = {
1724
+ type: listType,
1725
+ [listType]: {
1726
+ rich_text: [],
1727
+ },
1728
+ };
1729
+ // Add parent text if present
1730
+ if (item.text && item.text.trim()) {
1731
+ const cleanText = NotionAITool.processNestedHtmlInListItem(item.text);
1732
+ if (cleanText) {
1733
+ listItemBlock[listType].rich_text = NotionAITool.parseBasicMarkdown(cleanText);
1734
+ }
1735
+ }
1736
+ // Collect all child blocks
1737
+ const allChildBlocks = [];
1738
+ // Add child blocks from hierarchical processing
1739
+ const childBlocks = listItemChildBlocks.get(i) || [];
1740
+ allChildBlocks.push(...childBlocks);
1741
+ // Process nested list children (traditional nested lists)
1742
+ if (item.children.length > 0) {
1743
+ for (const child of item.children) {
1744
+ const childListType = child.type === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
1745
+ NotionAITool.processNestedList(child.content, childListType, allChildBlocks);
1746
+ }
1747
+ }
1748
+ // Add children to the parent block
1749
+ if (allChildBlocks.length > 0) {
1750
+ listItemBlock[listType].children = allChildBlocks;
1536
1751
  }
1537
1752
  // Only add the block if it has text or children
1538
1753
  const listData = listItemBlock[listType];
@@ -1542,17 +1757,21 @@ class NotionAITool {
1542
1757
  }
1543
1758
  }
1544
1759
  catch (error) {
1545
- console.warn('Error processing nested list:', error);
1760
+ console.warn('Error processing nested list with child blocks:', error);
1546
1761
  // Fallback: create a simple text block with the content
1547
1762
  blocks.push({
1548
1763
  type: 'paragraph',
1549
1764
  paragraph: {
1550
- rich_text: [{ type: 'text', text: { content: 'Error processing list content' } }],
1765
+ rich_text: [{ type: 'text', text: { content: 'Error processing list content with child blocks' } }],
1551
1766
  },
1552
1767
  });
1553
1768
  }
1554
1769
  }
1555
- // Extract list items with proper branching structure - only process top-level <li> tags
1770
+ // Legacy function for backward compatibility
1771
+ static processNestedList(listContent, listType, blocks, childNodes) {
1772
+ NotionAITool.processNestedListWithChildBlocks(listContent, listType, blocks, new Map());
1773
+ }
1774
+ // Extract list items with proper branching structure - simplified for hierarchical processing
1556
1775
  static extractListItemsWithBranching(content) {
1557
1776
  const items = [];
1558
1777
  let pos = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-notion-advanced",
3
- "version": "1.2.30-beta",
3
+ "version": "1.2.32-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": [