n8n-nodes-notion-advanced 1.2.27-beta → 1.2.29-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,14 @@ 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 extractListItemsWithBranching(content: string): Array<{
|
59
|
+
text: string;
|
60
|
+
children: Array<{
|
61
|
+
type: string;
|
62
|
+
content: string;
|
63
|
+
}>;
|
64
|
+
}>;
|
65
|
+
static extractListItems(content: string): string[];
|
58
66
|
static getCalloutEmoji(type: string): string;
|
59
67
|
static getCalloutColor(type: string): string;
|
60
68
|
static parseBasicMarkdown(text: string): IDataObject[];
|
@@ -745,40 +745,77 @@ class NotionAITool {
|
|
745
745
|
return charIndex;
|
746
746
|
}
|
747
747
|
}
|
748
|
-
// Enhanced hierarchical XML tree structure
|
748
|
+
// Enhanced hierarchical XML tree structure using depth-aware parsing
|
749
749
|
static buildXMLTree(content, tagProcessors) {
|
750
750
|
var _a;
|
751
751
|
const allMatches = [];
|
752
|
-
|
753
|
-
// Step 1: Collect all XML tags with specific processors
|
752
|
+
// Step 1: Use depth-aware parsing for each tag processor
|
754
753
|
tagProcessors.forEach(({ regex, blockCreator, listProcessor }) => {
|
755
754
|
var _a;
|
756
|
-
const
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
}
|
774
|
-
|
775
|
-
|
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;
|
782
|
+
}
|
783
|
+
else {
|
784
|
+
// Found closing tag
|
785
|
+
depth--;
|
786
|
+
if (depth === 0) {
|
787
|
+
closeTagStart = nextClose;
|
788
|
+
break;
|
789
|
+
}
|
790
|
+
searchPos = nextClose + closePattern.length;
|
791
|
+
}
|
792
|
+
}
|
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);
|
811
|
+
}
|
812
|
+
pos = openTagEnd + 1;
|
776
813
|
}
|
777
814
|
});
|
778
815
|
// Step 2: Catch ANY remaining XML/HTML tags that weren't processed by specific processors
|
779
|
-
// This prevents ANY XML content from falling through to traditional processing
|
780
816
|
const genericXmlRegex = /<[^>]+>[\s\S]*?<\/[^>]+>|<[^>]+\/>/gis;
|
781
817
|
let genericMatch;
|
818
|
+
const processedRanges = allMatches.map(node => ({ start: node.start, end: node.end }));
|
782
819
|
while ((genericMatch = genericXmlRegex.exec(content)) !== null) {
|
783
820
|
const matchStart = genericMatch.index;
|
784
821
|
const matchEnd = genericMatch.index + genericMatch[0].length;
|
@@ -792,7 +829,7 @@ class NotionAITool {
|
|
792
829
|
start: matchStart,
|
793
830
|
end: matchEnd,
|
794
831
|
match: genericMatch[0],
|
795
|
-
processor: () => null,
|
832
|
+
processor: () => null,
|
796
833
|
groups: [],
|
797
834
|
children: [],
|
798
835
|
depth: 0,
|
@@ -801,12 +838,11 @@ class NotionAITool {
|
|
801
838
|
listProcessor: undefined
|
802
839
|
};
|
803
840
|
allMatches.push(xmlNode);
|
804
|
-
processedRanges.push({ start: matchStart, end: matchEnd });
|
805
841
|
}
|
806
842
|
}
|
807
843
|
// Sort by start position to maintain document order
|
808
844
|
allMatches.sort((a, b) => a.start - b.start);
|
809
|
-
// Build parent-child relationships
|
845
|
+
// Build parent-child relationships
|
810
846
|
const rootNodes = [];
|
811
847
|
const nodeStack = [];
|
812
848
|
for (const node of allMatches) {
|
@@ -826,7 +862,7 @@ class NotionAITool {
|
|
826
862
|
// This is a root node
|
827
863
|
rootNodes.push(node);
|
828
864
|
}
|
829
|
-
//
|
865
|
+
// Push to stack for potential children
|
830
866
|
if (!node.match.endsWith('/>') && node.match.includes('</')) {
|
831
867
|
nodeStack.push(node);
|
832
868
|
}
|
@@ -837,7 +873,34 @@ class NotionAITool {
|
|
837
873
|
static processXMLTreeDepthFirst(nodes, blocks, placeholderCounter) {
|
838
874
|
const replacements = new Map();
|
839
875
|
const processNode = (node) => {
|
840
|
-
//
|
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();
|
882
|
+
const openTagRegex = new RegExp(`^<${tagName}[^>]*>`, 'i');
|
883
|
+
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);
|
887
|
+
if (openMatch && closeMatch) {
|
888
|
+
const openTag = openMatch[0];
|
889
|
+
const closeTag = closeMatch[0];
|
890
|
+
const startIndex = openTag.length;
|
891
|
+
const endIndex = node.match.length - closeTag.length;
|
892
|
+
innerContent = node.match.substring(startIndex, endIndex);
|
893
|
+
}
|
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)
|
841
904
|
for (const child of node.children) {
|
842
905
|
const childReplacement = processNode(child);
|
843
906
|
replacements.set(child.id, childReplacement);
|
@@ -876,11 +939,6 @@ class NotionAITool {
|
|
876
939
|
}
|
877
940
|
// Process this node with updated inner content
|
878
941
|
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
942
|
// Use blockCreator to create the block
|
885
943
|
const block = node.processor(...node.groups);
|
886
944
|
if (block) {
|
@@ -1157,23 +1215,9 @@ class NotionAITool {
|
|
1157
1215
|
};
|
1158
1216
|
}
|
1159
1217
|
},
|
1160
|
-
// Standalone
|
1161
|
-
|
1162
|
-
|
1163
|
-
blockCreator: (content) => {
|
1164
|
-
if (content.trim()) {
|
1165
|
-
// Convert HTML to markdown first, then parse to rich text
|
1166
|
-
const markdownContent = NotionAITool.convertInlineHtmlToMarkdown(content.trim());
|
1167
|
-
return {
|
1168
|
-
type: 'bulleted_list_item',
|
1169
|
-
bulleted_list_item: {
|
1170
|
-
rich_text: NotionAITool.parseBasicMarkdown(markdownContent),
|
1171
|
-
},
|
1172
|
-
};
|
1173
|
-
}
|
1174
|
-
return null;
|
1175
|
-
}
|
1176
|
-
},
|
1218
|
+
// REMOVED: Standalone <li> processor
|
1219
|
+
// <li> tags should ONLY be processed within <ul>/<ol> contexts via the list processors above
|
1220
|
+
// Having a standalone <li> processor causes XML fragments and double processing
|
1177
1221
|
// Line breaks: <br/> or <br>
|
1178
1222
|
{
|
1179
1223
|
regex: /<br\s*\/?>/gis,
|
@@ -1399,61 +1443,28 @@ class NotionAITool {
|
|
1399
1443
|
if (!processed)
|
1400
1444
|
return '';
|
1401
1445
|
try {
|
1402
|
-
//
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
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();
|
1446
|
+
// First, aggressively remove any nested list tags and their content
|
1447
|
+
// This prevents XML fragments from appearing in the final content
|
1448
|
+
processed = processed.replace(/<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>/gi, '');
|
1449
|
+
// Remove any standalone list item tags that might be left behind
|
1450
|
+
processed = processed.replace(/<\/?li\s*[^>]*>/gi, '');
|
1451
|
+
// Remove any other common list-related fragments
|
1452
|
+
processed = processed.replace(/<\/?[uo]l\s*[^>]*>/gi, '');
|
1453
|
+
// Simple cleanup - just remove remaining HTML tags and clean whitespace
|
1454
|
+
// Avoid convertInlineHtmlToMarkdown to prevent duplication issues
|
1455
|
+
const result = processed
|
1456
|
+
.replace(/<[^>]*>/g, ' ') // Remove any remaining HTML tags
|
1457
|
+
.replace(/\s+/g, ' ') // Clean up whitespace
|
1458
|
+
.trim();
|
1459
|
+
return result;
|
1452
1460
|
}
|
1453
1461
|
catch (error) {
|
1454
1462
|
console.warn('Error processing nested HTML in list item:', error);
|
1455
|
-
// Fallback:
|
1456
|
-
return processed
|
1463
|
+
// Fallback: aggressively remove all HTML tags and return clean text
|
1464
|
+
return processed
|
1465
|
+
.replace(/<[^>]*>/g, ' ')
|
1466
|
+
.replace(/\s+/g, ' ')
|
1467
|
+
.trim();
|
1457
1468
|
}
|
1458
1469
|
}
|
1459
1470
|
// Helper function to convert inline HTML to markdown
|
@@ -1488,75 +1499,32 @@ class NotionAITool {
|
|
1488
1499
|
processed = processed.replace(/\s+/g, ' ').trim();
|
1489
1500
|
return processed;
|
1490
1501
|
}
|
1491
|
-
// Helper function to process
|
1502
|
+
// Helper function to process lists using branch-based approach
|
1503
|
+
// Each <ul> and <ol> represents a new branch that contains children
|
1492
1504
|
static processNestedList(listContent, listType, blocks) {
|
1493
1505
|
try {
|
1494
|
-
//
|
1495
|
-
const
|
1496
|
-
|
1497
|
-
|
1498
|
-
let itemContent = match[1].trim();
|
1499
|
-
if (!itemContent)
|
1506
|
+
// Process each <li> element as a potential branch point
|
1507
|
+
const listItems = NotionAITool.extractListItemsWithBranching(listContent);
|
1508
|
+
for (const item of listItems) {
|
1509
|
+
if (!item.text && !item.children.length)
|
1500
1510
|
continue;
|
1501
|
-
//
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
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);
|
1524
|
-
if (nestedListMatch) {
|
1525
|
-
const [, nestedListTag, nestedContent] = nestedListMatch;
|
1526
|
-
const nestedListType = nestedListTag === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
|
1527
|
-
// Recursively process nested list
|
1528
|
-
NotionAITool.processNestedList(nestedContent, nestedListType, blocks);
|
1529
|
-
}
|
1530
|
-
}
|
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);
|
1537
|
-
if (cleanContent) {
|
1538
|
-
blocks.push({
|
1539
|
-
type: listType,
|
1540
|
-
[listType]: {
|
1541
|
-
rich_text: NotionAITool.parseBasicMarkdown(cleanContent),
|
1542
|
-
},
|
1543
|
-
});
|
1544
|
-
}
|
1545
|
-
}
|
1546
|
-
}
|
1547
|
-
}
|
1548
|
-
else {
|
1549
|
-
// Simple item without nested lists
|
1550
|
-
const cleanContent = NotionAITool.processNestedHtmlInListItem(itemContent);
|
1551
|
-
if (cleanContent) {
|
1511
|
+
// Create list item for the parent text (if any)
|
1512
|
+
if (item.text && item.text.trim()) {
|
1513
|
+
const cleanText = NotionAITool.processNestedHtmlInListItem(item.text);
|
1514
|
+
if (cleanText) {
|
1552
1515
|
blocks.push({
|
1553
1516
|
type: listType,
|
1554
1517
|
[listType]: {
|
1555
|
-
rich_text: NotionAITool.parseBasicMarkdown(
|
1518
|
+
rich_text: NotionAITool.parseBasicMarkdown(cleanText),
|
1556
1519
|
},
|
1557
1520
|
});
|
1558
1521
|
}
|
1559
1522
|
}
|
1523
|
+
// Process each child branch
|
1524
|
+
for (const child of item.children) {
|
1525
|
+
const childListType = child.type === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
|
1526
|
+
NotionAITool.processNestedList(child.content, childListType, blocks);
|
1527
|
+
}
|
1560
1528
|
}
|
1561
1529
|
}
|
1562
1530
|
catch (error) {
|
@@ -1570,6 +1538,220 @@ class NotionAITool {
|
|
1570
1538
|
});
|
1571
1539
|
}
|
1572
1540
|
}
|
1541
|
+
// Extract list items with proper branching structure - only process top-level <li> tags
|
1542
|
+
static extractListItemsWithBranching(content) {
|
1543
|
+
const items = [];
|
1544
|
+
let pos = 0;
|
1545
|
+
while (pos < content.length) {
|
1546
|
+
// Find next <li> tag at the current level
|
1547
|
+
const liStart = content.indexOf('<li', pos);
|
1548
|
+
if (liStart === -1)
|
1549
|
+
break;
|
1550
|
+
const liOpenEnd = content.indexOf('>', liStart);
|
1551
|
+
if (liOpenEnd === -1)
|
1552
|
+
break;
|
1553
|
+
// Find the matching </li> using proper depth tracking for nested tags
|
1554
|
+
let depth = 0;
|
1555
|
+
let searchPos = liOpenEnd + 1; // Start after the opening <li> tag
|
1556
|
+
let liEnd = -1;
|
1557
|
+
while (searchPos < content.length) {
|
1558
|
+
const nextLiOpen = content.indexOf('<li', searchPos);
|
1559
|
+
const nextLiClose = content.indexOf('</li>', searchPos);
|
1560
|
+
// Handle case where no more closing tags
|
1561
|
+
if (nextLiClose === -1)
|
1562
|
+
break;
|
1563
|
+
// If there's an opening tag before the next closing tag
|
1564
|
+
if (nextLiOpen !== -1 && nextLiOpen < nextLiClose) {
|
1565
|
+
depth++;
|
1566
|
+
searchPos = nextLiOpen + 3; // Move past '<li'
|
1567
|
+
}
|
1568
|
+
else {
|
1569
|
+
// Found a closing tag
|
1570
|
+
if (depth === 0) {
|
1571
|
+
// This is our matching closing tag
|
1572
|
+
liEnd = nextLiClose;
|
1573
|
+
break;
|
1574
|
+
}
|
1575
|
+
else {
|
1576
|
+
// This closing tag belongs to a nested li
|
1577
|
+
depth--;
|
1578
|
+
searchPos = nextLiClose + 5; // Move past '</li>'
|
1579
|
+
}
|
1580
|
+
}
|
1581
|
+
}
|
1582
|
+
if (liEnd === -1) {
|
1583
|
+
// No matching closing tag found
|
1584
|
+
pos = liOpenEnd + 1;
|
1585
|
+
continue;
|
1586
|
+
}
|
1587
|
+
// Extract the content between <li> and </li>
|
1588
|
+
const fullItemContent = content.substring(liOpenEnd + 1, liEnd);
|
1589
|
+
if (!fullItemContent.trim()) {
|
1590
|
+
pos = liEnd + 5;
|
1591
|
+
continue;
|
1592
|
+
}
|
1593
|
+
const item = { text: '', children: [] };
|
1594
|
+
// Process the content to separate text from nested lists
|
1595
|
+
let contentPos = 0;
|
1596
|
+
let textParts = [];
|
1597
|
+
while (contentPos < fullItemContent.length) {
|
1598
|
+
// Look for the next nested list (ul or ol)
|
1599
|
+
const nextUlStart = fullItemContent.indexOf('<ul', contentPos);
|
1600
|
+
const nextOlStart = fullItemContent.indexOf('<ol', contentPos);
|
1601
|
+
let nextListStart = -1;
|
1602
|
+
let listType = '';
|
1603
|
+
if (nextUlStart !== -1 && (nextOlStart === -1 || nextUlStart < nextOlStart)) {
|
1604
|
+
nextListStart = nextUlStart;
|
1605
|
+
listType = 'ul';
|
1606
|
+
}
|
1607
|
+
else if (nextOlStart !== -1) {
|
1608
|
+
nextListStart = nextOlStart;
|
1609
|
+
listType = 'ol';
|
1610
|
+
}
|
1611
|
+
if (nextListStart === -1) {
|
1612
|
+
// No more nested lists - add remaining text
|
1613
|
+
const remainingText = fullItemContent.substring(contentPos);
|
1614
|
+
if (remainingText.trim()) {
|
1615
|
+
textParts.push(remainingText);
|
1616
|
+
}
|
1617
|
+
break;
|
1618
|
+
}
|
1619
|
+
// Add text before the nested list
|
1620
|
+
const textBefore = fullItemContent.substring(contentPos, nextListStart);
|
1621
|
+
if (textBefore.trim()) {
|
1622
|
+
textParts.push(textBefore);
|
1623
|
+
}
|
1624
|
+
// Find the end of this nested list
|
1625
|
+
const listOpenEnd = fullItemContent.indexOf('>', nextListStart);
|
1626
|
+
if (listOpenEnd === -1) {
|
1627
|
+
// Malformed list tag
|
1628
|
+
textParts.push(fullItemContent.substring(contentPos));
|
1629
|
+
break;
|
1630
|
+
}
|
1631
|
+
// Track depth to find the matching closing tag
|
1632
|
+
let listDepth = 1;
|
1633
|
+
let listSearchPos = listOpenEnd + 1;
|
1634
|
+
let listEnd = -1;
|
1635
|
+
const openTag = `<${listType}`;
|
1636
|
+
const closeTag = `</${listType}>`;
|
1637
|
+
while (listSearchPos < fullItemContent.length && listDepth > 0) {
|
1638
|
+
const nextListOpen = fullItemContent.indexOf(openTag, listSearchPos);
|
1639
|
+
const nextListClose = fullItemContent.indexOf(closeTag, listSearchPos);
|
1640
|
+
if (nextListClose === -1)
|
1641
|
+
break;
|
1642
|
+
if (nextListOpen !== -1 && nextListOpen < nextListClose) {
|
1643
|
+
listDepth++;
|
1644
|
+
listSearchPos = nextListOpen + openTag.length;
|
1645
|
+
}
|
1646
|
+
else {
|
1647
|
+
listDepth--;
|
1648
|
+
if (listDepth === 0) {
|
1649
|
+
listEnd = nextListClose + closeTag.length;
|
1650
|
+
break;
|
1651
|
+
}
|
1652
|
+
listSearchPos = nextListClose + closeTag.length;
|
1653
|
+
}
|
1654
|
+
}
|
1655
|
+
if (listEnd !== -1) {
|
1656
|
+
// Extract the content between <ul>/<ol> and </ul>/<ol>
|
1657
|
+
const listContent = fullItemContent.substring(listOpenEnd + 1, listEnd - closeTag.length);
|
1658
|
+
item.children.push({
|
1659
|
+
type: listType,
|
1660
|
+
content: listContent
|
1661
|
+
});
|
1662
|
+
contentPos = listEnd;
|
1663
|
+
}
|
1664
|
+
else {
|
1665
|
+
// Malformed nested list - treat remaining as text
|
1666
|
+
textParts.push(fullItemContent.substring(contentPos));
|
1667
|
+
break;
|
1668
|
+
}
|
1669
|
+
}
|
1670
|
+
// Combine all text parts and clean them
|
1671
|
+
if (textParts.length > 0) {
|
1672
|
+
const combinedText = textParts.join(' ').trim();
|
1673
|
+
const cleanText = NotionAITool.processNestedHtmlInListItem(combinedText);
|
1674
|
+
if (cleanText) {
|
1675
|
+
item.text = cleanText;
|
1676
|
+
}
|
1677
|
+
}
|
1678
|
+
// Only add items that have either text or children
|
1679
|
+
if (item.text.trim() || item.children.length > 0) {
|
1680
|
+
items.push(item);
|
1681
|
+
}
|
1682
|
+
pos = liEnd + 5; // Move past </li>
|
1683
|
+
}
|
1684
|
+
return items;
|
1685
|
+
}
|
1686
|
+
// Helper function to properly extract list items handling nested <li> tags
|
1687
|
+
static extractListItems(content) {
|
1688
|
+
const items = [];
|
1689
|
+
// Use a more robust regex approach that respects nesting
|
1690
|
+
// This regex captures the complete <li>...</li> blocks including nested content
|
1691
|
+
const liRegex = /<li[^>]*>((?:[^<]|<(?!\/li>))*?(?:<[uo]l[^>]*>[\s\S]*?<\/[uo]l>(?:[^<]|<(?!\/li>))*?)*?)<\/li>/gi;
|
1692
|
+
let match;
|
1693
|
+
while ((match = liRegex.exec(content)) !== null) {
|
1694
|
+
const itemContent = match[1];
|
1695
|
+
if (itemContent && itemContent.trim()) {
|
1696
|
+
items.push(itemContent.trim());
|
1697
|
+
}
|
1698
|
+
}
|
1699
|
+
// Fallback to the old depth-tracking method if regex fails
|
1700
|
+
if (items.length === 0) {
|
1701
|
+
let currentPos = 0;
|
1702
|
+
while (currentPos < content.length) {
|
1703
|
+
// Find the next <li> opening tag
|
1704
|
+
const liStart = content.indexOf('<li', currentPos);
|
1705
|
+
if (liStart === -1)
|
1706
|
+
break;
|
1707
|
+
// Find the end of the opening tag
|
1708
|
+
const openTagEnd = content.indexOf('>', liStart);
|
1709
|
+
if (openTagEnd === -1)
|
1710
|
+
break;
|
1711
|
+
// Now find the matching closing </li> tag accounting for nesting
|
1712
|
+
let depth = 1;
|
1713
|
+
let pos = openTagEnd + 1;
|
1714
|
+
let itemEnd = -1;
|
1715
|
+
while (pos < content.length && depth > 0) {
|
1716
|
+
const nextLiOpen = content.indexOf('<li', pos);
|
1717
|
+
const nextLiClose = content.indexOf('</li>', pos);
|
1718
|
+
// If no more closing tags, we're done
|
1719
|
+
if (nextLiClose === -1)
|
1720
|
+
break;
|
1721
|
+
// If there's an opening tag before the next closing tag, increase depth
|
1722
|
+
if (nextLiOpen !== -1 && nextLiOpen < nextLiClose) {
|
1723
|
+
depth++;
|
1724
|
+
pos = nextLiOpen + 3; // Move past '<li'
|
1725
|
+
}
|
1726
|
+
else {
|
1727
|
+
// Found a closing tag
|
1728
|
+
depth--;
|
1729
|
+
if (depth === 0) {
|
1730
|
+
itemEnd = nextLiClose + 5; // Include the '</li>'
|
1731
|
+
break;
|
1732
|
+
}
|
1733
|
+
else {
|
1734
|
+
pos = nextLiClose + 5; // Move past '</li>'
|
1735
|
+
}
|
1736
|
+
}
|
1737
|
+
}
|
1738
|
+
if (itemEnd !== -1) {
|
1739
|
+
// Extract the content between <li...> and </li>
|
1740
|
+
const fullMatch = content.substring(liStart, itemEnd);
|
1741
|
+
const innerMatch = fullMatch.match(/<li[^>]*>([\s\S]*)<\/li>$/);
|
1742
|
+
if (innerMatch) {
|
1743
|
+
items.push(innerMatch[1]);
|
1744
|
+
}
|
1745
|
+
currentPos = itemEnd;
|
1746
|
+
}
|
1747
|
+
else {
|
1748
|
+
// Malformed HTML, skip this tag
|
1749
|
+
currentPos = openTagEnd + 1;
|
1750
|
+
}
|
1751
|
+
}
|
1752
|
+
}
|
1753
|
+
return items;
|
1754
|
+
}
|
1573
1755
|
// Helper function to get callout emoji based on type
|
1574
1756
|
static getCalloutEmoji(type) {
|
1575
1757
|
const emojiMap = {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "n8n-nodes-notion-advanced",
|
3
|
-
"version": "1.2.
|
3
|
+
"version": "1.2.29-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": [
|