triliumnext-mcp 0.3.10-beta.1 → 0.3.10-beta.2
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/build/modules/attributeManager.js +36 -2
- package/build/modules/noteManager.js +17 -1
- package/build/modules/searchQueryBuilder.js +7 -0
- package/build/modules/toolDefinitions.js +6 -6
- package/build/utils/contentIntegrity.js +2 -8
- package/build/utils/contentRules.js +13 -21
- package/build/utils/templateMapper.js +113 -0
- package/build/utils/validationUtils.js +3 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import axios from 'axios';
|
|
6
6
|
import { logVerbose, logVerboseApi, logVerboseAxiosError } from "../utils/verboseUtils.js";
|
|
7
|
+
import { validateAndTranslateTemplate, createTemplateRelationError } from "../utils/templateMapper.js";
|
|
7
8
|
/**
|
|
8
9
|
* Manage note attributes with write operations (create, update, delete)
|
|
9
10
|
* This function provides write-only access to note attributes
|
|
@@ -49,12 +50,30 @@ async function create_single_attribute(noteId, attribute, axiosInstance) {
|
|
|
49
50
|
errors: validation.errors
|
|
50
51
|
};
|
|
51
52
|
}
|
|
53
|
+
// Translate template names to note IDs for template relations
|
|
54
|
+
let processedValue = attribute.value || "";
|
|
55
|
+
if (attribute.type === "relation" && attribute.name === "template" && attribute.value) {
|
|
56
|
+
try {
|
|
57
|
+
processedValue = validateAndTranslateTemplate(attribute.value);
|
|
58
|
+
logVerbose("create_single_attribute", `Translated template relation`, {
|
|
59
|
+
from: attribute.value,
|
|
60
|
+
to: processedValue
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
message: createTemplateRelationError(attribute.value),
|
|
67
|
+
errors: [error instanceof Error ? error.message : 'Template validation failed']
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
52
71
|
// Prepare attribute data for ETAPI
|
|
53
72
|
const attributeData = {
|
|
54
73
|
noteId: noteId,
|
|
55
74
|
type: attribute.type,
|
|
56
75
|
name: attribute.name,
|
|
57
|
-
value:
|
|
76
|
+
value: processedValue,
|
|
58
77
|
position: attribute.position || 10,
|
|
59
78
|
isInheritable: attribute.isInheritable || false
|
|
60
79
|
};
|
|
@@ -95,11 +114,26 @@ async function create_batch_attributes(noteId, attributes, axiosInstance) {
|
|
|
95
114
|
errors.push(`Validation failed for ${attribute.type} '${attribute.name}': ${validation.errors.join(', ')}`);
|
|
96
115
|
return null;
|
|
97
116
|
}
|
|
117
|
+
// Translate template names to note IDs for template relations
|
|
118
|
+
let processedValue = attribute.value || "";
|
|
119
|
+
if (attribute.type === "relation" && attribute.name === "template" && attribute.value) {
|
|
120
|
+
try {
|
|
121
|
+
processedValue = validateAndTranslateTemplate(attribute.value);
|
|
122
|
+
logVerbose("create_batch_attributes", `Translated template relation`, {
|
|
123
|
+
from: attribute.value,
|
|
124
|
+
to: processedValue
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
errors.push(createTemplateRelationError(attribute.value));
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
98
132
|
const attributeData = {
|
|
99
133
|
noteId: noteId,
|
|
100
134
|
type: attribute.type,
|
|
101
135
|
name: attribute.name,
|
|
102
|
-
value:
|
|
136
|
+
value: processedValue,
|
|
103
137
|
position: attribute.position || 10,
|
|
104
138
|
isInheritable: attribute.isInheritable || false
|
|
105
139
|
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { processContentArray } from '../utils/contentProcessor.js';
|
|
6
6
|
import { logVerbose, logVerboseError, logVerboseApi } from '../utils/verboseUtils.js';
|
|
7
7
|
import { getContentRequirements, validateContentForNoteType, extractTemplateRelation } from '../utils/contentRules.js';
|
|
8
|
+
import { validateAndTranslateTemplate, createTemplateRelationError } from '../utils/templateMapper.js';
|
|
8
9
|
/**
|
|
9
10
|
* Strip HTML tags from content for text notes
|
|
10
11
|
*/
|
|
@@ -209,11 +210,26 @@ export async function handleCreateNote(args, axiosInstance) {
|
|
|
209
210
|
*/
|
|
210
211
|
async function createNoteAttributes(noteId, attributes, axiosInstance) {
|
|
211
212
|
const attributePromises = attributes.map(async (attr) => {
|
|
213
|
+
// Translate template names to note IDs for template relations
|
|
214
|
+
let processedValue = attr.value || '';
|
|
215
|
+
if (attr.type === "relation" && attr.name === "template" && attr.value) {
|
|
216
|
+
try {
|
|
217
|
+
processedValue = validateAndTranslateTemplate(attr.value);
|
|
218
|
+
logVerbose("createNoteAttributes", `Translated template relation`, {
|
|
219
|
+
from: attr.value,
|
|
220
|
+
to: processedValue
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
logVerboseError("createNoteAttributes", error);
|
|
225
|
+
throw new Error(createTemplateRelationError(attr.value));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
212
228
|
const attributeData = {
|
|
213
229
|
noteId: noteId,
|
|
214
230
|
type: attr.type,
|
|
215
231
|
name: attr.name,
|
|
216
|
-
value:
|
|
232
|
+
value: processedValue,
|
|
217
233
|
position: attr.position || 10,
|
|
218
234
|
isInheritable: attr.isInheritable || false
|
|
219
235
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logVerboseInput, logVerboseOutput, logVerboseTransform } from "../utils/verboseUtils.js";
|
|
2
|
+
import { translateTemplateNameToId, isBuiltinTemplate } from "../utils/templateMapper.js";
|
|
2
3
|
export function buildSearchQuery(params) {
|
|
3
4
|
const queryParts = [];
|
|
4
5
|
// Verbose logging
|
|
@@ -191,6 +192,12 @@ function enhanceRelationProperty(name, op, value) {
|
|
|
191
192
|
if (op === 'exists' || op === 'not_exists' || !value) {
|
|
192
193
|
return name;
|
|
193
194
|
}
|
|
195
|
+
// Translate built-in template names to note IDs
|
|
196
|
+
if (isTemplateOrSystemRelation(name) && isBuiltinTemplate(value)) {
|
|
197
|
+
const translatedValue = translateTemplateNameToId(value);
|
|
198
|
+
logVerboseTransform("searchQueryBuilder", value, translatedValue, `Translated template name`);
|
|
199
|
+
return `${name}.noteId`;
|
|
200
|
+
}
|
|
194
201
|
// Auto-detect noteId patterns and apply appropriate suffix
|
|
195
202
|
if (isNoteIdPattern(value) && isTemplateOrSystemRelation(name)) {
|
|
196
203
|
return `${name}.noteId`;
|
|
@@ -15,7 +15,7 @@ export function createWriteTools() {
|
|
|
15
15
|
properties: {
|
|
16
16
|
parentNoteId: {
|
|
17
17
|
type: "string",
|
|
18
|
-
description: "ID of the parent note",
|
|
18
|
+
description: "ID of the parent note. ⚠️ IMPORTANT: When creating child notes for special note types, ensure proper requirements:\n• RENDER parents: child must be type='code', mime='text/html' (HTML content for rendering)\n• CALENDAR parents: child must have #startDate, #endDate, #startTime, #endTime labels\n• BOARD parents: child must have #status label (e.g., 'To Do', 'In Progress', 'Done')\n• Missing required labels/relations will cause child notes to not display properly",
|
|
19
19
|
default: "root"
|
|
20
20
|
},
|
|
21
21
|
title: {
|
|
@@ -25,11 +25,11 @@ export function createWriteTools() {
|
|
|
25
25
|
type: {
|
|
26
26
|
type: "string",
|
|
27
27
|
enum: ["text", "code", "render", "search", "relationMap", "book", "noteMap", "mermaid", "webView"],
|
|
28
|
-
description: "Type of note (aligned with TriliumNext ETAPI specification)",
|
|
28
|
+
description: "Type of note (aligned with TriliumNext ETAPI specification). ⚠️ SPECIAL TYPES REQUIRE CHILD NOTES:\n• RENDER: Creates empty container - requires child HTML code note with ~renderNote relation\n• CALENDAR (template): Creates empty container - requires child notes with date/time labels\n• BOARD (template): Creates empty container - requires child notes with #status labels\n• All other types can contain content directly",
|
|
29
29
|
},
|
|
30
30
|
content: {
|
|
31
31
|
type: "string",
|
|
32
|
-
description: "Content of the note (optional). Content requirements by note type: TEXT notes require HTML content (plain text auto-wrapped in <p> tags, e.g., '<p>Hello world</p>', '<strong>bold</strong>'); CODE/MERMAID notes require plain text ONLY (HTML tags rejected, e.g., 'def fibonacci(n):'); ⚠️ OMIT CONTENT for: 1) WEBVIEW notes (use #webViewSrc label instead), 2) Container templates (Board, Calendar, Grid View, List View, Table, Geo Map), 3) System notes: RENDER
|
|
32
|
+
description: "Content of the note (optional). Content requirements by note type: TEXT notes require HTML content (plain text auto-wrapped in <p> tags, e.g., '<p>Hello world</p>', '<strong>bold</strong>'); CODE/MERMAID notes require plain text ONLY (HTML tags rejected, e.g., 'def fibonacci(n):'); ⚠️ OMIT CONTENT for: 1) WEBVIEW notes (use #webViewSrc label instead), 2) Container templates (Board, Calendar, Grid View, List View, Table, Geo Map), 3) System notes: RENDER, SEARCH, RELATION_MAP, NOTE_MAP, BOOK - these must be EMPTY to work properly. When omitted, note will be created with empty content.\n\n📋 WORKFLOW FOR SPECIAL NOTE TYPES:\n• RENDER notes: Create empty → create child HTML code note → add ~renderNote relation to child\n• CALENDAR/BOARD/GRID/LIST/TABLE/GEO templates: Create empty with ~template relation → add child notes with proper labels\n• TEXT SNIPPET templates: Create with ~template relation + content\n• Most other types: Add content directly during creation"
|
|
33
33
|
},
|
|
34
34
|
mime: {
|
|
35
35
|
type: "string",
|
|
@@ -37,7 +37,7 @@ export function createWriteTools() {
|
|
|
37
37
|
},
|
|
38
38
|
attributes: {
|
|
39
39
|
type: "array",
|
|
40
|
-
description: "Optional attributes to create with the note (labels and relations). Enables one-step note creation with metadata.
|
|
40
|
+
description: "Optional attributes to create with the note (labels and relations). Enables one-step note creation with metadata. ⚠️ CRITICAL: Always add template relations during create_note when possible!\n\n📋 TEMPLATE RELATIONS (add during create_note):\n• ~template = 'Board' (kanban task boards)\n• ~template = 'Calendar' (calendar event displays)\n• ~template = 'Grid View' (grid layouts)\n• ~template = 'List View' (list layouts)\n• ~template = 'Table' (table structures)\n• ~template = 'Geo Map' (geographic maps)\n• ~template = 'Text Snippet' (reusable text)\n\n⚠️ SPECIAL CASES requiring separate steps:\n• ~renderNote = '<noteId>' (RENDER notes - requires existing child note ID)\n• Custom relations pointing to specific notes (use note IDs after creation)\n\n📋 LABELS & OTHER RELATIONS:\n• Labels: #tag format (e.g., #important, #project)\n• Relations: ~connection format (e.g., ~author, ~publisher)\n• Template relations use human-readable names (auto-translated to system IDs)\n\n⚠️ TEMPLATE RESTRICTIONS: Container templates MUST be empty notes - add content as child notes",
|
|
41
41
|
items: {
|
|
42
42
|
type: "object",
|
|
43
43
|
properties: {
|
|
@@ -332,7 +332,7 @@ export function createWriteAttributeTools() {
|
|
|
332
332
|
return [
|
|
333
333
|
{
|
|
334
334
|
name: "manage_attributes",
|
|
335
|
-
description: "Manage note attributes with write operations (create, update, delete). Create labels (#tags), template relations (~template), update existing attributes, and organize notes with metadata.
|
|
335
|
+
description: "Manage note attributes with write operations (create, update, delete). Create labels (#tags), template relations (~template), update existing attributes, and organize notes with metadata. ⚠️ PRIORITY: Use create_note with attributes parameter for template relations when possible - only use this tool for post-creation modifications or complex scenarios.\n\n✅ BEST PRACTICE: Most template relations (~template = 'Board', 'Calendar', etc.) should be added during create_note\n❌ USE THIS TOOL FOR: ~renderNote relations, custom note-to-note relations, post-creation label updates\n\nIMPORTANT: This tool only provides write access - use read_attributes to view existing attributes. Relations require values pointing to existing notes (e.g., template relations use human-readable names like 'Board', 'Calendar' which are automatically translated to system note IDs; author relations use target note titles or IDs). UPDATE LIMITATIONS: For labels, only value and position can be updated. For relations, only position can be updated. The isInheritable property cannot be changed via update - delete and recreate to modify inheritability. Supports single operations and efficient batch creation for better performance.",
|
|
336
336
|
inputSchema: {
|
|
337
337
|
type: "object",
|
|
338
338
|
properties: {
|
|
@@ -362,7 +362,7 @@ export function createWriteAttributeTools() {
|
|
|
362
362
|
},
|
|
363
363
|
value: {
|
|
364
364
|
type: "string",
|
|
365
|
-
description: "Attribute value: REQUIRED for relations (relations must point to existing notes - use template names like 'Board', 'Calendar', 'Text Snippet' or target note IDs/titles like 'Tolkien' or 'abc123def'), optional for labels (e.g., status labels like 'In Progress', priority labels like 'High'). Relations always need values since they connect notes together."
|
|
365
|
+
description: "Attribute value: REQUIRED for relations (relations must point to existing notes - use human-readable template names like 'Board', 'Calendar', 'Text Snippet' which are automatically translated to system note IDs, or use target note IDs/titles like 'Tolkien' or 'abc123def' for custom relations), optional for labels (e.g., status labels like 'In Progress', priority labels like 'High'). Relations always need values since they connect notes together."
|
|
366
366
|
},
|
|
367
367
|
position: {
|
|
368
368
|
type: "number",
|
|
@@ -96,14 +96,8 @@ export async function validateContentForUpdate(rawContent, type) {
|
|
|
96
96
|
break;
|
|
97
97
|
case 'code':
|
|
98
98
|
case 'mermaid':
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
isValid: false,
|
|
103
|
-
processedContent: contentString,
|
|
104
|
-
error: `${type} notes require plain text only, but HTML content was detected. Remove HTML tags and use plain text format.`
|
|
105
|
-
};
|
|
106
|
-
}
|
|
99
|
+
// Allow both plain text and HTML content for code/mermaid notes
|
|
100
|
+
// HTML validation removed to support more flexible content
|
|
107
101
|
break;
|
|
108
102
|
case 'render':
|
|
109
103
|
case 'search':
|
|
@@ -13,6 +13,8 @@ const CONTAINER_TEMPLATES = [
|
|
|
13
13
|
'Table', // Spreadsheet-like tables
|
|
14
14
|
'Geo Map' // Geographic maps
|
|
15
15
|
];
|
|
16
|
+
// Import template translation utilities
|
|
17
|
+
import { isBuiltinTemplate } from "./templateMapper.js";
|
|
16
18
|
/**
|
|
17
19
|
* Get content rules for a note type (without considering templates)
|
|
18
20
|
*/
|
|
@@ -34,25 +36,25 @@ export function getNoteTypeContentRules(noteType) {
|
|
|
34
36
|
requiresHtml: false,
|
|
35
37
|
description: "Content must be empty - render notes display HTML content from child notes via ~renderNote relation",
|
|
36
38
|
examples: [""],
|
|
37
|
-
errorMessage: "Render notes must be empty. Create a child code note with type='code' and mime='
|
|
39
|
+
errorMessage: "Render notes must be empty. Create a child code note with type='code' and mime='text/html' containing your HTML, then link it with ~renderNote='child-note-title' relation."
|
|
38
40
|
};
|
|
39
41
|
case 'code':
|
|
40
42
|
return {
|
|
41
43
|
allowContent: true,
|
|
42
44
|
enforceEmpty: false,
|
|
43
45
|
requiresHtml: false,
|
|
44
|
-
description: "Plain text
|
|
45
|
-
examples: ["def fibonacci(n):", "console.log('hello');"],
|
|
46
|
-
errorMessage: "Code notes
|
|
46
|
+
description: "Plain text or HTML content allowed",
|
|
47
|
+
examples: ["def fibonacci(n):", "console.log('hello');", "<div>HTML code example</div>"],
|
|
48
|
+
errorMessage: "Code notes accept both plain text and HTML content."
|
|
47
49
|
};
|
|
48
50
|
case 'mermaid':
|
|
49
51
|
return {
|
|
50
52
|
allowContent: true,
|
|
51
53
|
enforceEmpty: false,
|
|
52
54
|
requiresHtml: false,
|
|
53
|
-
description: "Plain text
|
|
54
|
-
examples: ["graph TD; A-->B", "sequenceDiagram; A->B: Hello"],
|
|
55
|
-
errorMessage: "Mermaid notes
|
|
55
|
+
description: "Plain text or HTML content allowed (Mermaid diagram syntax)",
|
|
56
|
+
examples: ["graph TD; A-->B", "sequenceDiagram; A->B: Hello", "<div>HTML with Mermaid</div>"],
|
|
57
|
+
errorMessage: "Mermaid notes accept both plain text and HTML content."
|
|
56
58
|
};
|
|
57
59
|
case 'webView':
|
|
58
60
|
return {
|
|
@@ -132,7 +134,7 @@ export function getTemplateContentRules(noteType, templateRelation) {
|
|
|
132
134
|
? templateRelation
|
|
133
135
|
: templateRelation?.value;
|
|
134
136
|
// Check if this is a container template that must be empty (overrides base rules)
|
|
135
|
-
if (noteType === 'book' && templateValue && CONTAINER_TEMPLATES.includes(templateValue)) {
|
|
137
|
+
if (noteType === 'book' && templateValue && isBuiltinTemplate(templateValue) && CONTAINER_TEMPLATES.includes(templateValue)) {
|
|
136
138
|
return {
|
|
137
139
|
...baseRules,
|
|
138
140
|
allowContent: false,
|
|
@@ -231,9 +233,7 @@ export async function validateContentForNoteType(content, noteType, currentConte
|
|
|
231
233
|
// Type-specific validation
|
|
232
234
|
switch (noteType) {
|
|
233
235
|
case 'text':
|
|
234
|
-
|
|
235
|
-
case 'webView':
|
|
236
|
-
// HTML required for these types
|
|
236
|
+
// HTML required for text notes
|
|
237
237
|
if (rules.requiresHtml && !isLikelyHtml(textContent)) {
|
|
238
238
|
// Auto-wrap plain text in HTML
|
|
239
239
|
const wrappedContent = `<p>${textContent}</p>`;
|
|
@@ -246,16 +246,8 @@ export async function validateContentForNoteType(content, noteType, currentConte
|
|
|
246
246
|
break;
|
|
247
247
|
case 'code':
|
|
248
248
|
case 'mermaid':
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
valid: false,
|
|
253
|
-
content,
|
|
254
|
-
error: `${noteType} notes require plain text only, but HTML content was detected. ` +
|
|
255
|
-
`Remove HTML tags and use plain text format. ` +
|
|
256
|
-
`Expected format: ${rules.examples.join(', ')}`
|
|
257
|
-
};
|
|
258
|
-
}
|
|
249
|
+
// Allow both plain text and HTML content for code/mermaid notes
|
|
250
|
+
// HTML validation removed to support more flexible content
|
|
259
251
|
break;
|
|
260
252
|
}
|
|
261
253
|
return {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template name to note ID mapping system for TriliumNext MCP
|
|
3
|
+
*
|
|
4
|
+
* This module provides translation between human-readable template names
|
|
5
|
+
* and their corresponding system note IDs in TriliumNext.
|
|
6
|
+
*/
|
|
7
|
+
// Built-in template name to note ID mapping
|
|
8
|
+
export const TEMPLATE_NAME_TO_ID = {
|
|
9
|
+
'Grid View': '_template_grid_view',
|
|
10
|
+
'Calendar': '_template_calendar',
|
|
11
|
+
'Board': '_template_board',
|
|
12
|
+
'List View': '_template_list_view',
|
|
13
|
+
'Table': '_template_table',
|
|
14
|
+
'Geo Map': '_template_geo_map',
|
|
15
|
+
'Text Snippet': '_template_text_snippet'
|
|
16
|
+
};
|
|
17
|
+
// Reverse mapping for ID to name resolution
|
|
18
|
+
export const TEMPLATE_ID_TO_NAME = Object.fromEntries(Object.entries(TEMPLATE_NAME_TO_ID).map(([name, id]) => [id, name]));
|
|
19
|
+
// List of all built-in template names for validation
|
|
20
|
+
export const BUILTIN_TEMPLATE_NAMES = Object.keys(TEMPLATE_NAME_TO_ID);
|
|
21
|
+
// List of all built-in template IDs for validation
|
|
22
|
+
export const BUILTIN_TEMPLATE_IDS = Object.values(TEMPLATE_NAME_TO_ID);
|
|
23
|
+
/**
|
|
24
|
+
* Translates a human-readable template name to its system note ID
|
|
25
|
+
* @param templateName - Human-readable template name (e.g., "Grid View")
|
|
26
|
+
* @returns System note ID (e.g., "_template_grid_view")
|
|
27
|
+
*/
|
|
28
|
+
export function translateTemplateNameToId(templateName) {
|
|
29
|
+
return TEMPLATE_NAME_TO_ID[templateName] || templateName;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Translates a system note ID back to human-readable template name
|
|
33
|
+
* @param templateId - System note ID (e.g., "_template_grid_view")
|
|
34
|
+
* @returns Human-readable template name (e.g., "Grid View")
|
|
35
|
+
*/
|
|
36
|
+
export function translateTemplateIdToName(templateId) {
|
|
37
|
+
return TEMPLATE_ID_TO_NAME[templateId] || templateId;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Checks if a template name is a built-in template
|
|
41
|
+
* @param templateName - Template name to check
|
|
42
|
+
* @returns True if it's a built-in template
|
|
43
|
+
*/
|
|
44
|
+
export function isBuiltinTemplate(templateName) {
|
|
45
|
+
return templateName in TEMPLATE_NAME_TO_ID;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a template ID is a built-in template ID
|
|
49
|
+
* @param templateId - Template ID to check
|
|
50
|
+
* @returns True if it's a built-in template ID
|
|
51
|
+
*/
|
|
52
|
+
export function isBuiltinTemplateId(templateId) {
|
|
53
|
+
return templateId in TEMPLATE_ID_TO_NAME;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validates and translates a template name for API use
|
|
57
|
+
* @param templateName - Template name to validate and translate
|
|
58
|
+
* @returns Translated template ID ready for API calls
|
|
59
|
+
* @throws Error if template name is invalid
|
|
60
|
+
*/
|
|
61
|
+
export function validateAndTranslateTemplate(templateName) {
|
|
62
|
+
if (typeof templateName !== 'string' || templateName.trim() === '') {
|
|
63
|
+
throw new Error('Template name cannot be empty');
|
|
64
|
+
}
|
|
65
|
+
// Check if it's a built-in template
|
|
66
|
+
if (isBuiltinTemplate(templateName)) {
|
|
67
|
+
return translateTemplateNameToId(templateName);
|
|
68
|
+
}
|
|
69
|
+
// Check if it's already a valid note ID pattern
|
|
70
|
+
if (isNoteIdPattern(templateName)) {
|
|
71
|
+
return templateName;
|
|
72
|
+
}
|
|
73
|
+
// Not a built-in template and not a note ID - show helpful error
|
|
74
|
+
throw new Error(`Invalid template: "${templateName}". Template relations must link to a note ID. ` +
|
|
75
|
+
`For built-in templates, use one of: ${BUILTIN_TEMPLATE_NAMES.join(', ')}. ` +
|
|
76
|
+
`For custom templates, use the note ID (e.g., "abc123"). ` +
|
|
77
|
+
`~template relations should always reference note IDs, not human-readable names.`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Checks if a string matches Trilium note ID patterns
|
|
81
|
+
* @param value - String to check
|
|
82
|
+
* @returns True if it matches note ID patterns
|
|
83
|
+
*/
|
|
84
|
+
function isNoteIdPattern(value) {
|
|
85
|
+
// Trilium note IDs are typically alphanumeric with underscores, like "_template_grid_view" or "abc123def"
|
|
86
|
+
return /^[a-zA-Z0-9_]+$/.test(value) && value.length > 2;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Gets a list of available built-in templates for error messages
|
|
90
|
+
* @returns Formatted string listing available templates
|
|
91
|
+
*/
|
|
92
|
+
export function getAvailableTemplatesMessage() {
|
|
93
|
+
return `Available built-in templates: ${BUILTIN_TEMPLATE_NAMES.join(', ')}. ` +
|
|
94
|
+
`Custom templates should use their note ID (e.g., "abc123").`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Provides enhanced error message for template relation validation
|
|
98
|
+
* @param templateValue - The template value that failed validation
|
|
99
|
+
* @returns Detailed error message
|
|
100
|
+
*/
|
|
101
|
+
export function createTemplateRelationError(templateValue) {
|
|
102
|
+
if (typeof templateValue !== 'string' || templateValue.trim() === '') {
|
|
103
|
+
return 'Template relation value cannot be empty';
|
|
104
|
+
}
|
|
105
|
+
if (isBuiltinTemplate(templateValue)) {
|
|
106
|
+
// This shouldn't happen if validation is working correctly
|
|
107
|
+
return `Built-in template "${templateValue}" should have been translated to "${translateTemplateNameToId(templateValue)}"`;
|
|
108
|
+
}
|
|
109
|
+
if (isNoteIdPattern(templateValue)) {
|
|
110
|
+
return `Template note ID "${templateValue}" may not exist. Verify the note ID is correct.`;
|
|
111
|
+
}
|
|
112
|
+
return `Invalid template relation: "${templateValue}". ${getAvailableTemplatesMessage()}`;
|
|
113
|
+
}
|
|
@@ -148,7 +148,9 @@ export function validateOperator(op) {
|
|
|
148
148
|
export function validateTemplateRelation(templateName) {
|
|
149
149
|
const validTemplates = ['Calendar', 'Board', 'Text Snippet', 'Grid View', 'List View', 'Table', 'Geo Map'];
|
|
150
150
|
if (typeof templateName !== 'string' || !validTemplates.includes(templateName)) {
|
|
151
|
-
throw new Error(`Invalid template: ${templateName}. Must be one of: ${validTemplates.join(', ')}`
|
|
151
|
+
throw new Error(`Invalid template: ${templateName}. Must be one of: ${validTemplates.join(', ')}. ` +
|
|
152
|
+
`Template relations must link to note IDs. For built-in templates, use their note ID (e.g., "_template_grid_view"). ` +
|
|
153
|
+
`For custom templates, use the actual note ID.`);
|
|
152
154
|
}
|
|
153
155
|
return templateName;
|
|
154
156
|
}
|