n8n-nodes-notion-advanced 1.2.32-beta → 1.2.34-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.
@@ -1487,21 +1487,27 @@ class NotionAITool {
1487
1487
  processed = processed.replace(/<\/?li\s*[^>]*>/gi, '');
1488
1488
  // Remove any other common list-related fragments
1489
1489
  processed = processed.replace(/<\/?[uo]l\s*[^>]*>/gi, '');
1490
- // Simple cleanup - just remove remaining HTML tags and clean whitespace
1491
- // 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
1492
1493
  const result = processed
1493
- .replace(/<[^>]*>/g, ' ') // Remove any remaining HTML tags
1494
1494
  .replace(/\s+/g, ' ') // Clean up whitespace
1495
1495
  .trim();
1496
1496
  return result;
1497
1497
  }
1498
1498
  catch (error) {
1499
1499
  console.warn('Error processing nested HTML in list item:', error);
1500
- // Fallback: aggressively remove all HTML tags and return clean text
1501
- return processed
1502
- .replace(/<[^>]*>/g, ' ')
1503
- .replace(/\s+/g, ' ')
1504
- .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
+ }
1505
1511
  }
1506
1512
  }
1507
1513
  // Helper function to convert inline HTML to markdown
@@ -1538,7 +1544,6 @@ class NotionAITool {
1538
1544
  }
1539
1545
  // Build hierarchy structure for lists using HierarchyNode approach
1540
1546
  static buildListHierarchy(listContent, listType, childHierarchyNodes) {
1541
- var _a;
1542
1547
  try {
1543
1548
  // Process each <li> element and build hierarchy
1544
1549
  const listItems = NotionAITool.extractListItemsWithBranching(listContent);
@@ -1547,7 +1552,7 @@ class NotionAITool {
1547
1552
  const listItemPositions = NotionAITool.getListItemPositions(listContent);
1548
1553
  for (let i = 0; i < listItems.length; i++) {
1549
1554
  const item = listItems[i];
1550
- if (!item.text && !item.children.length)
1555
+ if (!item.text && !item.children.length && !(item.extractedChildBlocks && item.extractedChildBlocks.length > 0))
1551
1556
  continue;
1552
1557
  // Create list item block
1553
1558
  const listItemBlock = {
@@ -1565,19 +1570,24 @@ class NotionAITool {
1565
1570
  }
1566
1571
  // Collect child hierarchy nodes for this list item based on position mapping
1567
1572
  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
- }
1573
+ // Add extracted child blocks (from XML tags within list items)
1574
+ // These are the primary source for child blocks in list items
1575
+ if (item.extractedChildBlocks && Array.isArray(item.extractedChildBlocks)) {
1576
+ const extractedBlocks = item.extractedChildBlocks;
1577
+ for (const block of extractedBlocks) {
1578
+ itemChildNodes.push({
1579
+ block,
1580
+ children: [],
1581
+ metadata: {
1582
+ sourcePosition: i,
1583
+ tagName: block.type
1584
+ }
1585
+ });
1579
1586
  }
1580
1587
  }
1588
+ // Note: We prioritize extractedChildBlocks over childHierarchyNodes for list items
1589
+ // since list-specific processing in extractListItemsWithBranching is more accurate
1590
+ // for handling child blocks within <li> elements
1581
1591
  // Process nested list children (traditional nested lists)
1582
1592
  if (item.children.length > 0) {
1583
1593
  for (const child of item.children) {
@@ -1597,7 +1607,7 @@ class NotionAITool {
1597
1607
  tagName: listType
1598
1608
  }
1599
1609
  };
1600
- // Only add if it has content or children
1610
+ // Only add if it has content or children (including extracted child blocks)
1601
1611
  const listData = listItemBlock[listType];
1602
1612
  if ((listData.rich_text && listData.rich_text.length > 0) || itemChildNodes.length > 0) {
1603
1613
  listItemHierarchyNodes.push(listItemHierarchyNode);
@@ -1824,13 +1834,198 @@ class NotionAITool {
1824
1834
  continue;
1825
1835
  }
1826
1836
  const item = { text: '', children: [] };
1827
- // Process the content to separate text from nested lists
1837
+ // STEP 1: Extract and convert child block XML to actual blocks before removing from parent text
1838
+ let parentTextContent = fullItemContent;
1839
+ const extractedChildBlocks = [];
1840
+ // List of XML tags that create child blocks (these should be removed from parent text)
1841
+ const childBlockTags = [
1842
+ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
1843
+ 'blockquote', 'quote', 'callout', 'code', 'pre',
1844
+ 'todo', 'toggle', 'image', 'embed', 'bookmark',
1845
+ 'equation', 'divider'
1846
+ ];
1847
+ // Extract child blocks first, then remove from parent text
1848
+ childBlockTags.forEach(tag => {
1849
+ // Extract and process paired tags first
1850
+ const pairedRegex = new RegExp(`<${tag}[^>]*>(.*?)<\\/${tag}>`, 'gis');
1851
+ let match;
1852
+ while ((match = pairedRegex.exec(parentTextContent)) !== null) {
1853
+ const fullMatch = match[0];
1854
+ const content = match[1];
1855
+ // Create child block based on tag type
1856
+ try {
1857
+ let childBlock = null;
1858
+ switch (tag) {
1859
+ case 'embed':
1860
+ childBlock = {
1861
+ type: 'embed',
1862
+ embed: { url: content.trim() }
1863
+ };
1864
+ break;
1865
+ case 'bookmark':
1866
+ childBlock = {
1867
+ type: 'bookmark',
1868
+ bookmark: { url: content.trim() }
1869
+ };
1870
+ break;
1871
+ case 'p':
1872
+ const markdownContent = NotionAITool.convertInlineHtmlToMarkdown(content.trim());
1873
+ childBlock = {
1874
+ type: 'paragraph',
1875
+ paragraph: { rich_text: NotionAITool.parseBasicMarkdown(markdownContent) }
1876
+ };
1877
+ break;
1878
+ case 'h1':
1879
+ case 'h2':
1880
+ case 'h3':
1881
+ const headingType = `heading_${tag.charAt(1)}`;
1882
+ childBlock = {
1883
+ type: headingType,
1884
+ [headingType]: { rich_text: [{ type: 'text', text: { content: content.trim() } }] }
1885
+ };
1886
+ break;
1887
+ case 'quote':
1888
+ case 'blockquote':
1889
+ childBlock = {
1890
+ type: 'quote',
1891
+ quote: { rich_text: NotionAITool.parseBasicMarkdown(content.trim()) }
1892
+ };
1893
+ break;
1894
+ case 'callout':
1895
+ // Extract type attribute if present
1896
+ const typeMatch = fullMatch.match(/type="([^"]*)"/);
1897
+ const calloutType = typeMatch ? typeMatch[1] : 'info';
1898
+ const emoji = NotionAITool.getCalloutEmoji(calloutType.toLowerCase());
1899
+ const color = NotionAITool.getCalloutColor(calloutType.toLowerCase());
1900
+ childBlock = {
1901
+ type: 'callout',
1902
+ callout: {
1903
+ rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
1904
+ icon: { type: 'emoji', emoji },
1905
+ color: color
1906
+ }
1907
+ };
1908
+ break;
1909
+ case 'code':
1910
+ case 'pre':
1911
+ // Extract language attribute if present
1912
+ const langMatch = fullMatch.match(/language="([^"]*)"/);
1913
+ const language = langMatch ? langMatch[1] : 'plain_text';
1914
+ childBlock = {
1915
+ type: 'code',
1916
+ code: {
1917
+ rich_text: [{ type: 'text', text: { content: content.trim() } }],
1918
+ language: language === 'plain text' ? 'plain_text' : language
1919
+ }
1920
+ };
1921
+ break;
1922
+ case 'todo':
1923
+ // Extract checked attribute if present
1924
+ const checkedMatch = fullMatch.match(/checked="([^"]*)"/);
1925
+ const isChecked = checkedMatch ? checkedMatch[1].toLowerCase() === 'true' : false;
1926
+ childBlock = {
1927
+ type: 'to_do',
1928
+ to_do: {
1929
+ rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
1930
+ checked: isChecked
1931
+ }
1932
+ };
1933
+ break;
1934
+ case 'toggle':
1935
+ childBlock = {
1936
+ type: 'toggle',
1937
+ toggle: {
1938
+ rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
1939
+ children: []
1940
+ }
1941
+ };
1942
+ break;
1943
+ case 'equation':
1944
+ childBlock = {
1945
+ type: 'equation',
1946
+ equation: { expression: content.trim() }
1947
+ };
1948
+ break;
1949
+ case 'image':
1950
+ // Extract src and alt attributes
1951
+ const srcMatch = fullMatch.match(/src="([^"]*)"/);
1952
+ const altMatch = fullMatch.match(/alt="([^"]*)"/);
1953
+ const src = srcMatch ? srcMatch[1] : '';
1954
+ const alt = altMatch ? altMatch[1] : '';
1955
+ const caption = content.trim() || alt;
1956
+ childBlock = {
1957
+ type: 'image',
1958
+ image: {
1959
+ type: 'external',
1960
+ external: { url: src },
1961
+ caption: caption ? NotionAITool.parseBasicMarkdown(caption) : []
1962
+ }
1963
+ };
1964
+ break;
1965
+ case 'divider':
1966
+ childBlock = {
1967
+ type: 'divider',
1968
+ divider: {}
1969
+ };
1970
+ break;
1971
+ }
1972
+ if (childBlock) {
1973
+ extractedChildBlocks.push(childBlock);
1974
+ }
1975
+ }
1976
+ catch (error) {
1977
+ console.warn(`Error creating child block for tag ${tag}:`, error);
1978
+ }
1979
+ }
1980
+ // Handle self-closing tags (mainly for divider, image)
1981
+ const selfClosingRegex = new RegExp(`<${tag}[^>]*\\/>`, 'gis');
1982
+ let selfMatch;
1983
+ while ((selfMatch = selfClosingRegex.exec(parentTextContent)) !== null) {
1984
+ const fullMatch = selfMatch[0];
1985
+ try {
1986
+ let childBlock = null;
1987
+ if (tag === 'divider') {
1988
+ childBlock = {
1989
+ type: 'divider',
1990
+ divider: {}
1991
+ };
1992
+ }
1993
+ else if (tag === 'image') {
1994
+ // Extract src and alt attributes
1995
+ const srcMatch = fullMatch.match(/src="([^"]*)"/);
1996
+ const altMatch = fullMatch.match(/alt="([^"]*)"/);
1997
+ const src = srcMatch ? srcMatch[1] : '';
1998
+ const alt = altMatch ? altMatch[1] : '';
1999
+ childBlock = {
2000
+ type: 'image',
2001
+ image: {
2002
+ type: 'external',
2003
+ external: { url: src },
2004
+ caption: alt ? NotionAITool.parseBasicMarkdown(alt) : []
2005
+ }
2006
+ };
2007
+ }
2008
+ if (childBlock) {
2009
+ extractedChildBlocks.push(childBlock);
2010
+ }
2011
+ }
2012
+ catch (error) {
2013
+ console.warn(`Error creating self-closing child block for tag ${tag}:`, error);
2014
+ }
2015
+ }
2016
+ // Now remove both paired and self-closing tags from parent text
2017
+ parentTextContent = parentTextContent.replace(pairedRegex, '');
2018
+ parentTextContent = parentTextContent.replace(selfClosingRegex, '');
2019
+ });
2020
+ // Store extracted child blocks in the item for later use
2021
+ item.extractedChildBlocks = extractedChildBlocks;
2022
+ // STEP 2: Process the content to separate remaining text from nested lists
1828
2023
  let contentPos = 0;
1829
2024
  let textParts = [];
1830
- while (contentPos < fullItemContent.length) {
2025
+ while (contentPos < parentTextContent.length) {
1831
2026
  // Look for the next nested list (ul or ol)
1832
- const nextUlStart = fullItemContent.indexOf('<ul', contentPos);
1833
- const nextOlStart = fullItemContent.indexOf('<ol', contentPos);
2027
+ const nextUlStart = parentTextContent.indexOf('<ul', contentPos);
2028
+ const nextOlStart = parentTextContent.indexOf('<ol', contentPos);
1834
2029
  let nextListStart = -1;
1835
2030
  let listType = '';
1836
2031
  if (nextUlStart !== -1 && (nextOlStart === -1 || nextUlStart < nextOlStart)) {
@@ -1843,22 +2038,22 @@ class NotionAITool {
1843
2038
  }
1844
2039
  if (nextListStart === -1) {
1845
2040
  // No more nested lists - add remaining text
1846
- const remainingText = fullItemContent.substring(contentPos);
2041
+ const remainingText = parentTextContent.substring(contentPos);
1847
2042
  if (remainingText.trim()) {
1848
2043
  textParts.push(remainingText);
1849
2044
  }
1850
2045
  break;
1851
2046
  }
1852
2047
  // Add text before the nested list
1853
- const textBefore = fullItemContent.substring(contentPos, nextListStart);
2048
+ const textBefore = parentTextContent.substring(contentPos, nextListStart);
1854
2049
  if (textBefore.trim()) {
1855
2050
  textParts.push(textBefore);
1856
2051
  }
1857
2052
  // Find the end of this nested list
1858
- const listOpenEnd = fullItemContent.indexOf('>', nextListStart);
2053
+ const listOpenEnd = parentTextContent.indexOf('>', nextListStart);
1859
2054
  if (listOpenEnd === -1) {
1860
2055
  // Malformed list tag
1861
- textParts.push(fullItemContent.substring(contentPos));
2056
+ textParts.push(parentTextContent.substring(contentPos));
1862
2057
  break;
1863
2058
  }
1864
2059
  // Track depth to find the matching closing tag
@@ -1867,9 +2062,9 @@ class NotionAITool {
1867
2062
  let listEnd = -1;
1868
2063
  const openTag = `<${listType}`;
1869
2064
  const closeTag = `</${listType}>`;
1870
- while (listSearchPos < fullItemContent.length && listDepth > 0) {
1871
- const nextListOpen = fullItemContent.indexOf(openTag, listSearchPos);
1872
- const nextListClose = fullItemContent.indexOf(closeTag, listSearchPos);
2065
+ while (listSearchPos < parentTextContent.length && listDepth > 0) {
2066
+ const nextListOpen = parentTextContent.indexOf(openTag, listSearchPos);
2067
+ const nextListClose = parentTextContent.indexOf(closeTag, listSearchPos);
1873
2068
  if (nextListClose === -1)
1874
2069
  break;
1875
2070
  if (nextListOpen !== -1 && nextListOpen < nextListClose) {
@@ -1886,7 +2081,8 @@ class NotionAITool {
1886
2081
  }
1887
2082
  }
1888
2083
  if (listEnd !== -1) {
1889
- // Extract the content between <ul>/<ol> and </ul>/<ol>
2084
+ // Extract the content between <ul>/<ol> and </ul>/<ol> from ORIGINAL content
2085
+ // (not parentTextContent which has child blocks removed)
1890
2086
  const listContent = fullItemContent.substring(listOpenEnd + 1, listEnd - closeTag.length);
1891
2087
  item.children.push({
1892
2088
  type: listType,
@@ -1896,7 +2092,7 @@ class NotionAITool {
1896
2092
  }
1897
2093
  else {
1898
2094
  // Malformed nested list - treat remaining as text
1899
- textParts.push(fullItemContent.substring(contentPos));
2095
+ textParts.push(parentTextContent.substring(contentPos));
1900
2096
  break;
1901
2097
  }
1902
2098
  }
@@ -1908,8 +2104,8 @@ class NotionAITool {
1908
2104
  item.text = cleanText;
1909
2105
  }
1910
2106
  }
1911
- // Only add items that have either text or children
1912
- if (item.text.trim() || item.children.length > 0) {
2107
+ // Only add items that have either text, children, or extracted child blocks
2108
+ if (item.text.trim() || item.children.length > 0 || (item.extractedChildBlocks && item.extractedChildBlocks.length > 0)) {
1913
2109
  items.push(item);
1914
2110
  }
1915
2111
  pos = liEnd + 5; // Move past </li>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-notion-advanced",
3
- "version": "1.2.32-beta",
3
+ "version": "1.2.34-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": [