n8n-nodes-notion-advanced 1.2.3-beta → 1.2.4-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.
- package/nodes/NotionAdvanced/NotionAITool.node.d.ts +18 -0
- package/nodes/NotionAdvanced/NotionAITool.node.js +1154 -0
- package/nodes/NotionAdvanced/NotionAITool.node.ts +611 -0
- package/nodes/NotionAdvanced/NotionAdvanced.node.d.ts +25 -0
- package/nodes/NotionAdvanced/NotionAdvanced.node.js +905 -0
- package/nodes/NotionAdvanced/NotionAdvanced.node.ts +1022 -0
- package/nodes/NotionAdvanced/NotionUtils.d.ts +104 -0
- package/nodes/NotionAdvanced/NotionUtils.js +465 -0
- package/nodes/NotionAdvanced/NotionUtils.ts +588 -0
- package/nodes/NotionAdvanced/notion.svg +3 -0
- package/package.json +5 -6
- package/types/NotionTypes.d.ts +359 -0
- package/types/NotionTypes.js +3 -0
- package/types/NotionTypes.ts +411 -0
@@ -0,0 +1,1154 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.NotionAITool = void 0;
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
5
|
+
const NotionUtils_1 = require("./NotionUtils");
|
6
|
+
class NotionAITool {
|
7
|
+
constructor() {
|
8
|
+
this.description = {
|
9
|
+
displayName: 'Notion AI Tool',
|
10
|
+
name: 'notionAiTool',
|
11
|
+
icon: 'file:notion.svg',
|
12
|
+
group: ['ai'],
|
13
|
+
version: 1,
|
14
|
+
subtitle: '={{$parameter["operation"]}}',
|
15
|
+
description: 'AI-powered tool for creating and managing Notion content. Designed for use with AI Agent Nodes.',
|
16
|
+
defaults: {
|
17
|
+
name: 'Notion AI Tool',
|
18
|
+
},
|
19
|
+
inputs: ['main'],
|
20
|
+
outputs: ['main'],
|
21
|
+
usableAsTool: true,
|
22
|
+
codex: {
|
23
|
+
categories: ['Productivity', 'AI', 'Documentation'],
|
24
|
+
subcategories: {
|
25
|
+
'Productivity': ['Notion', 'Knowledge Management'],
|
26
|
+
'AI': ['AI Agent Tools', 'Natural Language Processing'],
|
27
|
+
'Documentation': ['Page Creation', 'Content Management']
|
28
|
+
},
|
29
|
+
resources: {
|
30
|
+
primaryDocumentation: [
|
31
|
+
{
|
32
|
+
url: 'https://github.com/AZ-IT-US/n8n-notion-advanced-node#ai-tool-usage',
|
33
|
+
},
|
34
|
+
],
|
35
|
+
},
|
36
|
+
alias: ['notion', 'productivity', 'ai-tool', 'pages', 'database'],
|
37
|
+
},
|
38
|
+
credentials: [
|
39
|
+
{
|
40
|
+
name: 'notionApi',
|
41
|
+
required: true,
|
42
|
+
},
|
43
|
+
],
|
44
|
+
properties: [
|
45
|
+
{
|
46
|
+
displayName: 'Operation',
|
47
|
+
name: 'operation',
|
48
|
+
type: 'options',
|
49
|
+
noDataExpression: true,
|
50
|
+
options: [
|
51
|
+
{
|
52
|
+
name: 'Create Page with Content',
|
53
|
+
value: 'createPageWithContent',
|
54
|
+
description: 'Create a new Notion page with structured content including text, headings, lists, and formatting',
|
55
|
+
action: 'Create a Notion page with content',
|
56
|
+
},
|
57
|
+
{
|
58
|
+
name: 'Add Content to Page',
|
59
|
+
value: 'addContentToPage',
|
60
|
+
description: 'Append new content blocks (paragraphs, headings, lists, etc.) to an existing Notion page',
|
61
|
+
action: 'Add content to existing page',
|
62
|
+
},
|
63
|
+
{
|
64
|
+
name: 'Search and Retrieve Pages',
|
65
|
+
value: 'searchPages',
|
66
|
+
description: 'Search for Notion pages by title, content, or properties and retrieve their information',
|
67
|
+
action: 'Search and retrieve pages',
|
68
|
+
},
|
69
|
+
{
|
70
|
+
name: 'Update Page Properties',
|
71
|
+
value: 'updatePageProperties',
|
72
|
+
description: 'Update page title, properties, status, tags, or other metadata',
|
73
|
+
action: 'Update page properties',
|
74
|
+
},
|
75
|
+
{
|
76
|
+
name: 'Create Database Entry',
|
77
|
+
value: 'createDatabaseEntry',
|
78
|
+
description: 'Create a new entry in a Notion database with specified properties and values',
|
79
|
+
action: 'Create database entry',
|
80
|
+
},
|
81
|
+
{
|
82
|
+
name: 'Query Database',
|
83
|
+
value: 'queryDatabase',
|
84
|
+
description: 'Search and filter database entries based on criteria and retrieve matching records',
|
85
|
+
action: 'Query database',
|
86
|
+
},
|
87
|
+
],
|
88
|
+
default: 'createPageWithContent',
|
89
|
+
},
|
90
|
+
// CREATE PAGE WITH CONTENT
|
91
|
+
{
|
92
|
+
displayName: 'Page Title',
|
93
|
+
name: 'pageTitle',
|
94
|
+
type: 'string',
|
95
|
+
required: true,
|
96
|
+
displayOptions: {
|
97
|
+
show: {
|
98
|
+
operation: ['createPageWithContent'],
|
99
|
+
},
|
100
|
+
},
|
101
|
+
default: '',
|
102
|
+
description: 'The title of the new page to create',
|
103
|
+
},
|
104
|
+
{
|
105
|
+
displayName: 'Parent Page/Database ID',
|
106
|
+
name: 'parentId',
|
107
|
+
type: 'string',
|
108
|
+
required: true,
|
109
|
+
displayOptions: {
|
110
|
+
show: {
|
111
|
+
operation: ['createPageWithContent', 'createDatabaseEntry'],
|
112
|
+
},
|
113
|
+
},
|
114
|
+
default: '',
|
115
|
+
description: 'ID of the parent page or database where this should be created. Can be a Notion URL or page ID.',
|
116
|
+
},
|
117
|
+
{
|
118
|
+
displayName: 'Content',
|
119
|
+
name: 'content',
|
120
|
+
type: 'string',
|
121
|
+
typeOptions: {
|
122
|
+
rows: 6,
|
123
|
+
},
|
124
|
+
displayOptions: {
|
125
|
+
show: {
|
126
|
+
operation: ['createPageWithContent', 'addContentToPage'],
|
127
|
+
},
|
128
|
+
},
|
129
|
+
default: '',
|
130
|
+
description: 'The content to add. Use natural language - AI will structure it into appropriate blocks (headings, paragraphs, lists, etc.)',
|
131
|
+
placeholder: 'Example:\n# Main Heading\nThis is a paragraph with **bold** and *italic* text.\n\n## Subheading\n- First bullet point\n- Second bullet point\n\n> This is a quote block',
|
132
|
+
},
|
133
|
+
// ADD CONTENT TO PAGE
|
134
|
+
{
|
135
|
+
displayName: 'Target Page ID',
|
136
|
+
name: 'targetPageId',
|
137
|
+
type: 'string',
|
138
|
+
required: true,
|
139
|
+
displayOptions: {
|
140
|
+
show: {
|
141
|
+
operation: ['addContentToPage', 'updatePageProperties'],
|
142
|
+
},
|
143
|
+
},
|
144
|
+
default: '',
|
145
|
+
description: 'ID or URL of the existing page to modify',
|
146
|
+
},
|
147
|
+
// SEARCH PAGES
|
148
|
+
{
|
149
|
+
displayName: 'Search Query',
|
150
|
+
name: 'searchQuery',
|
151
|
+
type: 'string',
|
152
|
+
displayOptions: {
|
153
|
+
show: {
|
154
|
+
operation: ['searchPages'],
|
155
|
+
},
|
156
|
+
},
|
157
|
+
default: '',
|
158
|
+
description: 'Search terms to find pages. Leave empty to get all pages.',
|
159
|
+
},
|
160
|
+
// UPDATE PAGE PROPERTIES
|
161
|
+
{
|
162
|
+
displayName: 'Properties to Update',
|
163
|
+
name: 'propertiesToUpdate',
|
164
|
+
type: 'string',
|
165
|
+
typeOptions: {
|
166
|
+
rows: 4,
|
167
|
+
},
|
168
|
+
displayOptions: {
|
169
|
+
show: {
|
170
|
+
operation: ['updatePageProperties'],
|
171
|
+
},
|
172
|
+
},
|
173
|
+
default: '',
|
174
|
+
description: 'Properties to update in JSON format or natural language. Example: {"status": "In Progress", "priority": "High"} or "Set status to Done and priority to Low"',
|
175
|
+
},
|
176
|
+
// DATABASE OPERATIONS
|
177
|
+
{
|
178
|
+
displayName: 'Database ID',
|
179
|
+
name: 'databaseId',
|
180
|
+
type: 'string',
|
181
|
+
required: true,
|
182
|
+
displayOptions: {
|
183
|
+
show: {
|
184
|
+
operation: ['queryDatabase'],
|
185
|
+
},
|
186
|
+
},
|
187
|
+
default: '',
|
188
|
+
description: 'ID or URL of the database to query',
|
189
|
+
},
|
190
|
+
{
|
191
|
+
displayName: 'Entry Properties',
|
192
|
+
name: 'entryProperties',
|
193
|
+
type: 'string',
|
194
|
+
typeOptions: {
|
195
|
+
rows: 4,
|
196
|
+
},
|
197
|
+
displayOptions: {
|
198
|
+
show: {
|
199
|
+
operation: ['createDatabaseEntry'],
|
200
|
+
},
|
201
|
+
},
|
202
|
+
default: '',
|
203
|
+
description: 'Properties for the new database entry in JSON format or natural language description',
|
204
|
+
},
|
205
|
+
{
|
206
|
+
displayName: 'Query Filter',
|
207
|
+
name: 'queryFilter',
|
208
|
+
type: 'string',
|
209
|
+
displayOptions: {
|
210
|
+
show: {
|
211
|
+
operation: ['queryDatabase'],
|
212
|
+
},
|
213
|
+
},
|
214
|
+
default: '',
|
215
|
+
description: 'Filter criteria in natural language (e.g., "status is Done and priority is High") or JSON format',
|
216
|
+
},
|
217
|
+
// COMMON OPTIONS
|
218
|
+
{
|
219
|
+
displayName: 'Additional Options',
|
220
|
+
name: 'additionalOptions',
|
221
|
+
type: 'collection',
|
222
|
+
placeholder: 'Add Option',
|
223
|
+
default: {},
|
224
|
+
options: [
|
225
|
+
{
|
226
|
+
displayName: 'Icon',
|
227
|
+
name: 'icon',
|
228
|
+
type: 'string',
|
229
|
+
default: '',
|
230
|
+
description: 'Emoji icon for the page (e.g., 📝, 🎯, 📊)',
|
231
|
+
},
|
232
|
+
{
|
233
|
+
displayName: 'Cover Image URL',
|
234
|
+
name: 'coverUrl',
|
235
|
+
type: 'string',
|
236
|
+
default: '',
|
237
|
+
description: 'URL of cover image for the page',
|
238
|
+
},
|
239
|
+
{
|
240
|
+
displayName: 'Max Results',
|
241
|
+
name: 'maxResults',
|
242
|
+
type: 'number',
|
243
|
+
default: 20,
|
244
|
+
description: 'Maximum number of results to return (1-100)',
|
245
|
+
},
|
246
|
+
],
|
247
|
+
},
|
248
|
+
],
|
249
|
+
};
|
250
|
+
}
|
251
|
+
async execute() {
|
252
|
+
const items = this.getInputData();
|
253
|
+
const responseData = [];
|
254
|
+
// Validate credentials
|
255
|
+
const isValid = await NotionUtils_1.validateCredentials.call(this);
|
256
|
+
if (!isValid) {
|
257
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid Notion API credentials');
|
258
|
+
}
|
259
|
+
for (let i = 0; i < items.length; i++) {
|
260
|
+
try {
|
261
|
+
const operation = this.getNodeParameter('operation', i);
|
262
|
+
let result;
|
263
|
+
switch (operation) {
|
264
|
+
case 'createPageWithContent':
|
265
|
+
result = await NotionAITool.createPageWithContent(this, i);
|
266
|
+
break;
|
267
|
+
case 'addContentToPage':
|
268
|
+
result = await NotionAITool.addContentToPage(this, i);
|
269
|
+
break;
|
270
|
+
case 'searchPages':
|
271
|
+
result = await NotionAITool.searchPages(this, i);
|
272
|
+
break;
|
273
|
+
case 'updatePageProperties':
|
274
|
+
result = await NotionAITool.updatePageProperties(this, i);
|
275
|
+
break;
|
276
|
+
case 'createDatabaseEntry':
|
277
|
+
result = await NotionAITool.createDatabaseEntry(this, i);
|
278
|
+
break;
|
279
|
+
case 'queryDatabase':
|
280
|
+
result = await NotionAITool.queryDatabase(this, i);
|
281
|
+
break;
|
282
|
+
default:
|
283
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
|
284
|
+
}
|
285
|
+
responseData.push({
|
286
|
+
operation,
|
287
|
+
success: true,
|
288
|
+
...result,
|
289
|
+
});
|
290
|
+
}
|
291
|
+
catch (error) {
|
292
|
+
if (this.continueOnFail()) {
|
293
|
+
responseData.push({
|
294
|
+
error: error.message,
|
295
|
+
success: false,
|
296
|
+
});
|
297
|
+
}
|
298
|
+
else {
|
299
|
+
throw error;
|
300
|
+
}
|
301
|
+
}
|
302
|
+
}
|
303
|
+
return [this.helpers.returnJsonArray(responseData)];
|
304
|
+
}
|
305
|
+
static async createPageWithContent(executeFunctions, itemIndex) {
|
306
|
+
const pageTitle = executeFunctions.getNodeParameter('pageTitle', itemIndex);
|
307
|
+
const parentId = executeFunctions.getNodeParameter('parentId', itemIndex);
|
308
|
+
const content = executeFunctions.getNodeParameter('content', itemIndex, '');
|
309
|
+
const additionalOptions = executeFunctions.getNodeParameter('additionalOptions', itemIndex, {});
|
310
|
+
const resolvedParentId = await NotionUtils_1.resolvePageId.call(executeFunctions, parentId);
|
311
|
+
// Create the page first
|
312
|
+
const pageBody = {
|
313
|
+
parent: { page_id: resolvedParentId },
|
314
|
+
properties: {
|
315
|
+
title: {
|
316
|
+
title: [(0, NotionUtils_1.createRichText)(pageTitle)],
|
317
|
+
},
|
318
|
+
},
|
319
|
+
};
|
320
|
+
// Add icon and cover if provided
|
321
|
+
if (additionalOptions.icon) {
|
322
|
+
pageBody.icon = { type: 'emoji', emoji: additionalOptions.icon };
|
323
|
+
}
|
324
|
+
if (additionalOptions.coverUrl) {
|
325
|
+
pageBody.cover = { type: 'external', external: { url: additionalOptions.coverUrl } };
|
326
|
+
}
|
327
|
+
const page = await NotionUtils_1.notionApiRequest.call(executeFunctions, 'POST', '/pages', pageBody);
|
328
|
+
// If content is provided, add it to the page
|
329
|
+
if (content) {
|
330
|
+
const blocks = NotionAITool.parseContentToBlocks(content);
|
331
|
+
if (blocks.length > 0) {
|
332
|
+
await NotionUtils_1.notionApiRequest.call(executeFunctions, 'PATCH', `/blocks/${page.id}/children`, {
|
333
|
+
children: blocks,
|
334
|
+
});
|
335
|
+
}
|
336
|
+
}
|
337
|
+
return {
|
338
|
+
pageId: page.id,
|
339
|
+
title: pageTitle,
|
340
|
+
url: page.url,
|
341
|
+
message: `Created page "${pageTitle}" with content`,
|
342
|
+
};
|
343
|
+
}
|
344
|
+
static async addContentToPage(executeFunctions, itemIndex) {
|
345
|
+
const targetPageId = executeFunctions.getNodeParameter('targetPageId', itemIndex);
|
346
|
+
const content = executeFunctions.getNodeParameter('content', itemIndex);
|
347
|
+
const resolvedPageId = await NotionUtils_1.resolvePageId.call(executeFunctions, targetPageId);
|
348
|
+
const blocks = NotionAITool.parseContentToBlocks(content);
|
349
|
+
if (blocks.length === 0) {
|
350
|
+
throw new n8n_workflow_1.NodeOperationError(executeFunctions.getNode(), 'No valid content blocks found to add');
|
351
|
+
}
|
352
|
+
const result = await NotionUtils_1.notionApiRequest.call(executeFunctions, 'PATCH', `/blocks/${resolvedPageId}/children`, {
|
353
|
+
children: blocks,
|
354
|
+
});
|
355
|
+
return {
|
356
|
+
pageId: resolvedPageId,
|
357
|
+
blocksAdded: blocks.length,
|
358
|
+
message: `Added ${blocks.length} content blocks to page`,
|
359
|
+
result,
|
360
|
+
};
|
361
|
+
}
|
362
|
+
static async searchPages(executeFunctions, itemIndex) {
|
363
|
+
var _a, _b;
|
364
|
+
const searchQuery = executeFunctions.getNodeParameter('searchQuery', itemIndex, '');
|
365
|
+
const additionalOptions = executeFunctions.getNodeParameter('additionalOptions', itemIndex, {});
|
366
|
+
const maxResults = additionalOptions.maxResults || 20;
|
367
|
+
const body = {
|
368
|
+
page_size: Math.min(maxResults, 100),
|
369
|
+
};
|
370
|
+
if (searchQuery) {
|
371
|
+
body.query = searchQuery;
|
372
|
+
}
|
373
|
+
body.filter = {
|
374
|
+
property: 'object',
|
375
|
+
value: 'page',
|
376
|
+
};
|
377
|
+
const response = await NotionUtils_1.notionApiRequest.call(executeFunctions, 'POST', '/search', body);
|
378
|
+
return {
|
379
|
+
totalResults: ((_a = response.results) === null || _a === void 0 ? void 0 : _a.length) || 0,
|
380
|
+
pages: response.results || [],
|
381
|
+
message: `Found ${((_b = response.results) === null || _b === void 0 ? void 0 : _b.length) || 0} pages`,
|
382
|
+
};
|
383
|
+
}
|
384
|
+
static async updatePageProperties(executeFunctions, itemIndex) {
|
385
|
+
const targetPageId = executeFunctions.getNodeParameter('targetPageId', itemIndex);
|
386
|
+
const propertiesToUpdate = executeFunctions.getNodeParameter('propertiesToUpdate', itemIndex);
|
387
|
+
const resolvedPageId = await NotionUtils_1.resolvePageId.call(executeFunctions, targetPageId);
|
388
|
+
const properties = NotionAITool.parsePropertiesToUpdate(propertiesToUpdate);
|
389
|
+
const result = await NotionUtils_1.notionApiRequest.call(executeFunctions, 'PATCH', `/pages/${resolvedPageId}`, {
|
390
|
+
properties,
|
391
|
+
});
|
392
|
+
return {
|
393
|
+
pageId: resolvedPageId,
|
394
|
+
updatedProperties: Object.keys(properties),
|
395
|
+
message: `Updated ${Object.keys(properties).length} properties`,
|
396
|
+
result,
|
397
|
+
};
|
398
|
+
}
|
399
|
+
static async createDatabaseEntry(executeFunctions, itemIndex) {
|
400
|
+
const parentId = executeFunctions.getNodeParameter('parentId', itemIndex);
|
401
|
+
const entryProperties = executeFunctions.getNodeParameter('entryProperties', itemIndex);
|
402
|
+
const resolvedParentId = await NotionUtils_1.resolvePageId.call(executeFunctions, parentId);
|
403
|
+
const properties = NotionAITool.parsePropertiesToUpdate(entryProperties);
|
404
|
+
const result = await NotionUtils_1.notionApiRequest.call(executeFunctions, 'POST', '/pages', {
|
405
|
+
parent: { database_id: resolvedParentId },
|
406
|
+
properties,
|
407
|
+
});
|
408
|
+
return {
|
409
|
+
entryId: result.id,
|
410
|
+
databaseId: resolvedParentId,
|
411
|
+
message: 'Created new database entry',
|
412
|
+
result,
|
413
|
+
};
|
414
|
+
}
|
415
|
+
static async queryDatabase(executeFunctions, itemIndex) {
|
416
|
+
var _a, _b;
|
417
|
+
const databaseId = executeFunctions.getNodeParameter('databaseId', itemIndex);
|
418
|
+
const queryFilter = executeFunctions.getNodeParameter('queryFilter', itemIndex, '');
|
419
|
+
const additionalOptions = executeFunctions.getNodeParameter('additionalOptions', itemIndex, {});
|
420
|
+
const maxResults = additionalOptions.maxResults || 20;
|
421
|
+
const resolvedDatabaseId = await NotionUtils_1.resolvePageId.call(executeFunctions, databaseId);
|
422
|
+
const body = {
|
423
|
+
page_size: Math.min(maxResults, 100),
|
424
|
+
};
|
425
|
+
if (queryFilter) {
|
426
|
+
try {
|
427
|
+
body.filter = JSON.parse(queryFilter);
|
428
|
+
}
|
429
|
+
catch {
|
430
|
+
// If not JSON, create a simple text filter
|
431
|
+
body.filter = {
|
432
|
+
property: 'Name',
|
433
|
+
title: {
|
434
|
+
contains: queryFilter,
|
435
|
+
},
|
436
|
+
};
|
437
|
+
}
|
438
|
+
}
|
439
|
+
const response = await NotionUtils_1.notionApiRequest.call(executeFunctions, 'POST', `/databases/${resolvedDatabaseId}/query`, body);
|
440
|
+
return {
|
441
|
+
databaseId: resolvedDatabaseId,
|
442
|
+
totalResults: ((_a = response.results) === null || _a === void 0 ? void 0 : _a.length) || 0,
|
443
|
+
entries: response.results || [],
|
444
|
+
message: `Found ${((_b = response.results) === null || _b === void 0 ? void 0 : _b.length) || 0} database entries`,
|
445
|
+
};
|
446
|
+
}
|
447
|
+
static parseContentToBlocks(content) {
|
448
|
+
const blocks = [];
|
449
|
+
// Handle both actual newlines and escaped \n characters
|
450
|
+
const normalizedContent = content.replace(/\\n/g, '\n');
|
451
|
+
// First, process XML-like tags for reliable parsing
|
452
|
+
const processedContent = NotionAITool.processXmlTags(normalizedContent, blocks);
|
453
|
+
// Then process remaining content with traditional markdown patterns
|
454
|
+
const lines = processedContent.split('\n');
|
455
|
+
for (let i = 0; i < lines.length; i++) {
|
456
|
+
const line = lines[i];
|
457
|
+
const trimmedLine = line.trim();
|
458
|
+
// Skip completely empty lines and XML placeholders
|
459
|
+
if (!trimmedLine || trimmedLine.startsWith('__XML_BLOCK_'))
|
460
|
+
continue;
|
461
|
+
// Traditional markdown patterns (for backwards compatibility)
|
462
|
+
if (trimmedLine.startsWith('# ')) {
|
463
|
+
blocks.push({
|
464
|
+
type: 'heading_1',
|
465
|
+
heading_1: {
|
466
|
+
rich_text: [(0, NotionUtils_1.createRichText)(trimmedLine.substring(2).trim())],
|
467
|
+
},
|
468
|
+
});
|
469
|
+
}
|
470
|
+
else if (trimmedLine.startsWith('## ')) {
|
471
|
+
blocks.push({
|
472
|
+
type: 'heading_2',
|
473
|
+
heading_2: {
|
474
|
+
rich_text: [(0, NotionUtils_1.createRichText)(trimmedLine.substring(3).trim())],
|
475
|
+
},
|
476
|
+
});
|
477
|
+
}
|
478
|
+
else if (trimmedLine.startsWith('### ')) {
|
479
|
+
blocks.push({
|
480
|
+
type: 'heading_3',
|
481
|
+
heading_3: {
|
482
|
+
rich_text: [(0, NotionUtils_1.createRichText)(trimmedLine.substring(4).trim())],
|
483
|
+
},
|
484
|
+
});
|
485
|
+
}
|
486
|
+
else if (trimmedLine === '---' || trimmedLine === '***') {
|
487
|
+
blocks.push({
|
488
|
+
type: 'divider',
|
489
|
+
divider: {},
|
490
|
+
});
|
491
|
+
}
|
492
|
+
else if (trimmedLine.includes('[!') && trimmedLine.startsWith('>')) {
|
493
|
+
// Callout blocks: > [!info] content
|
494
|
+
const calloutMatch = trimmedLine.match(/^>\s*\[!(\w+)\]\s*(.*)/i);
|
495
|
+
if (calloutMatch) {
|
496
|
+
const [, calloutType, text] = calloutMatch;
|
497
|
+
const emoji = NotionAITool.getCalloutEmoji(calloutType.toLowerCase());
|
498
|
+
const color = NotionAITool.getCalloutColor(calloutType.toLowerCase());
|
499
|
+
blocks.push({
|
500
|
+
type: 'callout',
|
501
|
+
callout: {
|
502
|
+
rich_text: NotionAITool.parseBasicMarkdown(text),
|
503
|
+
icon: { type: 'emoji', emoji },
|
504
|
+
color: color,
|
505
|
+
},
|
506
|
+
});
|
507
|
+
}
|
508
|
+
else {
|
509
|
+
blocks.push({
|
510
|
+
type: 'quote',
|
511
|
+
quote: {
|
512
|
+
rich_text: NotionAITool.parseBasicMarkdown(trimmedLine.substring(1).trim()),
|
513
|
+
},
|
514
|
+
});
|
515
|
+
}
|
516
|
+
}
|
517
|
+
else if (trimmedLine.startsWith(' && trimmedLine.endsWith(')')) {
|
518
|
+
// Image: 
|
519
|
+
const match = trimmedLine.match(/^!\[(.*?)\]\((.*?)\)$/);
|
520
|
+
if (match) {
|
521
|
+
const [, altText, url] = match;
|
522
|
+
blocks.push({
|
523
|
+
type: 'image',
|
524
|
+
image: {
|
525
|
+
type: 'external',
|
526
|
+
external: { url },
|
527
|
+
caption: altText ? NotionAITool.parseBasicMarkdown(altText) : [],
|
528
|
+
},
|
529
|
+
});
|
530
|
+
}
|
531
|
+
}
|
532
|
+
else if (trimmedLine.startsWith('$$') && trimmedLine.endsWith('$$') && trimmedLine.length > 4) {
|
533
|
+
// Equation: $$equation$$
|
534
|
+
const equation = trimmedLine.substring(2, trimmedLine.length - 2).trim();
|
535
|
+
blocks.push({
|
536
|
+
type: 'equation',
|
537
|
+
equation: {
|
538
|
+
expression: equation,
|
539
|
+
},
|
540
|
+
});
|
541
|
+
}
|
542
|
+
else if ((trimmedLine.startsWith('http://') || trimmedLine.startsWith('https://')) && !trimmedLine.includes(' ')) {
|
543
|
+
// Check if it's a video URL for embed, otherwise bookmark
|
544
|
+
const videoPatterns = [
|
545
|
+
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)/i,
|
546
|
+
/(?:https?:\/\/)?(?:www\.)?(?:vimeo\.com\/)/i,
|
547
|
+
/(?:https?:\/\/)?(?:www\.)?(?:dailymotion\.com\/video\/)/i,
|
548
|
+
/(?:https?:\/\/)?(?:www\.)?(?:twitch\.tv\/)/i,
|
549
|
+
/(?:https?:\/\/)?(?:www\.)?(?:loom\.com\/share\/)/i,
|
550
|
+
/(?:https?:\/\/)?(?:www\.)?(?:figma\.com\/)/i,
|
551
|
+
/(?:https?:\/\/)?(?:www\.)?(?:miro\.com\/)/i,
|
552
|
+
/(?:https?:\/\/)?(?:codepen\.io\/)/i
|
553
|
+
];
|
554
|
+
const isEmbeddableUrl = videoPatterns.some(pattern => pattern.test(trimmedLine));
|
555
|
+
if (isEmbeddableUrl) {
|
556
|
+
blocks.push({
|
557
|
+
type: 'embed',
|
558
|
+
embed: {
|
559
|
+
url: trimmedLine,
|
560
|
+
},
|
561
|
+
});
|
562
|
+
}
|
563
|
+
else {
|
564
|
+
blocks.push({
|
565
|
+
type: 'bookmark',
|
566
|
+
bookmark: {
|
567
|
+
url: trimmedLine,
|
568
|
+
},
|
569
|
+
});
|
570
|
+
}
|
571
|
+
}
|
572
|
+
else if (trimmedLine.startsWith('- [') && (trimmedLine.includes('[ ]') || trimmedLine.includes('[x]') || trimmedLine.includes('[X]'))) {
|
573
|
+
// To-do list items: - [ ] or - [x] or - [X]
|
574
|
+
const isChecked = trimmedLine.includes('[x]') || trimmedLine.includes('[X]');
|
575
|
+
const text = trimmedLine.replace(/^-\s*\[[ xX]\]\s*/, '').trim();
|
576
|
+
blocks.push({
|
577
|
+
type: 'to_do',
|
578
|
+
to_do: {
|
579
|
+
rich_text: NotionAITool.parseBasicMarkdown(text),
|
580
|
+
checked: isChecked,
|
581
|
+
},
|
582
|
+
});
|
583
|
+
}
|
584
|
+
else if (trimmedLine.startsWith('- ') && !trimmedLine.startsWith('- [')) {
|
585
|
+
// Bullet list items: - item (but not todos)
|
586
|
+
const listText = trimmedLine.substring(2).trim();
|
587
|
+
blocks.push({
|
588
|
+
type: 'bulleted_list_item',
|
589
|
+
bulleted_list_item: {
|
590
|
+
rich_text: NotionAITool.parseBasicMarkdown(listText),
|
591
|
+
},
|
592
|
+
});
|
593
|
+
}
|
594
|
+
else if (/^\d+\.\s/.test(trimmedLine)) {
|
595
|
+
// Numbered list items: 1. item
|
596
|
+
const listText = trimmedLine.replace(/^\d+\.\s/, '').trim();
|
597
|
+
blocks.push({
|
598
|
+
type: 'numbered_list_item',
|
599
|
+
numbered_list_item: {
|
600
|
+
rich_text: NotionAITool.parseBasicMarkdown(listText),
|
601
|
+
},
|
602
|
+
});
|
603
|
+
}
|
604
|
+
else if (trimmedLine.startsWith('> ') && !trimmedLine.includes('[!')) {
|
605
|
+
// Quote block (but not callout)
|
606
|
+
blocks.push({
|
607
|
+
type: 'quote',
|
608
|
+
quote: {
|
609
|
+
rich_text: NotionAITool.parseBasicMarkdown(trimmedLine.substring(2).trim()),
|
610
|
+
},
|
611
|
+
});
|
612
|
+
}
|
613
|
+
else if (trimmedLine.startsWith('```')) {
|
614
|
+
// Handle code blocks
|
615
|
+
const language = trimmedLine.substring(3).trim() || 'plain text';
|
616
|
+
const codeLines = [];
|
617
|
+
i++; // Skip the opening ```
|
618
|
+
// Collect all code lines until closing ```
|
619
|
+
while (i < lines.length && !lines[i].trim().startsWith('```')) {
|
620
|
+
codeLines.push(lines[i]);
|
621
|
+
i++;
|
622
|
+
}
|
623
|
+
blocks.push({
|
624
|
+
type: 'code',
|
625
|
+
code: {
|
626
|
+
rich_text: [(0, NotionUtils_1.createRichText)(codeLines.join('\n'))],
|
627
|
+
language: language === 'plain text' ? 'plain_text' : language,
|
628
|
+
},
|
629
|
+
});
|
630
|
+
}
|
631
|
+
else {
|
632
|
+
// Regular paragraph - handle basic markdown formatting
|
633
|
+
const richText = NotionAITool.parseBasicMarkdown(trimmedLine);
|
634
|
+
blocks.push({
|
635
|
+
type: 'paragraph',
|
636
|
+
paragraph: {
|
637
|
+
rich_text: richText,
|
638
|
+
},
|
639
|
+
});
|
640
|
+
}
|
641
|
+
}
|
642
|
+
return blocks;
|
643
|
+
}
|
644
|
+
// New XML-like tag processing function
|
645
|
+
static processXmlTags(content, blocks) {
|
646
|
+
let processedContent = content;
|
647
|
+
let blockCounter = 0;
|
648
|
+
// Process XML-like tags in order of priority
|
649
|
+
const tagProcessors = [
|
650
|
+
// Callouts: <callout type="info">content</callout>
|
651
|
+
{
|
652
|
+
regex: /<callout\s*(?:type="([^"]*)")?\s*>(.*?)<\/callout>/gis,
|
653
|
+
processor: (match, type = 'info', content) => {
|
654
|
+
const emoji = NotionAITool.getCalloutEmoji(type.toLowerCase());
|
655
|
+
const color = NotionAITool.getCalloutColor(type.toLowerCase());
|
656
|
+
blocks.push({
|
657
|
+
type: 'callout',
|
658
|
+
callout: {
|
659
|
+
rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
|
660
|
+
icon: { type: 'emoji', emoji },
|
661
|
+
color: color,
|
662
|
+
},
|
663
|
+
});
|
664
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
665
|
+
}
|
666
|
+
},
|
667
|
+
// Code blocks: <code language="javascript">content</code>
|
668
|
+
{
|
669
|
+
regex: /<code\s*(?:language="([^"]*)")?\s*>(.*?)<\/code>/gis,
|
670
|
+
processor: (match, language = 'plain_text', content) => {
|
671
|
+
blocks.push({
|
672
|
+
type: 'code',
|
673
|
+
code: {
|
674
|
+
rich_text: [(0, NotionUtils_1.createRichText)(content.trim())],
|
675
|
+
language: language === 'plain text' ? 'plain_text' : language,
|
676
|
+
},
|
677
|
+
});
|
678
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
679
|
+
}
|
680
|
+
},
|
681
|
+
// Images: <image src="url" alt="description">caption</image>
|
682
|
+
{
|
683
|
+
regex: /<image\s+src="([^"]*)"(?:\s+alt="([^"]*)")?\s*>(.*?)<\/image>/gis,
|
684
|
+
processor: (match, src, alt = '', caption = '') => {
|
685
|
+
const captionText = caption.trim() || alt;
|
686
|
+
blocks.push({
|
687
|
+
type: 'image',
|
688
|
+
image: {
|
689
|
+
type: 'external',
|
690
|
+
external: { url: src },
|
691
|
+
caption: captionText ? NotionAITool.parseBasicMarkdown(captionText) : [],
|
692
|
+
},
|
693
|
+
});
|
694
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
695
|
+
}
|
696
|
+
},
|
697
|
+
// Self-closing images: <image src="url" alt="description"/>
|
698
|
+
{
|
699
|
+
regex: /<image\s+src="([^"]*)"(?:\s+alt="([^"]*)")?\s*\/>/gis,
|
700
|
+
processor: (match, src, alt = '') => {
|
701
|
+
blocks.push({
|
702
|
+
type: 'image',
|
703
|
+
image: {
|
704
|
+
type: 'external',
|
705
|
+
external: { url: src },
|
706
|
+
caption: alt ? NotionAITool.parseBasicMarkdown(alt) : [],
|
707
|
+
},
|
708
|
+
});
|
709
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
710
|
+
}
|
711
|
+
},
|
712
|
+
// Equations: <equation>E=mc^2</equation>
|
713
|
+
{
|
714
|
+
regex: /<equation>(.*?)<\/equation>/gis,
|
715
|
+
processor: (match, expression) => {
|
716
|
+
blocks.push({
|
717
|
+
type: 'equation',
|
718
|
+
equation: {
|
719
|
+
expression: expression.trim(),
|
720
|
+
},
|
721
|
+
});
|
722
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
723
|
+
}
|
724
|
+
},
|
725
|
+
// Embeds: <embed>url</embed>
|
726
|
+
{
|
727
|
+
regex: /<embed>(.*?)<\/embed>/gis,
|
728
|
+
processor: (match, url) => {
|
729
|
+
blocks.push({
|
730
|
+
type: 'embed',
|
731
|
+
embed: {
|
732
|
+
url: url.trim(),
|
733
|
+
},
|
734
|
+
});
|
735
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
736
|
+
}
|
737
|
+
},
|
738
|
+
// Bookmarks: <bookmark>url</bookmark>
|
739
|
+
{
|
740
|
+
regex: /<bookmark>(.*?)<\/bookmark>/gis,
|
741
|
+
processor: (match, url) => {
|
742
|
+
blocks.push({
|
743
|
+
type: 'bookmark',
|
744
|
+
bookmark: {
|
745
|
+
url: url.trim(),
|
746
|
+
},
|
747
|
+
});
|
748
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
749
|
+
}
|
750
|
+
},
|
751
|
+
// Toggles: <toggle>title</toggle>
|
752
|
+
{
|
753
|
+
regex: /<toggle>(.*?)<\/toggle>/gis,
|
754
|
+
processor: (match, title) => {
|
755
|
+
blocks.push({
|
756
|
+
type: 'toggle',
|
757
|
+
toggle: {
|
758
|
+
rich_text: NotionAITool.parseBasicMarkdown(title.trim()),
|
759
|
+
children: [],
|
760
|
+
},
|
761
|
+
});
|
762
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
763
|
+
}
|
764
|
+
},
|
765
|
+
// Quotes: <quote>content</quote>
|
766
|
+
{
|
767
|
+
regex: /<quote>(.*?)<\/quote>/gis,
|
768
|
+
processor: (match, content) => {
|
769
|
+
blocks.push({
|
770
|
+
type: 'quote',
|
771
|
+
quote: {
|
772
|
+
rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
|
773
|
+
},
|
774
|
+
});
|
775
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
776
|
+
}
|
777
|
+
},
|
778
|
+
// Dividers: <divider/> or <divider></divider>
|
779
|
+
{
|
780
|
+
regex: /<divider\s*\/?>/gis,
|
781
|
+
processor: (match) => {
|
782
|
+
blocks.push({
|
783
|
+
type: 'divider',
|
784
|
+
divider: {},
|
785
|
+
});
|
786
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
787
|
+
}
|
788
|
+
},
|
789
|
+
// To-do items: <todo checked="true">content</todo>
|
790
|
+
{
|
791
|
+
regex: /<todo\s*(?:checked="([^"]*)")?\s*>(.*?)<\/todo>/gis,
|
792
|
+
processor: (match, checked = 'false', content) => {
|
793
|
+
const isChecked = checked.toLowerCase() === 'true';
|
794
|
+
blocks.push({
|
795
|
+
type: 'to_do',
|
796
|
+
to_do: {
|
797
|
+
rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
|
798
|
+
checked: isChecked,
|
799
|
+
},
|
800
|
+
});
|
801
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
802
|
+
}
|
803
|
+
},
|
804
|
+
// Headings: <h1>content</h1>, <h2>content</h2>, <h3>content</h3>
|
805
|
+
{
|
806
|
+
regex: /<h([123])>(.*?)<\/h[123]>/gis,
|
807
|
+
processor: (match, level, content) => {
|
808
|
+
const headingType = `heading_${level}`;
|
809
|
+
blocks.push({
|
810
|
+
type: headingType,
|
811
|
+
[headingType]: {
|
812
|
+
rich_text: [(0, NotionUtils_1.createRichText)(content.trim())],
|
813
|
+
},
|
814
|
+
});
|
815
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
816
|
+
}
|
817
|
+
},
|
818
|
+
// Paragraphs: <p>content</p>
|
819
|
+
{
|
820
|
+
regex: /<p>(.*?)<\/p>/gis,
|
821
|
+
processor: (match, content) => {
|
822
|
+
blocks.push({
|
823
|
+
type: 'paragraph',
|
824
|
+
paragraph: {
|
825
|
+
rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
|
826
|
+
},
|
827
|
+
});
|
828
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
829
|
+
}
|
830
|
+
},
|
831
|
+
// Process complete bulleted lists first: <ul><li>item</li></ul>
|
832
|
+
{
|
833
|
+
regex: /<ul\s*[^>]*>(.*?)<\/ul>/gis,
|
834
|
+
processor: (match, listContent) => {
|
835
|
+
// Extract individual list items and process them
|
836
|
+
const items = listContent.match(/<li\s*[^>]*>(.*?)<\/li>/gis) || [];
|
837
|
+
items.forEach(item => {
|
838
|
+
const itemContent = item.replace(/<\/?li[^>]*>/gi, '').trim();
|
839
|
+
if (itemContent) {
|
840
|
+
blocks.push({
|
841
|
+
type: 'bulleted_list_item',
|
842
|
+
bulleted_list_item: {
|
843
|
+
rich_text: NotionAITool.parseBasicMarkdown(itemContent),
|
844
|
+
},
|
845
|
+
});
|
846
|
+
}
|
847
|
+
});
|
848
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
849
|
+
}
|
850
|
+
},
|
851
|
+
// Process complete numbered lists first: <ol><li>item</li></ol>
|
852
|
+
{
|
853
|
+
regex: /<ol\s*[^>]*>(.*?)<\/ol>/gis,
|
854
|
+
processor: (match, listContent) => {
|
855
|
+
// Extract individual list items and process them
|
856
|
+
const items = listContent.match(/<li\s*[^>]*>(.*?)<\/li>/gis) || [];
|
857
|
+
items.forEach(item => {
|
858
|
+
const itemContent = item.replace(/<\/?li[^>]*>/gi, '').trim();
|
859
|
+
if (itemContent) {
|
860
|
+
blocks.push({
|
861
|
+
type: 'numbered_list_item',
|
862
|
+
numbered_list_item: {
|
863
|
+
rich_text: NotionAITool.parseBasicMarkdown(itemContent),
|
864
|
+
},
|
865
|
+
});
|
866
|
+
}
|
867
|
+
});
|
868
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
869
|
+
}
|
870
|
+
},
|
871
|
+
// Blockquotes: <blockquote>content</blockquote>
|
872
|
+
{
|
873
|
+
regex: /<blockquote>(.*?)<\/blockquote>/gis,
|
874
|
+
processor: (match, content) => {
|
875
|
+
blocks.push({
|
876
|
+
type: 'quote',
|
877
|
+
quote: {
|
878
|
+
rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
|
879
|
+
},
|
880
|
+
});
|
881
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
882
|
+
}
|
883
|
+
},
|
884
|
+
// Preformatted text: <pre>content</pre>
|
885
|
+
{
|
886
|
+
regex: /<pre>(.*?)<\/pre>/gis,
|
887
|
+
processor: (match, content) => {
|
888
|
+
blocks.push({
|
889
|
+
type: 'code',
|
890
|
+
code: {
|
891
|
+
rich_text: [(0, NotionUtils_1.createRichText)(content.trim())],
|
892
|
+
language: 'plain_text',
|
893
|
+
},
|
894
|
+
});
|
895
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
896
|
+
}
|
897
|
+
},
|
898
|
+
// Standalone list items (only if not already processed in lists): <li>content</li>
|
899
|
+
{
|
900
|
+
regex: /<li\s*[^>]*>(.*?)<\/li>/gis,
|
901
|
+
processor: (match, content) => {
|
902
|
+
if (content.trim()) {
|
903
|
+
blocks.push({
|
904
|
+
type: 'bulleted_list_item',
|
905
|
+
bulleted_list_item: {
|
906
|
+
rich_text: NotionAITool.parseBasicMarkdown(content.trim()),
|
907
|
+
},
|
908
|
+
});
|
909
|
+
}
|
910
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
911
|
+
}
|
912
|
+
},
|
913
|
+
// Strong/Bold: <strong>content</strong> or <b>content</b> (only as standalone)
|
914
|
+
{
|
915
|
+
regex: /(?:^|>|\s)<(strong|b)>(.*?)<\/(strong|b)>(?=<|$|\s)/gis,
|
916
|
+
processor: (match, tag, content) => {
|
917
|
+
blocks.push({
|
918
|
+
type: 'paragraph',
|
919
|
+
paragraph: {
|
920
|
+
rich_text: NotionAITool.parseBasicMarkdown(`**${content.trim()}**`),
|
921
|
+
},
|
922
|
+
});
|
923
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
924
|
+
}
|
925
|
+
},
|
926
|
+
// Emphasis/Italic: <em>content</em> or <i>content</i> (only as standalone)
|
927
|
+
{
|
928
|
+
regex: /(?:^|>|\s)<(em|i)>(.*?)<\/(em|i)>(?=<|$|\s)/gis,
|
929
|
+
processor: (match, tag, content) => {
|
930
|
+
blocks.push({
|
931
|
+
type: 'paragraph',
|
932
|
+
paragraph: {
|
933
|
+
rich_text: NotionAITool.parseBasicMarkdown(`*${content.trim()}*`),
|
934
|
+
},
|
935
|
+
});
|
936
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
937
|
+
}
|
938
|
+
},
|
939
|
+
// Line breaks: <br/> or <br>
|
940
|
+
{
|
941
|
+
regex: /<br\s*\/?>/gis,
|
942
|
+
processor: (match) => {
|
943
|
+
blocks.push({
|
944
|
+
type: 'paragraph',
|
945
|
+
paragraph: {
|
946
|
+
rich_text: [(0, NotionUtils_1.createRichText)('')],
|
947
|
+
},
|
948
|
+
});
|
949
|
+
return `__XML_BLOCK_${blockCounter++}__`;
|
950
|
+
}
|
951
|
+
},
|
952
|
+
];
|
953
|
+
// Process each tag type
|
954
|
+
tagProcessors.forEach(({ regex, processor }) => {
|
955
|
+
processedContent = processedContent.replace(regex, (match, group1, group2, group3) => {
|
956
|
+
return processor(match, group1 || '', group2 || '', group3 || '');
|
957
|
+
});
|
958
|
+
});
|
959
|
+
// Clean up any remaining HTML tags that weren't processed
|
960
|
+
processedContent = NotionAITool.cleanupRemainingHtml(processedContent);
|
961
|
+
return processedContent;
|
962
|
+
}
|
963
|
+
// Cleanup function to remove remaining HTML tags
|
964
|
+
static cleanupRemainingHtml(content) {
|
965
|
+
let cleaned = content;
|
966
|
+
// Remove common HTML tags that might be left behind
|
967
|
+
const htmlTagsToRemove = [
|
968
|
+
/<\/?ul\s*[^>]*>/gi,
|
969
|
+
/<\/?ol\s*[^>]*>/gi,
|
970
|
+
/<\/?li\s*[^>]*>/gi,
|
971
|
+
/<\/?strong\s*[^>]*>/gi,
|
972
|
+
/<\/?b\s*[^>]*>/gi,
|
973
|
+
/<\/?em\s*[^>]*>/gi,
|
974
|
+
/<\/?i\s*[^>]*>/gi,
|
975
|
+
/<\/?div\s*[^>]*>/gi,
|
976
|
+
/<\/?span\s*[^>]*>/gi,
|
977
|
+
/<br\s*\/?>/gi,
|
978
|
+
];
|
979
|
+
htmlTagsToRemove.forEach(regex => {
|
980
|
+
cleaned = cleaned.replace(regex, '');
|
981
|
+
});
|
982
|
+
// Remove empty lines created by tag removal
|
983
|
+
cleaned = cleaned.replace(/^\s*[\r\n]/gm, '');
|
984
|
+
// Remove multiple consecutive line breaks
|
985
|
+
cleaned = cleaned.replace(/\n{3,}/g, '\n\n');
|
986
|
+
return cleaned.trim();
|
987
|
+
}
|
988
|
+
// Helper function to get callout emoji based on type
|
989
|
+
static getCalloutEmoji(type) {
|
990
|
+
const emojiMap = {
|
991
|
+
'info': 'ℹ️',
|
992
|
+
'warning': '⚠️',
|
993
|
+
'danger': '🚨',
|
994
|
+
'error': '❌',
|
995
|
+
'note': '📝',
|
996
|
+
'tip': '💡',
|
997
|
+
'success': '✅',
|
998
|
+
'question': '❓',
|
999
|
+
};
|
1000
|
+
return emojiMap[type] || 'ℹ️';
|
1001
|
+
}
|
1002
|
+
// Helper function to get callout color based on type
|
1003
|
+
static getCalloutColor(type) {
|
1004
|
+
const colorMap = {
|
1005
|
+
'info': 'blue',
|
1006
|
+
'warning': 'yellow',
|
1007
|
+
'danger': 'red',
|
1008
|
+
'error': 'red',
|
1009
|
+
'note': 'gray',
|
1010
|
+
'tip': 'green',
|
1011
|
+
'success': 'green',
|
1012
|
+
'question': 'purple',
|
1013
|
+
};
|
1014
|
+
return colorMap[type] || 'gray';
|
1015
|
+
}
|
1016
|
+
// Helper function to parse basic markdown formatting in text
|
1017
|
+
static parseBasicMarkdown(text) {
|
1018
|
+
const richTextObjects = [];
|
1019
|
+
// Find and collect all formatting patterns with their positions
|
1020
|
+
const patterns = [
|
1021
|
+
{ regex: /\[([^\]]+)\]\(([^)]+)\)/g, type: 'link' }, // [text](url)
|
1022
|
+
{ regex: /\*\*\*([^*]+)\*\*\*/g, type: 'bold_italic' }, // ***bold italic***
|
1023
|
+
{ regex: /\*\*([^*]+)\*\*/g, type: 'bold' }, // **bold**
|
1024
|
+
{ regex: /\*([^*]+)\*/g, type: 'italic' }, // *italic*
|
1025
|
+
{ regex: /~~([^~]+)~~/g, type: 'strikethrough' }, // ~~strikethrough~~
|
1026
|
+
{ regex: /`([^`]+)`/g, type: 'code' }, // `code`
|
1027
|
+
];
|
1028
|
+
const matches = [];
|
1029
|
+
// Collect all matches
|
1030
|
+
patterns.forEach(pattern => {
|
1031
|
+
const regex = new RegExp(pattern.regex.source, 'g');
|
1032
|
+
let match;
|
1033
|
+
while ((match = regex.exec(text)) !== null) {
|
1034
|
+
if (pattern.type === 'link') {
|
1035
|
+
matches.push({
|
1036
|
+
start: match.index,
|
1037
|
+
end: match.index + match[0].length,
|
1038
|
+
text: match[1], // Link text
|
1039
|
+
type: pattern.type,
|
1040
|
+
url: match[2] // Link URL
|
1041
|
+
});
|
1042
|
+
}
|
1043
|
+
else {
|
1044
|
+
matches.push({
|
1045
|
+
start: match.index,
|
1046
|
+
end: match.index + match[0].length,
|
1047
|
+
text: match[1], // Inner text
|
1048
|
+
type: pattern.type
|
1049
|
+
});
|
1050
|
+
}
|
1051
|
+
}
|
1052
|
+
});
|
1053
|
+
// Sort matches by position and resolve overlaps (prefer longer matches)
|
1054
|
+
matches.sort((a, b) => {
|
1055
|
+
if (a.start !== b.start)
|
1056
|
+
return a.start - b.start;
|
1057
|
+
return (b.end - b.start) - (a.end - a.start); // Prefer longer matches
|
1058
|
+
});
|
1059
|
+
// Remove overlapping matches (keep the first/longer one)
|
1060
|
+
const resolvedMatches = [];
|
1061
|
+
for (const match of matches) {
|
1062
|
+
const hasOverlap = resolvedMatches.some(existing => (match.start < existing.end && match.end > existing.start));
|
1063
|
+
if (!hasOverlap) {
|
1064
|
+
resolvedMatches.push(match);
|
1065
|
+
}
|
1066
|
+
}
|
1067
|
+
// Sort again by position
|
1068
|
+
resolvedMatches.sort((a, b) => a.start - b.start);
|
1069
|
+
// If no formatting found, return simple rich text
|
1070
|
+
if (resolvedMatches.length === 0) {
|
1071
|
+
return [(0, NotionUtils_1.createRichText)(text)];
|
1072
|
+
}
|
1073
|
+
// Build rich text segments
|
1074
|
+
let lastIndex = 0;
|
1075
|
+
resolvedMatches.forEach(match => {
|
1076
|
+
// Add plain text before this match
|
1077
|
+
if (match.start > lastIndex) {
|
1078
|
+
const plainText = text.substring(lastIndex, match.start);
|
1079
|
+
if (plainText) {
|
1080
|
+
richTextObjects.push((0, NotionUtils_1.createRichText)(plainText));
|
1081
|
+
}
|
1082
|
+
}
|
1083
|
+
// Add formatted text
|
1084
|
+
const richTextObj = {
|
1085
|
+
type: 'text',
|
1086
|
+
text: { content: match.text },
|
1087
|
+
annotations: {}
|
1088
|
+
};
|
1089
|
+
// Apply formatting based on type
|
1090
|
+
switch (match.type) {
|
1091
|
+
case 'bold':
|
1092
|
+
richTextObj.annotations.bold = true;
|
1093
|
+
break;
|
1094
|
+
case 'italic':
|
1095
|
+
richTextObj.annotations.italic = true;
|
1096
|
+
break;
|
1097
|
+
case 'bold_italic':
|
1098
|
+
richTextObj.annotations.bold = true;
|
1099
|
+
richTextObj.annotations.italic = true;
|
1100
|
+
break;
|
1101
|
+
case 'strikethrough':
|
1102
|
+
richTextObj.annotations.strikethrough = true;
|
1103
|
+
break;
|
1104
|
+
case 'code':
|
1105
|
+
richTextObj.annotations.code = true;
|
1106
|
+
break;
|
1107
|
+
case 'link':
|
1108
|
+
richTextObj.text.link = { url: match.url };
|
1109
|
+
break;
|
1110
|
+
}
|
1111
|
+
// Clean up empty annotations
|
1112
|
+
if (Object.keys(richTextObj.annotations).length === 0) {
|
1113
|
+
delete richTextObj.annotations;
|
1114
|
+
}
|
1115
|
+
richTextObjects.push(richTextObj);
|
1116
|
+
lastIndex = match.end;
|
1117
|
+
});
|
1118
|
+
// Add remaining plain text
|
1119
|
+
if (lastIndex < text.length) {
|
1120
|
+
const remainingText = text.substring(lastIndex);
|
1121
|
+
if (remainingText) {
|
1122
|
+
richTextObjects.push((0, NotionUtils_1.createRichText)(remainingText));
|
1123
|
+
}
|
1124
|
+
}
|
1125
|
+
return richTextObjects.length > 0 ? richTextObjects : [(0, NotionUtils_1.createRichText)(text)];
|
1126
|
+
}
|
1127
|
+
static parsePropertiesToUpdate(propertiesString) {
|
1128
|
+
try {
|
1129
|
+
// Try to parse as JSON first
|
1130
|
+
return JSON.parse(propertiesString);
|
1131
|
+
}
|
1132
|
+
catch {
|
1133
|
+
// If not JSON, try to parse natural language
|
1134
|
+
const properties = {};
|
1135
|
+
// Simple natural language parsing
|
1136
|
+
const patterns = [
|
1137
|
+
/set\s+(\w+)\s+to\s+(.+?)(?:\s+and|$)/gi,
|
1138
|
+
/(\w+)\s*:\s*(.+?)(?:\s*,|$)/gi,
|
1139
|
+
/(\w+)\s*=\s*(.+?)(?:\s*,|$)/gi,
|
1140
|
+
];
|
1141
|
+
for (const pattern of patterns) {
|
1142
|
+
let match;
|
1143
|
+
while ((match = pattern.exec(propertiesString)) !== null) {
|
1144
|
+
const [, key, value] = match;
|
1145
|
+
properties[key.trim()] = {
|
1146
|
+
rich_text: [(0, NotionUtils_1.createRichText)(value.trim())],
|
1147
|
+
};
|
1148
|
+
}
|
1149
|
+
}
|
1150
|
+
return properties;
|
1151
|
+
}
|
1152
|
+
}
|
1153
|
+
}
|
1154
|
+
exports.NotionAITool = NotionAITool;
|