n8n-nodes-notion-advanced 1.2.26-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
|
-
//
|
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,21 +903,16 @@ 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 `__BLOCK_${placeholderCounter.value++}__`;
|
883
|
-
}
|
884
906
|
// Use blockCreator to create the block
|
885
907
|
const block = node.processor(...node.groups);
|
886
908
|
if (block) {
|
887
909
|
blocks.push(block);
|
888
910
|
}
|
889
|
-
return
|
911
|
+
return ''; // Remove completely - no placeholder needed
|
890
912
|
}
|
891
913
|
catch (error) {
|
892
914
|
console.warn(`Error processing XML node ${node.tagName}:`, error);
|
893
|
-
return
|
915
|
+
return ''; // Remove even on error to prevent artifacts
|
894
916
|
}
|
895
917
|
};
|
896
918
|
// Process all root nodes
|
@@ -1203,9 +1225,7 @@ class NotionAITool {
|
|
1203
1225
|
const replacements = NotionAITool.processXMLTreeDepthFirst(xmlTree, blocks, counterRef);
|
1204
1226
|
// Step 3: Apply hierarchical replacements to content
|
1205
1227
|
processedContent = NotionAITool.applyHierarchicalReplacements(processedContent, xmlTree, replacements);
|
1206
|
-
// Step 4:
|
1207
|
-
processedContent = NotionAITool.cleanupAllPlaceholders(processedContent);
|
1208
|
-
// Step 5: Clean up any remaining HTML tags
|
1228
|
+
// Step 4: Clean up any remaining HTML tags
|
1209
1229
|
processedContent = NotionAITool.cleanupRemainingHtml(processedContent);
|
1210
1230
|
if (DEBUG_ORDERING) {
|
1211
1231
|
console.log(`Processed ${xmlTree.length} root XML nodes hierarchically, created ${blocks.length} blocks`);
|
@@ -1229,11 +1249,11 @@ class NotionAITool {
|
|
1229
1249
|
if (block) {
|
1230
1250
|
blocks.push(block);
|
1231
1251
|
}
|
1232
|
-
return
|
1252
|
+
return ''; // Remove completely - no placeholder needed
|
1233
1253
|
}
|
1234
1254
|
catch (error) {
|
1235
1255
|
console.warn('Error in fallback processor:', error);
|
1236
|
-
return
|
1256
|
+
return ''; // Remove even on error
|
1237
1257
|
}
|
1238
1258
|
},
|
1239
1259
|
groups: match.slice(1)
|
@@ -1401,61 +1421,28 @@ class NotionAITool {
|
|
1401
1421
|
if (!processed)
|
1402
1422
|
return '';
|
1403
1423
|
try {
|
1404
|
-
//
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
}
|
1419
|
-
// Sort matches by position
|
1420
|
-
blockMatches.sort((a, b) => a.start - b.start);
|
1421
|
-
// Process text segments between block elements
|
1422
|
-
blockMatches.forEach((blockMatch, index) => {
|
1423
|
-
// Add text before this block element
|
1424
|
-
if (blockMatch.start > lastIndex) {
|
1425
|
-
const beforeText = processed.substring(lastIndex, blockMatch.start).trim();
|
1426
|
-
if (beforeText) {
|
1427
|
-
segments.push(NotionAITool.convertInlineHtmlToMarkdown(beforeText));
|
1428
|
-
}
|
1429
|
-
}
|
1430
|
-
// Process content inside block element
|
1431
|
-
const innerContent = blockMatch.content.replace(new RegExp(`^<${blockMatch.tag}[^>]*>`, 'i'), '')
|
1432
|
-
.replace(new RegExp(`</${blockMatch.tag}>$`, 'i'), '')
|
1433
|
-
.trim();
|
1434
|
-
if (innerContent) {
|
1435
|
-
segments.push(NotionAITool.convertInlineHtmlToMarkdown(innerContent));
|
1436
|
-
}
|
1437
|
-
lastIndex = blockMatch.end;
|
1438
|
-
});
|
1439
|
-
// Add remaining text after last block element
|
1440
|
-
if (lastIndex < processed.length) {
|
1441
|
-
const remainingText = processed.substring(lastIndex).trim();
|
1442
|
-
if (remainingText) {
|
1443
|
-
segments.push(NotionAITool.convertInlineHtmlToMarkdown(remainingText));
|
1444
|
-
}
|
1445
|
-
}
|
1446
|
-
// If no block elements were found, process the whole content
|
1447
|
-
if (blockMatches.length === 0) {
|
1448
|
-
segments.push(NotionAITool.convertInlineHtmlToMarkdown(processed));
|
1449
|
-
}
|
1450
|
-
// Join segments with space and clean up
|
1451
|
-
const result = segments.filter(s => s.trim()).join(' ').trim();
|
1452
|
-
// Final cleanup of any remaining artifacts
|
1453
|
-
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;
|
1454
1438
|
}
|
1455
1439
|
catch (error) {
|
1456
1440
|
console.warn('Error processing nested HTML in list item:', error);
|
1457
|
-
// Fallback:
|
1458
|
-
return processed
|
1441
|
+
// Fallback: aggressively remove all HTML tags and return clean text
|
1442
|
+
return processed
|
1443
|
+
.replace(/<[^>]*>/g, ' ')
|
1444
|
+
.replace(/\s+/g, ' ')
|
1445
|
+
.trim();
|
1459
1446
|
}
|
1460
1447
|
}
|
1461
1448
|
// Helper function to convert inline HTML to markdown
|
@@ -1493,49 +1480,36 @@ class NotionAITool {
|
|
1493
1480
|
// Helper function to process nested lists and flatten them for Notion
|
1494
1481
|
static processNestedList(listContent, listType, blocks) {
|
1495
1482
|
try {
|
1496
|
-
// More robust list item extraction
|
1497
|
-
const
|
1498
|
-
|
1499
|
-
|
1500
|
-
let itemContent = match[1].trim();
|
1501
|
-
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())
|
1502
1487
|
continue;
|
1503
1488
|
// Check if this item contains nested lists
|
1504
1489
|
const hasNestedList = /<[uo]l\s*[^>]*>/i.test(itemContent);
|
1505
1490
|
if (hasNestedList) {
|
1506
|
-
// Split content into parts
|
1507
|
-
const parts = itemContent.split(/(<[uo]l\s*[^>]*>[\s\S]*?<\/[uo]l>)/
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
if
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
},
|
1518
|
-
});
|
1519
|
-
}
|
1520
|
-
}
|
1521
|
-
// Process nested lists
|
1522
|
-
for (let i = 1; i < parts.length; i += 2) {
|
1523
|
-
const nestedListHtml = parts[i];
|
1524
|
-
if (nestedListHtml) {
|
1525
|
-
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);
|
1526
1502
|
if (nestedListMatch) {
|
1527
|
-
const [,
|
1528
|
-
const nestedListType =
|
1503
|
+
const [, listTag, innerContent] = nestedListMatch;
|
1504
|
+
const nestedListType = listTag === 'ul' ? 'bulleted_list_item' : 'numbered_list_item';
|
1529
1505
|
// Recursively process nested list
|
1530
|
-
NotionAITool.processNestedList(
|
1506
|
+
NotionAITool.processNestedList(innerContent, nestedListType, blocks);
|
1531
1507
|
}
|
1532
1508
|
}
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
if (afterContent) {
|
1538
|
-
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);
|
1539
1513
|
if (cleanContent) {
|
1540
1514
|
blocks.push({
|
1541
1515
|
type: listType,
|
@@ -1572,6 +1546,62 @@ class NotionAITool {
|
|
1572
1546
|
});
|
1573
1547
|
}
|
1574
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
|
+
}
|
1575
1605
|
// Helper function to get callout emoji based on type
|
1576
1606
|
static getCalloutEmoji(type) {
|
1577
1607
|
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.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": [
|