@crypto512/jicon-mcp 1.0.3 → 1.1.0
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/README.md +40 -3
- package/TOOL_LIST.md +261 -140
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +2 -0
- package/dist/config/constants.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +2 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +4 -0
- package/dist/config/types.js.map +1 -1
- package/dist/confluence/client.d.ts +11 -2
- package/dist/confluence/client.d.ts.map +1 -1
- package/dist/confluence/client.js +29 -12
- package/dist/confluence/client.js.map +1 -1
- package/dist/confluence/tools.d.ts +2 -2
- package/dist/confluence/tools.d.ts.map +1 -1
- package/dist/confluence/tools.js +145 -59
- package/dist/confluence/tools.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/jira/client.d.ts.map +1 -1
- package/dist/jira/client.js +3 -13
- package/dist/jira/client.js.map +1 -1
- package/dist/jira/tools.d.ts.map +1 -1
- package/dist/jira/tools.js +9 -30
- package/dist/jira/tools.js.map +1 -1
- package/dist/permissions/tool-registry.d.ts +4 -2
- package/dist/permissions/tool-registry.d.ts.map +1 -1
- package/dist/permissions/tool-registry.js +14 -2
- package/dist/permissions/tool-registry.js.map +1 -1
- package/dist/tempo/formatters.d.ts.map +1 -1
- package/dist/tempo/formatters.js +1 -16
- package/dist/tempo/formatters.js.map +1 -1
- package/dist/tempo/tools.d.ts +2 -2
- package/dist/tempo/tools.d.ts.map +1 -1
- package/dist/tempo/tools.js +5 -13
- package/dist/tempo/tools.js.map +1 -1
- package/dist/utils/buffer-tools.d.ts +115 -63
- package/dist/utils/buffer-tools.d.ts.map +1 -1
- package/dist/utils/buffer-tools.js +385 -330
- package/dist/utils/buffer-tools.js.map +1 -1
- package/dist/utils/content-buffer.d.ts +24 -1
- package/dist/utils/content-buffer.d.ts.map +1 -1
- package/dist/utils/content-buffer.js +49 -39
- package/dist/utils/content-buffer.js.map +1 -1
- package/dist/utils/jicon-help.d.ts +5 -5
- package/dist/utils/jicon-help.d.ts.map +1 -1
- package/dist/utils/jicon-help.js +259 -536
- package/dist/utils/jicon-help.js.map +1 -1
- package/dist/utils/plantuml/client.d.ts +1 -1
- package/dist/utils/plantuml/client.d.ts.map +1 -1
- package/dist/utils/plantuml/client.js +34 -59
- package/dist/utils/plantuml/client.js.map +1 -1
- package/dist/utils/plantuml/docker-manager.d.ts +3 -1
- package/dist/utils/plantuml/docker-manager.d.ts.map +1 -1
- package/dist/utils/plantuml/docker-manager.js +6 -2
- package/dist/utils/plantuml/docker-manager.js.map +1 -1
- package/dist/utils/plantuml/include-expander.d.ts +32 -0
- package/dist/utils/plantuml/include-expander.d.ts.map +1 -0
- package/dist/utils/plantuml/include-expander.js +129 -0
- package/dist/utils/plantuml/include-expander.js.map +1 -0
- package/dist/utils/plantuml/index.d.ts +2 -1
- package/dist/utils/plantuml/index.d.ts.map +1 -1
- package/dist/utils/plantuml/index.js +3 -1
- package/dist/utils/plantuml/index.js.map +1 -1
- package/dist/utils/plantuml/service.d.ts +32 -8
- package/dist/utils/plantuml/service.d.ts.map +1 -1
- package/dist/utils/plantuml/service.js +108 -15
- package/dist/utils/plantuml/service.js.map +1 -1
- package/dist/utils/plantuml/tools.d.ts +4 -4
- package/dist/utils/plantuml/tools.d.ts.map +1 -1
- package/dist/utils/plantuml/tools.js +50 -5
- package/dist/utils/plantuml/tools.js.map +1 -1
- package/dist/utils/plantuml/types.d.ts +4 -4
- package/dist/utils/plantuml/validation-helper.d.ts +33 -0
- package/dist/utils/plantuml/validation-helper.d.ts.map +1 -0
- package/dist/utils/plantuml/validation-helper.js +97 -0
- package/dist/utils/plantuml/validation-helper.js.map +1 -0
- package/dist/utils/response-formatter.js +7 -7
- package/dist/utils/response-formatter.js.map +1 -1
- package/dist/utils/time-formatter.d.ts +10 -0
- package/dist/utils/time-formatter.d.ts.map +1 -0
- package/dist/utils/time-formatter.js +22 -0
- package/dist/utils/time-formatter.js.map +1 -0
- package/dist/utils/url-tools.d.ts.map +1 -1
- package/dist/utils/url-tools.js +11 -4
- package/dist/utils/url-tools.js.map +1 -1
- package/dist/utils/xhtml/error-locator.d.ts +70 -0
- package/dist/utils/xhtml/error-locator.d.ts.map +1 -0
- package/dist/utils/xhtml/error-locator.js +229 -0
- package/dist/utils/xhtml/error-locator.js.map +1 -0
- package/dist/utils/xhtml/index.d.ts +6 -3
- package/dist/utils/xhtml/index.d.ts.map +1 -1
- package/dist/utils/xhtml/index.js +7 -3
- package/dist/utils/xhtml/index.js.map +1 -1
- package/dist/utils/xhtml/operations.d.ts +54 -0
- package/dist/utils/xhtml/operations.d.ts.map +1 -1
- package/dist/utils/xhtml/operations.js +205 -0
- package/dist/utils/xhtml/operations.js.map +1 -1
- package/dist/utils/xhtml/plantuml.d.ts +1 -1
- package/dist/utils/xhtml/plantuml.d.ts.map +1 -1
- package/dist/utils/xhtml/plantuml.js +19 -30
- package/dist/utils/xhtml/plantuml.js.map +1 -1
- package/dist/utils/xhtml/serializer.d.ts +5 -0
- package/dist/utils/xhtml/serializer.d.ts.map +1 -1
- package/dist/utils/xhtml/serializer.js +18 -4
- package/dist/utils/xhtml/serializer.js.map +1 -1
- package/dist/utils/xhtml/types.d.ts +3 -3
- package/dist/utils/xhtml/types.d.ts.map +1 -1
- package/dist/utils/xhtml/validator.d.ts.map +1 -1
- package/dist/utils/xhtml/validator.js +87 -4
- package/dist/utils/xhtml/validator.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,8 +10,10 @@ import * as fs from "fs";
|
|
|
10
10
|
import * as path from "path";
|
|
11
11
|
import { contentBuffer } from "./content-buffer.js";
|
|
12
12
|
import { formatSuccess, formatError, getMaxOutputSize } from "./response-formatter.js";
|
|
13
|
-
import {
|
|
14
|
-
|
|
13
|
+
import { parseXhtml, serializeXhtml, validateXhtmlAsync, buildPlantUmlMacro, detectRawPlantUml,
|
|
14
|
+
// Element ID-based operations
|
|
15
|
+
parseStructure, insertById, appendToDocument, replaceById, removeById, } from "./xhtml/index.js";
|
|
16
|
+
import { validatePlantUmlWithFallback } from "./plantuml/index.js";
|
|
15
17
|
/**
|
|
16
18
|
* Format grep results as text output similar to grep CLI
|
|
17
19
|
*/
|
|
@@ -86,9 +88,14 @@ function isBinaryContentType(contentType) {
|
|
|
86
88
|
export function createBufferTools() {
|
|
87
89
|
return {
|
|
88
90
|
buffer_create: {
|
|
89
|
-
description: `Create a new buffer with initial content.
|
|
91
|
+
description: `Create a new buffer with initial content. Returns bufferId and structure (for XHTML).
|
|
92
|
+
|
|
93
|
+
For XHTML content, returns structure with element IDs for use with buffer_edit:
|
|
94
|
+
- Each element gets a unique ID (stable during session)
|
|
95
|
+
- Use buffer_edit with after/before/replace to modify by element ID`,
|
|
90
96
|
inputSchema: z.object({
|
|
91
97
|
content: z.string().describe("Initial content for the buffer"),
|
|
98
|
+
contentType: z.enum(["xhtml", "plain", "json"]).describe("Content type: 'xhtml' for Confluence, 'plain' for text, 'json' for data"),
|
|
92
99
|
metadata: z
|
|
93
100
|
.record(z.unknown())
|
|
94
101
|
.optional()
|
|
@@ -96,15 +103,65 @@ export function createBufferTools() {
|
|
|
96
103
|
}),
|
|
97
104
|
handler: async (args) => {
|
|
98
105
|
try {
|
|
99
|
-
const
|
|
106
|
+
const mergedMetadata = {
|
|
107
|
+
...args.metadata,
|
|
108
|
+
contentType: args.contentType,
|
|
109
|
+
};
|
|
110
|
+
let bufferId;
|
|
111
|
+
let structure;
|
|
112
|
+
let nextId;
|
|
113
|
+
if (args.contentType === "xhtml") {
|
|
114
|
+
// Check for common CDATA mistake: wrapping entire content in CDATA
|
|
115
|
+
const trimmedContent = args.content.trim();
|
|
116
|
+
if (trimmedContent.startsWith("<![CDATA[")) {
|
|
117
|
+
return formatError({
|
|
118
|
+
error: true,
|
|
119
|
+
message: "Content starts with CDATA wrapper. Remove <![CDATA[ and ]]> from the content parameter.",
|
|
120
|
+
statusCode: 400,
|
|
121
|
+
details: {
|
|
122
|
+
hint: "CDATA is only used inside <ac:plain-text-body> elements (for code blocks and PlantUML). Never wrap the entire content in CDATA.",
|
|
123
|
+
example: "Use: <h1>Title</h1><p>Text</p>\nNot: <![CDATA[<h1>Title</h1><p>Text</p>]]>",
|
|
124
|
+
helpTopic: 'Call help(topic="storage") for Confluence XHTML format guide.',
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Parse XHTML and build structure with element IDs
|
|
129
|
+
const parseResult = parseXhtml(args.content);
|
|
130
|
+
if (!parseResult.document) {
|
|
131
|
+
return formatError({
|
|
132
|
+
error: true,
|
|
133
|
+
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
134
|
+
statusCode: 400,
|
|
135
|
+
details: {
|
|
136
|
+
parseError: parseResult.error?.message,
|
|
137
|
+
line: parseResult.error?.line,
|
|
138
|
+
column: parseResult.error?.column,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Assign IDs and get structure
|
|
143
|
+
const structureResult = parseStructure(parseResult.document);
|
|
144
|
+
structure = structureResult.structure;
|
|
145
|
+
nextId = structureResult.nextId;
|
|
146
|
+
// Serialize back (now with data-jicon-id attributes)
|
|
147
|
+
const contentWithIds = serializeXhtml(parseResult.document);
|
|
148
|
+
bufferId = contentBuffer.storeWithStructure(contentWithIds, structure, nextId, mergedMetadata);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
bufferId = contentBuffer.store(args.content, mergedMetadata);
|
|
152
|
+
}
|
|
100
153
|
const info = contentBuffer.getInfo(bufferId);
|
|
101
154
|
return formatSuccess({
|
|
102
155
|
bufferId,
|
|
103
156
|
totalSize: info?.totalSize ?? args.content.length,
|
|
157
|
+
contentType: args.contentType,
|
|
104
158
|
createdAt: info ? new Date(info.createdAt).toISOString() : new Date().toISOString(),
|
|
105
159
|
expiresAt: info ? new Date(info.expiresAt).toISOString() : undefined,
|
|
106
|
-
metadata:
|
|
107
|
-
|
|
160
|
+
metadata: mergedMetadata,
|
|
161
|
+
...(structure && { structure, nextId }),
|
|
162
|
+
message: args.contentType === "xhtml"
|
|
163
|
+
? "Buffer created with element IDs. Use buffer_edit with after/before/replace to modify."
|
|
164
|
+
: "Buffer created.",
|
|
108
165
|
});
|
|
109
166
|
}
|
|
110
167
|
catch (error) {
|
|
@@ -343,81 +400,57 @@ Example: Find buffer for draft 12345 → look for metadata.resourceId === "12345
|
|
|
343
400
|
},
|
|
344
401
|
},
|
|
345
402
|
buffer_edit: {
|
|
346
|
-
description: `
|
|
403
|
+
description: `Edit buffer content. For XHTML buffers, use element IDs. For plain/json, use string replacement.
|
|
347
404
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.default(false)
|
|
357
|
-
.describe("Replace all occurrences (default: false, fails if not unique)"),
|
|
358
|
-
}),
|
|
359
|
-
handler: async (args) => {
|
|
360
|
-
try {
|
|
361
|
-
// Check if buffer contains XHTML content - reject to force use of buffer_edit_xhtml
|
|
362
|
-
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
363
|
-
if (bufferInfo?.metadata?.contentType === "xhtml") {
|
|
364
|
-
return formatError({
|
|
365
|
-
error: true,
|
|
366
|
-
message: "This buffer contains Confluence XHTML content. Use buffer_edit_xhtml instead for proper structure-aware editing.",
|
|
367
|
-
statusCode: 400,
|
|
368
|
-
details: {
|
|
369
|
-
hint: "For PlantUML diagrams, use: buffer_edit_xhtml(bufferId, operation='insert-plantuml', ...)",
|
|
370
|
-
contentType: "xhtml",
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
const result = contentBuffer.edit(args.bufferId, args.old_string, args.new_string, args.replace_all ?? false);
|
|
375
|
-
if (!result) {
|
|
376
|
-
return formatError({
|
|
377
|
-
error: true,
|
|
378
|
-
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
379
|
-
statusCode: 404,
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
// Check if it's an error result
|
|
383
|
-
if ("error" in result && result.error) {
|
|
384
|
-
return formatError({
|
|
385
|
-
error: true,
|
|
386
|
-
message: result.message,
|
|
387
|
-
statusCode: 400,
|
|
388
|
-
details: { occurrences: result.occurrences },
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
return formatSuccess(result);
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
return formatError(error instanceof Error ? error : new Error(String(error)));
|
|
395
|
-
}
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
buffer_edit_xhtml: {
|
|
399
|
-
description: `Structure-aware XHTML editing for Confluence storage format. Supports CSS-like selectors to target elements, with operations: insert, insert-plantuml, update, update-plantuml, remove, move, wrap.
|
|
405
|
+
XHTML editing (by element ID) - single operation:
|
|
406
|
+
- after: Insert content after element with this ID
|
|
407
|
+
- before: Insert content before element with this ID
|
|
408
|
+
- replace: Replace element with this ID
|
|
409
|
+
- append: Add content at document end
|
|
410
|
+
- remove: Remove element with this ID
|
|
411
|
+
- content: XHTML to insert/replace
|
|
412
|
+
- plantuml: PlantUML code (auto-wrapped in Confluence macro)
|
|
400
413
|
|
|
401
|
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
-
|
|
405
|
-
- Pseudo: ':nth-child(2)', ':first-child', ':last-child'
|
|
406
|
-
- Descendants: 'table tbody tr'
|
|
407
|
-
- Direct child: 'ul > li'
|
|
414
|
+
XHTML batch editing - multiple operations in one call:
|
|
415
|
+
- operations: Array of {after?, before?, replace?, append?, remove?, content?, plantuml?}
|
|
416
|
+
- Operations are executed sequentially; stops on first error
|
|
417
|
+
- Much more efficient than multiple tool calls (parse once, serialize once)
|
|
408
418
|
|
|
409
|
-
|
|
410
|
-
- 'after-title': After first h1 or h2
|
|
411
|
-
- 'after-heading': After first heading (h1-h6)
|
|
412
|
-
- 'before-content': At document start
|
|
413
|
-
- 'end': At document end
|
|
414
|
-
- 'after-toc': After table of contents macro
|
|
419
|
+
Example batch: operations=[{after:6, plantuml:"@startuml..."}, {after:8, plantuml:"@startuml..."}]
|
|
415
420
|
|
|
416
|
-
|
|
417
|
-
|
|
421
|
+
Plain/JSON editing (string replacement):
|
|
422
|
+
- old_string: Text to replace
|
|
423
|
+
- new_string: Replacement text
|
|
424
|
+
- replace_all: Replace all occurrences
|
|
425
|
+
|
|
426
|
+
Returns updated structure for XHTML buffers.`,
|
|
427
|
+
inputSchema: z.object({
|
|
428
|
+
bufferId: z.string().describe("Buffer ID to modify"),
|
|
429
|
+
// XHTML batch operations
|
|
430
|
+
operations: z.array(z.object({
|
|
431
|
+
after: z.number().optional().describe("Insert after element with this ID"),
|
|
432
|
+
before: z.number().optional().describe("Insert before element with this ID"),
|
|
433
|
+
replace: z.number().optional().describe("Replace element with this ID"),
|
|
434
|
+
append: z.boolean().optional().describe("Append content at document end"),
|
|
435
|
+
remove: z.number().optional().describe("Remove element with this ID"),
|
|
436
|
+
content: z.string().optional().describe("XHTML content to insert/replace"),
|
|
437
|
+
plantuml: z.string().optional().describe("PlantUML code (auto-wrapped in macro)"),
|
|
438
|
+
})).optional().describe("Array of XHTML operations to execute sequentially (more efficient than multiple calls)"),
|
|
439
|
+
// XHTML element ID operations (single operation - backwards compatible)
|
|
440
|
+
after: z.number().optional().describe("Insert after element with this ID"),
|
|
441
|
+
before: z.number().optional().describe("Insert before element with this ID"),
|
|
442
|
+
replace: z.number().optional().describe("Replace element with this ID"),
|
|
443
|
+
append: z.boolean().optional().describe("Append content at document end"),
|
|
444
|
+
remove: z.number().optional().describe("Remove element with this ID"),
|
|
445
|
+
content: z.string().optional().describe("XHTML content to insert/replace"),
|
|
446
|
+
plantuml: z.string().optional().describe("PlantUML code (auto-wrapped in macro)"),
|
|
447
|
+
// Plain text operations
|
|
448
|
+
old_string: z.string().optional().describe("Text to replace (plain/json only)"),
|
|
449
|
+
new_string: z.string().optional().describe("Replacement text (plain/json only)"),
|
|
450
|
+
replace_all: z.boolean().optional().default(false).describe("Replace all occurrences"),
|
|
451
|
+
}),
|
|
418
452
|
handler: async (args) => {
|
|
419
453
|
try {
|
|
420
|
-
// Get buffer content
|
|
421
454
|
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
422
455
|
if (!bufferInfo) {
|
|
423
456
|
return formatError({
|
|
@@ -426,326 +459,334 @@ PlantUML: Use plantuml_validate first for full Docker-based validation. This too
|
|
|
426
459
|
statusCode: 404,
|
|
427
460
|
});
|
|
428
461
|
}
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
const originalContent = chunk.chunk;
|
|
438
|
-
const oldSize = originalContent.length;
|
|
439
|
-
// Parse XHTML
|
|
440
|
-
const parseResult = parseXhtml(originalContent);
|
|
441
|
-
if (!parseResult.document) {
|
|
442
|
-
return formatError({
|
|
443
|
-
error: true,
|
|
444
|
-
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
445
|
-
statusCode: 400,
|
|
446
|
-
details: {
|
|
447
|
-
parseError: parseResult.error?.message,
|
|
448
|
-
line: parseResult.error?.line,
|
|
449
|
-
column: parseResult.error?.column,
|
|
450
|
-
context: parseResult.error?.context,
|
|
451
|
-
hint: parseResult.error?.context
|
|
452
|
-
? `Check near: "${parseResult.error.context.substring(0, 60)}${parseResult.error.context.length > 60 ? "..." : ""}"`
|
|
453
|
-
: undefined,
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
// Handle PlantUML operations specially
|
|
458
|
-
let contentToInsert = args.content;
|
|
459
|
-
const operation = args.operation;
|
|
460
|
-
let resolvedSelector = args.selector;
|
|
461
|
-
let resolvedPosition = args.position;
|
|
462
|
-
let insertDiagramType; // Track diagram type for insert-plantuml
|
|
463
|
-
// Check if we need to derive selector from semanticPosition
|
|
464
|
-
if (!resolvedSelector && args.semanticPosition) {
|
|
465
|
-
const resolved = resolveSemanticPosition(parseResult.document, args.semanticPosition);
|
|
466
|
-
if (resolved) {
|
|
467
|
-
resolvedSelector = resolved.selector;
|
|
468
|
-
resolvedPosition = resolved.position;
|
|
462
|
+
const isXhtml = bufferInfo.metadata?.contentType === "xhtml";
|
|
463
|
+
// XHTML editing with element IDs
|
|
464
|
+
if (isXhtml) {
|
|
465
|
+
let operations;
|
|
466
|
+
if (args.operations && args.operations.length > 0) {
|
|
467
|
+
// Batch mode: use operations array
|
|
468
|
+
operations = args.operations;
|
|
469
469
|
}
|
|
470
470
|
else {
|
|
471
|
+
// Single operation mode (backwards compatible)
|
|
472
|
+
const hasIdOperation = args.after !== undefined || args.before !== undefined ||
|
|
473
|
+
args.replace !== undefined || args.append || args.remove !== undefined;
|
|
474
|
+
if (!hasIdOperation) {
|
|
475
|
+
return formatError({
|
|
476
|
+
error: true,
|
|
477
|
+
message: "XHTML buffer requires element ID operation: after, before, replace, append, remove, or operations array",
|
|
478
|
+
statusCode: 400,
|
|
479
|
+
details: {
|
|
480
|
+
structure: bufferInfo.structure,
|
|
481
|
+
nextId: bufferInfo.nextId,
|
|
482
|
+
hint: "Use after=ID, before=ID, replace=ID, append=true, remove=ID, or operations=[...]",
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
operations = [{
|
|
487
|
+
after: args.after,
|
|
488
|
+
before: args.before,
|
|
489
|
+
replace: args.replace,
|
|
490
|
+
append: args.append,
|
|
491
|
+
remove: args.remove,
|
|
492
|
+
content: args.content,
|
|
493
|
+
plantuml: args.plantuml,
|
|
494
|
+
}];
|
|
495
|
+
}
|
|
496
|
+
// Get buffer content
|
|
497
|
+
const chunk = contentBuffer.getChunk(args.bufferId, 0, bufferInfo.totalSize);
|
|
498
|
+
if (!chunk) {
|
|
471
499
|
return formatError({
|
|
472
500
|
error: true,
|
|
473
|
-
message: `
|
|
474
|
-
statusCode:
|
|
475
|
-
details: {
|
|
476
|
-
semanticPosition: args.semanticPosition,
|
|
477
|
-
hint: "Document may be empty or missing required elements",
|
|
478
|
-
},
|
|
501
|
+
message: `Failed to read buffer: ${args.bufferId}`,
|
|
502
|
+
statusCode: 500,
|
|
479
503
|
});
|
|
480
504
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return formatError({
|
|
485
|
-
error: true,
|
|
486
|
-
message: "Either selector or semanticPosition is required",
|
|
487
|
-
statusCode: 400,
|
|
488
|
-
details: {
|
|
489
|
-
hint: "Provide selector (e.g., 'h1', 'table', 'ac:structured-macro[ac:name=\"plantuml\"]') or semanticPosition (e.g., 'after-title', 'end')",
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
if (operation === "insert-plantuml") {
|
|
494
|
-
if (!args.plantuml) {
|
|
505
|
+
// Parse XHTML once
|
|
506
|
+
const parseResult = parseXhtml(chunk.chunk);
|
|
507
|
+
if (!parseResult.document) {
|
|
495
508
|
return formatError({
|
|
496
509
|
error: true,
|
|
497
|
-
message:
|
|
510
|
+
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
498
511
|
statusCode: 400,
|
|
499
512
|
});
|
|
500
513
|
}
|
|
501
|
-
|
|
502
|
-
let
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
514
|
+
const oldSize = chunk.chunk.length;
|
|
515
|
+
let nextId = bufferInfo.nextId ?? 1;
|
|
516
|
+
const allInsertedIds = [];
|
|
517
|
+
const diagramTypes = [];
|
|
518
|
+
// Execute all operations sequentially
|
|
519
|
+
for (let opIndex = 0; opIndex < operations.length; opIndex++) {
|
|
520
|
+
const op = operations[opIndex];
|
|
521
|
+
// Build content to insert (handle plantuml specially)
|
|
522
|
+
let contentToInsert = op.content;
|
|
523
|
+
let diagramType;
|
|
524
|
+
if (op.plantuml) {
|
|
525
|
+
// Validate PlantUML
|
|
526
|
+
const validation = await validatePlantUmlWithFallback(op.plantuml, "insert-plantuml", {});
|
|
527
|
+
if (!validation.valid) {
|
|
528
|
+
// Return error with operation index for debugging
|
|
529
|
+
const errorResult = validation.error;
|
|
530
|
+
const errorData = JSON.parse(errorResult.content[0].text);
|
|
531
|
+
errorData.operationIndex = opIndex;
|
|
532
|
+
errorData.completedOperations = opIndex;
|
|
533
|
+
return {
|
|
534
|
+
content: [{ type: "text", text: JSON.stringify(errorData, null, 2) }],
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
contentToInsert = buildPlantUmlMacro(validation.normalizedCode);
|
|
538
|
+
diagramType = validation.diagramType;
|
|
539
|
+
if (diagramType)
|
|
540
|
+
diagramTypes.push(diagramType);
|
|
541
|
+
}
|
|
542
|
+
// Execute single operation
|
|
543
|
+
let result;
|
|
544
|
+
if (op.remove !== undefined) {
|
|
545
|
+
result = removeById(parseResult.document, op.remove);
|
|
546
|
+
}
|
|
547
|
+
else if (op.replace !== undefined) {
|
|
548
|
+
if (!contentToInsert) {
|
|
508
549
|
return formatError({
|
|
509
550
|
error: true,
|
|
510
|
-
message:
|
|
551
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for replace operation`,
|
|
511
552
|
statusCode: 400,
|
|
512
|
-
details: {
|
|
513
|
-
plantumlErrors: dockerValidation.errors,
|
|
514
|
-
diagramType: dockerValidation.diagramType,
|
|
515
|
-
hint: "Check plantuml_validate for detailed error info",
|
|
516
|
-
},
|
|
517
553
|
});
|
|
518
554
|
}
|
|
519
|
-
|
|
520
|
-
insertDiagramType = dockerValidation.diagramType;
|
|
555
|
+
result = replaceById(parseResult.document, op.replace, contentToInsert);
|
|
521
556
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
525
|
-
if (!basicValidation.valid) {
|
|
557
|
+
else if (op.append) {
|
|
558
|
+
if (!contentToInsert) {
|
|
526
559
|
return formatError({
|
|
527
560
|
error: true,
|
|
528
|
-
message:
|
|
561
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for append operation`,
|
|
529
562
|
statusCode: 400,
|
|
530
|
-
details: {
|
|
531
|
-
plantumlError: basicValidation.error,
|
|
532
|
-
},
|
|
533
563
|
});
|
|
534
564
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
// Use basic synchronous validation
|
|
541
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
542
|
-
if (!basicValidation.valid) {
|
|
543
|
-
return formatError({
|
|
544
|
-
error: true,
|
|
545
|
-
message: "PlantUML syntax error",
|
|
546
|
-
statusCode: 400,
|
|
547
|
-
details: {
|
|
548
|
-
plantumlError: basicValidation.error,
|
|
549
|
-
hint: "Use plantuml_validate for full Docker-based validation",
|
|
550
|
-
},
|
|
551
|
-
});
|
|
565
|
+
result = appendToDocument(parseResult.document, contentToInsert, nextId);
|
|
566
|
+
if (result.nextId)
|
|
567
|
+
nextId = result.nextId;
|
|
552
568
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
// Build the macro
|
|
557
|
-
contentToInsert = buildPlantUmlMacro(normalizedCode, args.macroId);
|
|
558
|
-
}
|
|
559
|
-
else if (operation === "update-plantuml") {
|
|
560
|
-
if (!args.plantuml) {
|
|
561
|
-
return formatError({
|
|
562
|
-
error: true,
|
|
563
|
-
message: "plantuml parameter is required for update-plantuml operation",
|
|
564
|
-
statusCode: 400,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
// Validate PlantUML - use Docker service if available, else fallback to basic validation
|
|
568
|
-
let normalizedCode;
|
|
569
|
-
let updateDiagramType;
|
|
570
|
-
if (isPlantUmlServiceAvailable()) {
|
|
571
|
-
// Use Docker-based validation
|
|
572
|
-
try {
|
|
573
|
-
const dockerValidation = await validatePlantUmlWithDocker(args.plantuml);
|
|
574
|
-
if (!dockerValidation.valid) {
|
|
569
|
+
else if (op.after !== undefined) {
|
|
570
|
+
if (!contentToInsert) {
|
|
575
571
|
return formatError({
|
|
576
572
|
error: true,
|
|
577
|
-
message:
|
|
573
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for after operation`,
|
|
578
574
|
statusCode: 400,
|
|
579
|
-
details: {
|
|
580
|
-
plantumlErrors: dockerValidation.errors,
|
|
581
|
-
diagramType: dockerValidation.diagramType,
|
|
582
|
-
hint: "Check plantuml_validate for detailed error info",
|
|
583
|
-
},
|
|
584
575
|
});
|
|
585
576
|
}
|
|
586
|
-
|
|
587
|
-
|
|
577
|
+
result = insertById(parseResult.document, op.after, "after", contentToInsert, nextId);
|
|
578
|
+
if (result.nextId)
|
|
579
|
+
nextId = result.nextId;
|
|
588
580
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
592
|
-
if (!basicValidation.valid) {
|
|
581
|
+
else if (op.before !== undefined) {
|
|
582
|
+
if (!contentToInsert) {
|
|
593
583
|
return formatError({
|
|
594
584
|
error: true,
|
|
595
|
-
message:
|
|
585
|
+
message: `Operation ${opIndex + 1}: content or plantuml required for before operation`,
|
|
596
586
|
statusCode: 400,
|
|
597
|
-
details: {
|
|
598
|
-
plantumlError: basicValidation.error,
|
|
599
|
-
},
|
|
600
587
|
});
|
|
601
588
|
}
|
|
602
|
-
|
|
603
|
-
|
|
589
|
+
result = insertById(parseResult.document, op.before, "before", contentToInsert, nextId);
|
|
590
|
+
if (result.nextId)
|
|
591
|
+
nextId = result.nextId;
|
|
604
592
|
}
|
|
605
|
-
|
|
606
|
-
else {
|
|
607
|
-
// Use basic synchronous validation
|
|
608
|
-
const basicValidation = validatePlantUml(args.plantuml);
|
|
609
|
-
if (!basicValidation.valid) {
|
|
593
|
+
else {
|
|
610
594
|
return formatError({
|
|
611
595
|
error: true,
|
|
612
|
-
message:
|
|
596
|
+
message: `Operation ${opIndex + 1}: No valid operation specified (need after, before, replace, append, or remove)`,
|
|
613
597
|
statusCode: 400,
|
|
614
|
-
details: {
|
|
615
|
-
plantumlError: basicValidation.error,
|
|
616
|
-
hint: "Use plantuml_validate for full Docker-based validation",
|
|
617
|
-
},
|
|
618
598
|
});
|
|
619
599
|
}
|
|
620
|
-
|
|
621
|
-
updateDiagramType = basicValidation.diagramType;
|
|
622
|
-
}
|
|
623
|
-
// Find the macro element and update it directly
|
|
624
|
-
const selectorResult = querySelector(parseResult.document, args.selector);
|
|
625
|
-
if (selectorResult.matches.length === 0) {
|
|
626
|
-
const unsupportedPseudo = findUnsupportedPseudo(args.selector);
|
|
627
|
-
const hint = unsupportedPseudo
|
|
628
|
-
? `Pseudo-selector ':${unsupportedPseudo}' is not supported. Use ':contains("text")' to find elements by text content, or use 'ac:structured-macro[ac:name="plantuml"]' with matchIndex.`
|
|
629
|
-
: "Use 'ac:structured-macro[ac:name=\"plantuml\"]' to find PlantUML macros, or use matchIndex if multiple exist.";
|
|
630
|
-
return formatError({
|
|
631
|
-
error: true,
|
|
632
|
-
message: `No elements match selector: ${args.selector}`,
|
|
633
|
-
statusCode: 400,
|
|
634
|
-
details: { selector: args.selector, matchCount: 0, hint },
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
const targetIndex = args.matchIndex ?? 0;
|
|
638
|
-
if (targetIndex >= selectorResult.matches.length) {
|
|
639
|
-
return formatError({
|
|
640
|
-
error: true,
|
|
641
|
-
message: `matchIndex ${targetIndex} out of range (${selectorResult.matches.length} matches)`,
|
|
642
|
-
statusCode: 400,
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
const macroElement = selectorResult.matches[targetIndex].element;
|
|
646
|
-
// Verify it's a plantuml macro
|
|
647
|
-
if (macroElement.tagName.toLowerCase() !== "ac:structured-macro" ||
|
|
648
|
-
macroElement.getAttribute("ac:name") !== "plantuml") {
|
|
649
|
-
return formatError({
|
|
650
|
-
error: true,
|
|
651
|
-
message: "Selected element is not a PlantUML macro",
|
|
652
|
-
statusCode: 400,
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
// Update the macro content
|
|
656
|
-
const updated = updatePlantUmlInMacro(macroElement, normalizedCode);
|
|
657
|
-
if (!updated) {
|
|
658
|
-
return formatError({
|
|
659
|
-
error: true,
|
|
660
|
-
message: "Failed to update PlantUML macro content",
|
|
661
|
-
statusCode: 500,
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
// Serialize and update buffer
|
|
665
|
-
const newContent = serializeXhtml(parseResult.document);
|
|
666
|
-
// Validate if requested
|
|
667
|
-
if (args.validate !== false) {
|
|
668
|
-
const validationResult = validateXhtml(newContent);
|
|
669
|
-
if (!validationResult.valid) {
|
|
670
|
-
const firstError = validationResult.errors[0];
|
|
600
|
+
if (!result.success) {
|
|
671
601
|
return formatError({
|
|
672
602
|
error: true,
|
|
673
|
-
message: `
|
|
603
|
+
message: `Operation ${opIndex + 1} failed: ${result.error || "Unknown error"}`,
|
|
674
604
|
statusCode: 400,
|
|
675
605
|
details: {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
606
|
+
operationIndex: opIndex,
|
|
607
|
+
completedOperations: opIndex,
|
|
608
|
+
operation: op,
|
|
609
|
+
structure: bufferInfo.structure,
|
|
610
|
+
nextId: bufferInfo.nextId,
|
|
680
611
|
},
|
|
681
612
|
});
|
|
682
613
|
}
|
|
614
|
+
// Collect inserted IDs
|
|
615
|
+
if (result.insertedIds) {
|
|
616
|
+
allInsertedIds.push(...result.insertedIds);
|
|
617
|
+
}
|
|
683
618
|
}
|
|
684
|
-
//
|
|
685
|
-
|
|
619
|
+
// Serialize and update buffer once (after all operations)
|
|
620
|
+
const newContent = serializeXhtml(parseResult.document);
|
|
621
|
+
// Rebuild structure
|
|
622
|
+
const newStructure = parseStructure(parseResult.document);
|
|
623
|
+
// Update buffer
|
|
624
|
+
contentBuffer.update(args.bufferId, newContent, { contentType: "xhtml" }, newStructure.structure, newStructure.nextId);
|
|
686
625
|
return formatSuccess({
|
|
687
626
|
bufferId: args.bufferId,
|
|
688
627
|
success: true,
|
|
689
|
-
|
|
690
|
-
matchCount: 1,
|
|
628
|
+
operationsCompleted: operations.length,
|
|
691
629
|
oldSize,
|
|
692
630
|
newSize: newContent.length,
|
|
693
|
-
|
|
694
|
-
|
|
631
|
+
structure: newStructure.structure,
|
|
632
|
+
nextId: newStructure.nextId,
|
|
633
|
+
...(allInsertedIds.length > 0 && { insertedIds: allInsertedIds }),
|
|
634
|
+
...(diagramTypes.length > 0 && { diagramTypes }),
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
// Plain text/JSON editing
|
|
638
|
+
if (args.old_string === undefined || args.new_string === undefined) {
|
|
639
|
+
return formatError({
|
|
640
|
+
error: true,
|
|
641
|
+
message: "old_string and new_string required for plain/json buffer editing",
|
|
642
|
+
statusCode: 400,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const result = contentBuffer.edit(args.bufferId, args.old_string, args.new_string, args.replace_all ?? false);
|
|
646
|
+
if (!result) {
|
|
647
|
+
return formatError({
|
|
648
|
+
error: true,
|
|
649
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
650
|
+
statusCode: 404,
|
|
695
651
|
});
|
|
696
652
|
}
|
|
697
|
-
// Execute the operation
|
|
698
|
-
const result = executeOperation(parseResult.document, operation, resolvedSelector, {
|
|
699
|
-
position: resolvedPosition,
|
|
700
|
-
content: contentToInsert,
|
|
701
|
-
attributes: args.attributes,
|
|
702
|
-
targetSelector: args.targetSelector,
|
|
703
|
-
matchIndex: args.matchIndex,
|
|
704
|
-
matchAll: args.matchAll,
|
|
705
|
-
});
|
|
706
653
|
if ("error" in result && result.error) {
|
|
707
654
|
return formatError({
|
|
708
655
|
error: true,
|
|
709
656
|
message: result.message,
|
|
710
657
|
statusCode: 400,
|
|
711
|
-
details: result.
|
|
658
|
+
details: { occurrences: result.occurrences },
|
|
712
659
|
});
|
|
713
660
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
661
|
+
return formatSuccess(result);
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
return formatError(error instanceof Error ? error : new Error(String(error)));
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
buffer_get_structure: {
|
|
669
|
+
description: `Get current element structure for an XHTML buffer. Returns element IDs for use with buffer_edit.
|
|
670
|
+
|
|
671
|
+
Each element has:
|
|
672
|
+
- id: Unique ID (stable during session)
|
|
673
|
+
- type: Element type ("h1", "h2", "p", "plantuml", "ul", etc.)
|
|
674
|
+
- text: Preview text (truncated)
|
|
675
|
+
- children: Child count for lists/tables`,
|
|
676
|
+
inputSchema: z.object({
|
|
677
|
+
bufferId: z.string().describe("Buffer ID to get structure for"),
|
|
678
|
+
}),
|
|
679
|
+
handler: async (args) => {
|
|
680
|
+
try {
|
|
681
|
+
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
682
|
+
if (!bufferInfo) {
|
|
683
|
+
return formatError({
|
|
684
|
+
error: true,
|
|
685
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
686
|
+
statusCode: 404,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
if (bufferInfo.metadata?.contentType !== "xhtml") {
|
|
690
|
+
return formatError({
|
|
691
|
+
error: true,
|
|
692
|
+
message: "Buffer is not XHTML content",
|
|
693
|
+
statusCode: 400,
|
|
694
|
+
details: {
|
|
695
|
+
contentType: bufferInfo.metadata?.contentType,
|
|
696
|
+
hint: "Structure is only available for XHTML buffers",
|
|
697
|
+
},
|
|
698
|
+
});
|
|
733
699
|
}
|
|
734
|
-
// Update buffer in place (keeps same buffer ID)
|
|
735
|
-
contentBuffer.update(args.bufferId, newContent, { contentType: "xhtml" });
|
|
736
|
-
// Check if buffer is associated with a Confluence draft and add nextStep hint
|
|
737
|
-
const updatedBufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
738
|
-
const isDraft = updatedBufferInfo?.metadata?.isDraft === true;
|
|
739
|
-
const isConfluencePage = updatedBufferInfo?.metadata?.resourceType === "confluence_page";
|
|
740
700
|
return formatSuccess({
|
|
741
|
-
...result,
|
|
742
701
|
bufferId: args.bufferId,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
702
|
+
structure: bufferInfo.structure ?? [],
|
|
703
|
+
nextId: bufferInfo.nextId ?? 1,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
return formatError(error instanceof Error ? error : new Error(String(error)));
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
buffer_get_element: {
|
|
712
|
+
description: `Get the raw XHTML content of a specific element by ID.
|
|
713
|
+
|
|
714
|
+
Use this to inspect problematic elements when fixing XHTML parsing errors.
|
|
715
|
+
Returns the element's outerHTML, type, and line count.
|
|
716
|
+
|
|
717
|
+
Example: After a Confluence error at element 12, use this to see the content:
|
|
718
|
+
buffer_get_element(bufferId="buf_xxx", elementId=12)`,
|
|
719
|
+
inputSchema: z.object({
|
|
720
|
+
bufferId: z.string().describe("Buffer ID containing XHTML content"),
|
|
721
|
+
elementId: z.number().describe("Element ID from buffer structure"),
|
|
722
|
+
}),
|
|
723
|
+
handler: async (args) => {
|
|
724
|
+
try {
|
|
725
|
+
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
726
|
+
if (!bufferInfo) {
|
|
727
|
+
return formatError({
|
|
728
|
+
error: true,
|
|
729
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
730
|
+
statusCode: 404,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (bufferInfo.metadata?.contentType !== "xhtml") {
|
|
734
|
+
return formatError({
|
|
735
|
+
error: true,
|
|
736
|
+
message: "Buffer is not XHTML content",
|
|
737
|
+
statusCode: 400,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
// Get full buffer content
|
|
741
|
+
const chunk = contentBuffer.getChunk(args.bufferId, 0, bufferInfo.totalSize);
|
|
742
|
+
if (!chunk) {
|
|
743
|
+
return formatError({
|
|
744
|
+
error: true,
|
|
745
|
+
message: `Failed to read buffer: ${args.bufferId}`,
|
|
746
|
+
statusCode: 500,
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
// Parse and find element
|
|
750
|
+
const parseResult = parseXhtml(chunk.chunk);
|
|
751
|
+
if (!parseResult.document) {
|
|
752
|
+
return formatError({
|
|
753
|
+
error: true,
|
|
754
|
+
message: `Failed to parse XHTML: ${parseResult.error?.message}`,
|
|
755
|
+
statusCode: 400,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const root = parseResult.document.querySelector("xhtml-root");
|
|
759
|
+
if (!root) {
|
|
760
|
+
return formatError({
|
|
761
|
+
error: true,
|
|
762
|
+
message: "XHTML has no content root",
|
|
763
|
+
statusCode: 400,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const element = root.querySelector(`[data-jicon-id="${args.elementId}"]`);
|
|
767
|
+
if (!element) {
|
|
768
|
+
return formatError({
|
|
769
|
+
error: true,
|
|
770
|
+
message: `Element not found: ${args.elementId}`,
|
|
771
|
+
statusCode: 404,
|
|
772
|
+
details: {
|
|
773
|
+
availableIds: bufferInfo.structure?.map((e) => e.id) ?? [],
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
const content = element.outerHTML;
|
|
778
|
+
const lineCount = (content.match(/\n/g) || []).length + 1;
|
|
779
|
+
// Determine element type
|
|
780
|
+
let elementType = element.tagName.toLowerCase();
|
|
781
|
+
if (elementType === "ac:structured-macro") {
|
|
782
|
+
elementType = element.getAttribute("ac:name") || "macro";
|
|
783
|
+
}
|
|
784
|
+
return formatSuccess({
|
|
785
|
+
bufferId: args.bufferId,
|
|
786
|
+
elementId: args.elementId,
|
|
787
|
+
elementType,
|
|
788
|
+
lineCount,
|
|
789
|
+
content,
|
|
749
790
|
});
|
|
750
791
|
}
|
|
751
792
|
catch (error) {
|
|
@@ -795,10 +836,24 @@ Use this to validate content before calling confluence_update_page or confluence
|
|
|
795
836
|
statusCode: 500,
|
|
796
837
|
});
|
|
797
838
|
}
|
|
839
|
+
// Check for raw PlantUML (same check as confluence_draft_create)
|
|
840
|
+
// This ensures validation and draft creation agree on what's valid
|
|
841
|
+
const rawPlantUml = detectRawPlantUml(chunk.chunk);
|
|
798
842
|
// Validate XHTML with async PlantUML validation
|
|
799
843
|
const validationResult = await validateXhtmlAsync(chunk.chunk, {
|
|
800
844
|
validatePlantUml: args.validatePlantUml !== false,
|
|
801
845
|
});
|
|
846
|
+
// Add raw PlantUML error if detected
|
|
847
|
+
if (rawPlantUml) {
|
|
848
|
+
validationResult.errors.push({
|
|
849
|
+
type: "raw_plantuml",
|
|
850
|
+
message: "Content contains raw PlantUML code that is not wrapped in a Confluence macro.",
|
|
851
|
+
location: {
|
|
852
|
+
context: rawPlantUml.hint,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
validationResult.valid = false;
|
|
856
|
+
}
|
|
802
857
|
return formatSuccess({
|
|
803
858
|
bufferId: args.bufferId,
|
|
804
859
|
valid: validationResult.valid,
|