@gitsense/gsc-utils 0.2.24 → 0.2.27
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 +380 -62
- package/dist/gsc-utils.cjs.js +14270 -523
- package/dist/gsc-utils.esm.js +14270 -523
- package/package.json +1 -1
- package/src/AnalyzerUtils/cloner.js +149 -0
- package/src/AnalyzerUtils/constants.js +1 -1
- package/src/AnalyzerUtils/defaultPromptLoader.js +10 -10
- package/src/AnalyzerUtils/discovery.js +48 -39
- package/src/AnalyzerUtils/index.js +13 -7
- package/src/AnalyzerUtils/instructionLoader.js +6 -6
- package/src/AnalyzerUtils/jsonParser.js +35 -0
- package/src/AnalyzerUtils/management.js +6 -6
- package/src/AnalyzerUtils/saver.js +5 -5
- package/src/AnalyzerUtils/schemaLoader.js +194 -26
- package/src/AnalyzerUtils/updater.js +187 -0
- package/src/CodeBlockUtils/blockProcessor.js +14 -32
- package/src/CodeBlockUtils/constants.js +8 -4
- package/src/CodeBlockUtils/headerUtils.js +19 -3
- package/src/CodeBlockUtils/index.js +7 -6
- package/src/CodeBlockUtils/lineageTracer.js +95 -0
- package/src/CompactChatUtils/CompactedMessageUtils.js +224 -0
- package/src/CompactChatUtils/README.md +321 -0
- package/src/CompactChatUtils/ReferenceMessageUtils.js +143 -0
- package/src/CompactChatUtils/index.js +40 -0
- package/src/ContextUtils.js +41 -5
- package/src/DomUtils.js +559 -0
- package/src/GSToolBlockUtils.js +66 -1
- package/src/GitSenseChatUtils.js +108 -16
- package/src/LanguageNameUtils.js +171 -0
- package/src/MarkdownUtils.js +127 -0
- package/src/MessageUtils.js +1 -1
- package/src/MetaRawResultUtils.js +244 -0
- package/src/ObjectUtils.js +54 -0
- package/src/PatchUtils/constants.js +9 -3
- package/src/PatchUtils/patchParser.js +60 -37
- package/src/PatchUtils/patchProcessor.js +8 -5
- package/src/PatchUtils/patchVerifier/detectAndFixOverlappingHunks.js +1 -1
- package/src/SVGUtils.js +1467 -0
- package/src/SharedUtils/stringUtils.js +303 -0
- package/src/CodeBlockUtils/blockProcessor.js.rej +0 -8
package/src/DomUtils.js
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: DomUtils Helper Functions
|
|
3
|
+
* Block-UUID: 9a8e8e8e-8b15-4346-bbfa-740e6a5d2d79
|
|
4
|
+
* Parent-UUID: 8a528728-ce54-4445-946d-1743fb4b16be
|
|
5
|
+
* Version: 1.3.0
|
|
6
|
+
* Description: Provides helper functions for creating and manipulating DOM elements.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-12-14T04:18:40.180Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash (v1.1.0), Qwen 3 Coder 480B - Cerebras (v1.2.0), GLM-4.6 (v1.2.1), GLM-4.6 (v1.3.0)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function createElement(type, params) {
|
|
14
|
+
let elem = document.createElement(type);
|
|
15
|
+
|
|
16
|
+
if (!params) return elem;
|
|
17
|
+
|
|
18
|
+
// Extract all standard properties
|
|
19
|
+
let {
|
|
20
|
+
id,
|
|
21
|
+
ariaLabel,
|
|
22
|
+
xmlns,
|
|
23
|
+
role,
|
|
24
|
+
html,
|
|
25
|
+
text,
|
|
26
|
+
cls,
|
|
27
|
+
className,
|
|
28
|
+
colSpan,
|
|
29
|
+
pointerEvents,
|
|
30
|
+
title,
|
|
31
|
+
style,
|
|
32
|
+
append,
|
|
33
|
+
type: inputType,
|
|
34
|
+
value,
|
|
35
|
+
name,
|
|
36
|
+
checked,
|
|
37
|
+
selected,
|
|
38
|
+
disabled,
|
|
39
|
+
placeholder,
|
|
40
|
+
"for": _for,
|
|
41
|
+
"data-message-id": dmi,
|
|
42
|
+
"data-message-role": dmr,
|
|
43
|
+
attrs = {},
|
|
44
|
+
dataset,
|
|
45
|
+
rows
|
|
46
|
+
} = params;
|
|
47
|
+
|
|
48
|
+
// Set standard attributes
|
|
49
|
+
if (id != null) elem.id = id;
|
|
50
|
+
if (ariaLabel) elem.setAttribute("aria-label", ariaLabel);
|
|
51
|
+
if (xmlns) elem.setAttribute("xmlns", xmlns);
|
|
52
|
+
if (params.viewBox) elem.setAttribute("viewBox", params.viewBox);
|
|
53
|
+
if (params.fill) elem.setAttribute("fill", params.fill);
|
|
54
|
+
if (role) elem.setAttribute("role", role);
|
|
55
|
+
if (placeholder) elem.setAttribute("placeholder", placeholder);
|
|
56
|
+
if (cls || className) elem.setAttribute("class", cls || className);
|
|
57
|
+
if (pointerEvents) elem.setAttribute("pointer-events", pointerEvents);
|
|
58
|
+
if (title != null) elem.setAttribute("title", title);
|
|
59
|
+
if (inputType) elem.setAttribute("type", inputType);
|
|
60
|
+
if (name != null) elem.setAttribute("name", name);
|
|
61
|
+
if (params.width) elem.setAttribute("width", params.width);
|
|
62
|
+
if (params.height) elem.setAttribute("height", params.height);
|
|
63
|
+
if (params.stroke) elem.setAttribute("stroke", params.stroke);
|
|
64
|
+
if (value != null) elem.value = value;
|
|
65
|
+
if (checked != null && checked) elem.checked = true; // Use property for checked
|
|
66
|
+
if (selected != null && selected) elem.selected = true; // Use property for selected
|
|
67
|
+
if (disabled != null) elem.disabled = disabled; // Use property for disabled
|
|
68
|
+
if (dmi != null) elem.setAttribute("data-message-id", dmi);
|
|
69
|
+
if (dmr != null) elem.setAttribute("data-message-role", dmr);
|
|
70
|
+
if (_for != null ) elem.setAttribute("for", _for);
|
|
71
|
+
if (params.strokeWidth) elem.setAttribute("stroke-width", params.strokeWidth);
|
|
72
|
+
if (params.strokeLinecap) elem.setAttribute("stroke-linecap", params.strokeLinecap);
|
|
73
|
+
if (params.strokeLinejoin) elem.setAttribute("stroke-linejoin", params.strokeLinejoin);
|
|
74
|
+
if (colSpan != null) elem.colSpan = colSpan;
|
|
75
|
+
if (rows) elem.setAttribute("rows", params.rows);
|
|
76
|
+
|
|
77
|
+
// Set content
|
|
78
|
+
if (html != null) {
|
|
79
|
+
if (typeof html === "object" && html instanceof Node) { // Ensure html is a Node
|
|
80
|
+
elem.appendChild(html);
|
|
81
|
+
} else if (typeof html === "string") {
|
|
82
|
+
// For SVG elements, use innerHTML to support path elements
|
|
83
|
+
if (type === 'svg' && params.innerHTML) {
|
|
84
|
+
elem.innerHTML = params.innerHTML;
|
|
85
|
+
}
|
|
86
|
+
elem.innerHTML = html;
|
|
87
|
+
}
|
|
88
|
+
} else if (text != null) {
|
|
89
|
+
elem.innerText = text;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Set style properties
|
|
93
|
+
if (style) {
|
|
94
|
+
for (const name in style) {
|
|
95
|
+
elem.style[name] = style[name];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle append (now supports both arrays and single elements)
|
|
100
|
+
if (append) {
|
|
101
|
+
const appendItems = Array.isArray(append) ? append : [append];
|
|
102
|
+
appendItems.forEach(e => {
|
|
103
|
+
if (e instanceof Node) { // Ensure item to append is a Node
|
|
104
|
+
elem.appendChild(e);
|
|
105
|
+
} else if (e !== undefined && e !== null) {
|
|
106
|
+
console.warn("Attempted to append non-Node item:", e);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Set additional attributes from attrs object
|
|
112
|
+
if (attrs && typeof attrs === 'object') {
|
|
113
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
114
|
+
if (value !== undefined && value !== null) {
|
|
115
|
+
// Skip if already set by standard properties or handled directly
|
|
116
|
+
if (!['id', 'class', 'role', 'aria-label', 'title', 'style', 'selected', 'disabled', 'value', 'name', 'type', 'for'].includes(key) && !key.startsWith('data-')) {
|
|
117
|
+
elem.setAttribute(key, value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle dataset properties
|
|
124
|
+
if (dataset && typeof dataset === 'object') {
|
|
125
|
+
for (const [key, value] of Object.entries(dataset)) {
|
|
126
|
+
if (value !== undefined && value !== null) {
|
|
127
|
+
elem.dataset[key] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle event handlers (like onclick, onchange, etc.)
|
|
133
|
+
for (const [key, value] of Object.entries(params)) {
|
|
134
|
+
// Check if the property is an event handler (starts with 'on')
|
|
135
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
136
|
+
const eventName = key.slice(2).toLowerCase(); // Remove 'on' prefix and convert to lowercase
|
|
137
|
+
elem.addEventListener(eventName, value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return elem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Update all creator functions to pass through attrs:
|
|
145
|
+
const h = {
|
|
146
|
+
elemById: (id) => {
|
|
147
|
+
return document.getElementById(id);
|
|
148
|
+
},
|
|
149
|
+
createA: (params) => {
|
|
150
|
+
return h.createLink(params);
|
|
151
|
+
},
|
|
152
|
+
createArticle: (params) => {
|
|
153
|
+
return createElement("article", params);
|
|
154
|
+
},
|
|
155
|
+
createBr: () => {
|
|
156
|
+
return createElement("br");
|
|
157
|
+
},
|
|
158
|
+
createBreak: () => {
|
|
159
|
+
return createElement("br");
|
|
160
|
+
},
|
|
161
|
+
createButton: (params) => {
|
|
162
|
+
return createElement("button", params);
|
|
163
|
+
},
|
|
164
|
+
createCheckbox: (params) => {
|
|
165
|
+
// Use createElement and let it handle attributes like checked/disabled
|
|
166
|
+
return createElement("input", { ...params, type: "checkbox" });
|
|
167
|
+
},
|
|
168
|
+
createCode: (params) => {
|
|
169
|
+
return createElement("code", params);
|
|
170
|
+
},
|
|
171
|
+
createDetail: (params) => {
|
|
172
|
+
return createElement("details", params);
|
|
173
|
+
},
|
|
174
|
+
createDiv: (params) => {
|
|
175
|
+
return createElement("div", params);
|
|
176
|
+
},
|
|
177
|
+
createEm: (params) => { // Added
|
|
178
|
+
return createElement("em", params);
|
|
179
|
+
},
|
|
180
|
+
createForm: (params) => {
|
|
181
|
+
return createElement("form", params);
|
|
182
|
+
},
|
|
183
|
+
createInput: (params) => {
|
|
184
|
+
return createElement("input", params);
|
|
185
|
+
},
|
|
186
|
+
createImg : (params) => {
|
|
187
|
+
const img = createElement("img", params);
|
|
188
|
+
// src is handled by createElement via attrs or direct property if needed
|
|
189
|
+
return img;
|
|
190
|
+
},
|
|
191
|
+
createH1: (params) => {
|
|
192
|
+
return createElement("h1", params);
|
|
193
|
+
},
|
|
194
|
+
createH2: (params) => {
|
|
195
|
+
return createElement("h2", params);
|
|
196
|
+
},
|
|
197
|
+
createH3: (params) => {
|
|
198
|
+
return createElement("h3", params);
|
|
199
|
+
},
|
|
200
|
+
createH4: (params) => {
|
|
201
|
+
return createElement("h4", params);
|
|
202
|
+
},
|
|
203
|
+
createH5: (params) => {
|
|
204
|
+
return createElement("h5", params);
|
|
205
|
+
},
|
|
206
|
+
createHeader: (header, params) => {
|
|
207
|
+
// Ensure header is a string like 'h1', 'h2' etc.
|
|
208
|
+
if (typeof header === 'string' && /^h[1-6]$/i.test(header)) {
|
|
209
|
+
return createElement(header, params);
|
|
210
|
+
}
|
|
211
|
+
console.error("Invalid header type provided to createHeader:", header);
|
|
212
|
+
return createElement("div", params); // Fallback or throw error
|
|
213
|
+
},
|
|
214
|
+
createHr: (params) => {
|
|
215
|
+
return createElement("hr", params);
|
|
216
|
+
},
|
|
217
|
+
createLabel : (params) => {
|
|
218
|
+
// 'for' is handled by createElement
|
|
219
|
+
return createElement("label", params);
|
|
220
|
+
},
|
|
221
|
+
createLI: (params) => {
|
|
222
|
+
return createElement("li", params);
|
|
223
|
+
},
|
|
224
|
+
createLi: (params) => { // Alias for createLI
|
|
225
|
+
return createElement("li", params);
|
|
226
|
+
},
|
|
227
|
+
createLink: (params) => {
|
|
228
|
+
const link = createElement("a", params);
|
|
229
|
+
if (params?.href) link.href = params.href;
|
|
230
|
+
if (params?.target) link.target = params.target;
|
|
231
|
+
return link;
|
|
232
|
+
},
|
|
233
|
+
createNav: (params) => {
|
|
234
|
+
return createElement("nav", params);
|
|
235
|
+
},
|
|
236
|
+
createOl: (params) => {
|
|
237
|
+
return createElement("ol", params);
|
|
238
|
+
},
|
|
239
|
+
createOL: (params) => {
|
|
240
|
+
return createElement("ol", params);
|
|
241
|
+
},
|
|
242
|
+
createOption: (params) => { // Added
|
|
243
|
+
// value, text, selected are handled by createElement
|
|
244
|
+
return createElement("option", params);
|
|
245
|
+
},
|
|
246
|
+
createP: (params) => {
|
|
247
|
+
return createElement("p", params);
|
|
248
|
+
},
|
|
249
|
+
createParagraph: (params) => { // Alias for createP
|
|
250
|
+
return createElement("p", params);
|
|
251
|
+
},
|
|
252
|
+
createPre: (params) => {
|
|
253
|
+
return createElement("pre", params);
|
|
254
|
+
},
|
|
255
|
+
createSelect: (params) => { // Added
|
|
256
|
+
// name, disabled etc handled by createElement
|
|
257
|
+
const select = createElement("select", params);
|
|
258
|
+
|
|
259
|
+
// Handle options array if provided
|
|
260
|
+
if (params && params.options && Array.isArray(params.options)) {
|
|
261
|
+
params.options.forEach(option => {
|
|
262
|
+
if (option && typeof option === 'object') {
|
|
263
|
+
const optionElement = createElement("option", {
|
|
264
|
+
value: option.value,
|
|
265
|
+
text: option.text,
|
|
266
|
+
selected: option.selected
|
|
267
|
+
});
|
|
268
|
+
select.appendChild(optionElement);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return select;
|
|
274
|
+
},
|
|
275
|
+
createSpan: (params) => {
|
|
276
|
+
return createElement("span", params);
|
|
277
|
+
},
|
|
278
|
+
createSvg: (params) => {
|
|
279
|
+
// Create SVG element with xmlns attribute
|
|
280
|
+
const svgParams = { ...params, xmlns: "http://www.w3.org/2000/svg" };
|
|
281
|
+
const svg = createElement("svg", svgParams);
|
|
282
|
+
return svg;
|
|
283
|
+
},
|
|
284
|
+
createStrong: (params) => { // Added
|
|
285
|
+
return createElement("strong", params);
|
|
286
|
+
},
|
|
287
|
+
createSummary: (params) => {
|
|
288
|
+
return createElement("summary", params);
|
|
289
|
+
},
|
|
290
|
+
createSup: (params) => {
|
|
291
|
+
return createElement("sup", params);
|
|
292
|
+
},
|
|
293
|
+
createText: (text) => {
|
|
294
|
+
return document.createTextNode(text);
|
|
295
|
+
},
|
|
296
|
+
createTextarea: (params) => {
|
|
297
|
+
const textArea = createElement("textarea", params);
|
|
298
|
+
return textArea;
|
|
299
|
+
},
|
|
300
|
+
createTextArea: (params) => {
|
|
301
|
+
const textArea = createElement("textarea", params);
|
|
302
|
+
return textArea;
|
|
303
|
+
},
|
|
304
|
+
createTextNode: (text) => {
|
|
305
|
+
return document.createTextNode(text);
|
|
306
|
+
},
|
|
307
|
+
createTable: (params) => {
|
|
308
|
+
return createElement("table", params);
|
|
309
|
+
},
|
|
310
|
+
createTableBody: (params) => {
|
|
311
|
+
return createElement("tbody", params);
|
|
312
|
+
},
|
|
313
|
+
createTableCell: (params) => {
|
|
314
|
+
return createElement("td", params);
|
|
315
|
+
},
|
|
316
|
+
createTableHead: (params) => {
|
|
317
|
+
return createElement("thead", params);
|
|
318
|
+
},
|
|
319
|
+
createTableHeadCell: (params) => {
|
|
320
|
+
return createElement("th", params);
|
|
321
|
+
},
|
|
322
|
+
createTbody: (params) => {
|
|
323
|
+
return createElement("tbody", params);
|
|
324
|
+
},
|
|
325
|
+
createTd: (params) => {
|
|
326
|
+
return createElement("td", params);
|
|
327
|
+
},
|
|
328
|
+
createTh: (params) => {
|
|
329
|
+
return createElement("th", params);
|
|
330
|
+
},
|
|
331
|
+
createThead: (params) => {
|
|
332
|
+
return createElement("thead", params);
|
|
333
|
+
},
|
|
334
|
+
createTr: (params) => {
|
|
335
|
+
return createElement("tr", params);
|
|
336
|
+
},
|
|
337
|
+
createTableRow: (params) => {
|
|
338
|
+
return createElement("tr", params);
|
|
339
|
+
},
|
|
340
|
+
createTextInput: (params) => {
|
|
341
|
+
// Use createElement and let it handle attributes like value/disabled
|
|
342
|
+
return createElement("input", { ...params, type: "text" });
|
|
343
|
+
},
|
|
344
|
+
createUl: (params) => {
|
|
345
|
+
return createElement("ul", params);
|
|
346
|
+
},
|
|
347
|
+
createUL: (params) => {
|
|
348
|
+
return createElement("ul", params);
|
|
349
|
+
},
|
|
350
|
+
hide: (elem) => {
|
|
351
|
+
if (elem && elem.style) {
|
|
352
|
+
elem.style.display = "none";
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
show: (elem) => {
|
|
356
|
+
if (elem && elem.style) {
|
|
357
|
+
elem.style.display = ""; // Reset to default display
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
updateDOM: (sourceNode, targetNode) => {
|
|
361
|
+
// Step 1: Check if the node type or tag name is different
|
|
362
|
+
if (!sourceNode || !targetNode) return; // Basic guard
|
|
363
|
+
if (sourceNode.nodeType !== targetNode.nodeType || sourceNode.nodeName !== targetNode.nodeName) {
|
|
364
|
+
targetNode.replaceWith(sourceNode.cloneNode(true));
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Step 2: Check if it's an element node
|
|
369
|
+
if (sourceNode.nodeType === Node.ELEMENT_NODE) {
|
|
370
|
+
// Step 3: Compare and update attributes
|
|
371
|
+
let sourceAttributes = sourceNode.attributes;
|
|
372
|
+
let targetAttributes = targetNode.attributes;
|
|
373
|
+
const targetAttrMap = new Map();
|
|
374
|
+
for (let i = 0; i < targetAttributes.length; i++) {
|
|
375
|
+
targetAttrMap.set(targetAttributes[i].name, targetAttributes[i].value);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Update or add attributes from sourceNode to targetNode
|
|
379
|
+
for (let i = 0; i < sourceAttributes.length; i++) {
|
|
380
|
+
let attr = sourceAttributes[i];
|
|
381
|
+
if (targetAttrMap.get(attr.name) !== attr.value) {
|
|
382
|
+
targetNode.setAttribute(attr.name, attr.value);
|
|
383
|
+
}
|
|
384
|
+
targetAttrMap.delete(attr.name); // Mark as processed
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Remove attributes that are in target but not in source
|
|
388
|
+
for (const attrName of targetAttrMap.keys()) {
|
|
389
|
+
targetNode.removeAttribute(attrName);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Special handling for properties like value, checked, selected, disabled
|
|
393
|
+
if (sourceNode.value !== targetNode.value) {
|
|
394
|
+
targetNode.value = sourceNode.value;
|
|
395
|
+
}
|
|
396
|
+
if (sourceNode.checked !== targetNode.checked) {
|
|
397
|
+
targetNode.checked = sourceNode.checked;
|
|
398
|
+
}
|
|
399
|
+
if (sourceNode.selected !== targetNode.selected) {
|
|
400
|
+
targetNode.selected = sourceNode.selected;
|
|
401
|
+
}
|
|
402
|
+
if (sourceNode.disabled !== targetNode.disabled) {
|
|
403
|
+
targetNode.disabled = sourceNode.disabled;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
// Step 4: Recursively compare and update children
|
|
408
|
+
let sourceChildren = sourceNode.childNodes;
|
|
409
|
+
let targetChildren = targetNode.childNodes;
|
|
410
|
+
let maxLen = Math.max(sourceChildren.length, targetChildren.length);
|
|
411
|
+
|
|
412
|
+
for (let i = 0; i < maxLen; i++) {
|
|
413
|
+
if (i >= targetChildren.length) {
|
|
414
|
+
// If the target has fewer children, append the new child
|
|
415
|
+
if (sourceChildren[i]) {
|
|
416
|
+
targetNode.appendChild(sourceChildren[i].cloneNode(true));
|
|
417
|
+
}
|
|
418
|
+
} else if (i >= sourceChildren.length) {
|
|
419
|
+
// If the source has fewer children, remove extra target child
|
|
420
|
+
targetNode.removeChild(targetChildren[i]);
|
|
421
|
+
} else {
|
|
422
|
+
// Otherwise, update the existing child
|
|
423
|
+
h.updateDOM(sourceChildren[i], targetChildren[i]);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
} else if (sourceNode.nodeType === Node.TEXT_NODE) {
|
|
428
|
+
// Step 5: Update text content if the text nodes are different
|
|
429
|
+
if (sourceNode.nodeValue !== targetNode.nodeValue) {
|
|
430
|
+
targetNode.nodeValue = sourceNode.nodeValue;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Other node types (comments, etc.) are ignored for updates in this version
|
|
434
|
+
},
|
|
435
|
+
safeEscape: (html, x) => {
|
|
436
|
+
if ( x )
|
|
437
|
+
console.log(x);
|
|
438
|
+
|
|
439
|
+
if (typeof html !== 'string') return html; // Return non-strings as-is
|
|
440
|
+
|
|
441
|
+
// Generated by Claude 3.5 Sonnet
|
|
442
|
+
const preserveTags = [
|
|
443
|
+
'pre',
|
|
444
|
+
'code',
|
|
445
|
+
'script',
|
|
446
|
+
'style',
|
|
447
|
+
'textarea',
|
|
448
|
+
'math',
|
|
449
|
+
'mi',
|
|
450
|
+
'mo',
|
|
451
|
+
'mn',
|
|
452
|
+
'ms',
|
|
453
|
+
'mtext',
|
|
454
|
+
'samp',
|
|
455
|
+
'kbd',
|
|
456
|
+
'var',
|
|
457
|
+
'svg',
|
|
458
|
+
'xmp'
|
|
459
|
+
].join('|');
|
|
460
|
+
|
|
461
|
+
// Track whether we're inside a preserve tag
|
|
462
|
+
let isInPreserveTag = false;
|
|
463
|
+
let currentTag = '';
|
|
464
|
+
|
|
465
|
+
// Split the input into tokens that are either tags or text
|
|
466
|
+
// Improved regex to handle self-closing tags and attributes better
|
|
467
|
+
return html.split(/(<\/?(?:${preserveTags})(?:\s+[^>]*)?\/?>|<\/?[a-zA-Z][^>]*>)/i)
|
|
468
|
+
.filter(part => part) // Remove empty strings from split
|
|
469
|
+
.map(part => {
|
|
470
|
+
// Check if this part is a preserve tag
|
|
471
|
+
const preserveTagMatch = part.match(new RegExp(`^<(/?)(${preserveTags})([\\s>/])`, 'i'));
|
|
472
|
+
|
|
473
|
+
if (preserveTagMatch) {
|
|
474
|
+
// Toggle the preserve state based on opening/closing tags
|
|
475
|
+
if (!preserveTagMatch[1] && !part.endsWith('/>')) { // Opening tag, not self-closing
|
|
476
|
+
isInPreserveTag = true;
|
|
477
|
+
currentTag = preserveTagMatch[2];
|
|
478
|
+
} else if (preserveTagMatch[1]) { // Closing tag
|
|
479
|
+
// Only close if it matches the current open tag
|
|
480
|
+
if (isInPreserveTag && currentTag.toLowerCase() === preserveTagMatch[2].toLowerCase()) {
|
|
481
|
+
isInPreserveTag = false;
|
|
482
|
+
currentTag = '';
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return part; // Return the tag itself unmodified
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// If we're not in a preserve tag and this isn't an HTML tag, escape <
|
|
489
|
+
if (!isInPreserveTag && !part.startsWith('<')) {
|
|
490
|
+
return part.replace(/</g, '<');
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Return other tags or content within preserve tags unmodified
|
|
494
|
+
return part;
|
|
495
|
+
})
|
|
496
|
+
.join('');
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Injects CSS styles into the document head.
|
|
501
|
+
* @param {string} cssString - The CSS string to inject.
|
|
502
|
+
* @param {string} [id] - Optional ID for the style element to prevent duplicates.
|
|
503
|
+
* @returns {HTMLStyleElement} The created or existing style element.
|
|
504
|
+
*/
|
|
505
|
+
injectStyles: (cssString, id) => {
|
|
506
|
+
// If an ID is provided, check if styles with this ID already exist
|
|
507
|
+
if (id) {
|
|
508
|
+
const existingStyle = document.getElementById(id);
|
|
509
|
+
if (existingStyle) {
|
|
510
|
+
// Styles already injected, return the existing element
|
|
511
|
+
return existingStyle;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Create a new style element
|
|
516
|
+
const styleElement = document.createElement('style');
|
|
517
|
+
if (id) {
|
|
518
|
+
styleElement.id = id;
|
|
519
|
+
}
|
|
520
|
+
styleElement.textContent = cssString;
|
|
521
|
+
|
|
522
|
+
// Append to the document head
|
|
523
|
+
document.head.appendChild(styleElement);
|
|
524
|
+
|
|
525
|
+
return styleElement;
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Calculates the distance between an element's edge and the viewport's corresponding edge
|
|
530
|
+
* @param {HTMLElement} elem - The element to measure from
|
|
531
|
+
* @param {"left"|"right"} edge - Which edge to measure from
|
|
532
|
+
* @returns {number} The distance in pixels
|
|
533
|
+
* @throws {Error} If invalid edge parameter is provided or element not found
|
|
534
|
+
*/
|
|
535
|
+
getDistanceFromViewportEdge: (elem, edge = "right") => {
|
|
536
|
+
// Input validation
|
|
537
|
+
if (!elem || typeof elem.getBoundingClientRect !== 'function') {
|
|
538
|
+
throw new Error('Invalid element provided.');
|
|
539
|
+
}
|
|
540
|
+
if ( !["left", "right"].includes(edge) ) {
|
|
541
|
+
throw new Error('Edge parameter must be either "left" or "right"');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Get the bounding rectangle of the elem
|
|
545
|
+
const rect = elem.getBoundingClientRect();
|
|
546
|
+
|
|
547
|
+
// Get the width of the viewport
|
|
548
|
+
const viewportWidth = window.innerWidth;
|
|
549
|
+
|
|
550
|
+
// Calculate distance based on specified edge
|
|
551
|
+
if (edge === "right") {
|
|
552
|
+
return viewportWidth - rect.right;
|
|
553
|
+
} else { // edge === "left"
|
|
554
|
+
return rect.left;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
module.exports = { h };
|
package/src/GSToolBlockUtils.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
const GS_TOOL_BLOCK_TYPE = 'gs-tool';
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* Checks if a given code block content represents a GitSense Chat Tool Block.
|
|
15
17
|
* It verifies if the first non-empty line starts with "# GitSense Chat Tool".
|
|
@@ -28,6 +30,65 @@ function isToolBlock(content) {
|
|
|
28
30
|
return false;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
|
|
34
|
+
// TODO: Add JSDocs
|
|
35
|
+
function getToolBlocksByTool(content, tool, CodeBlockUtils) {
|
|
36
|
+
const { blocks, warnings } = CodeBlockUtils.extractCodeBlocks(content, { silent: true });
|
|
37
|
+
|
|
38
|
+
return blocks.filter((block, index) => {
|
|
39
|
+
if (block.type !== GS_TOOL_BLOCK_TYPE)
|
|
40
|
+
return;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const data = parseToolBlock(block.content);
|
|
44
|
+
|
|
45
|
+
if (data.tool === tool) {
|
|
46
|
+
block.index = index;
|
|
47
|
+
return block;
|
|
48
|
+
}
|
|
49
|
+
} catch(error) {
|
|
50
|
+
// FIXME: We need a more elegant way to identify examples.
|
|
51
|
+
if (!content.includes(/Internal INTEGER IDs/)) {
|
|
52
|
+
console.warn(`Invalid tool block JSON: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// TODO: Add JSDocs
|
|
59
|
+
function getToolBlockElemsByTool(dom, tool) {
|
|
60
|
+
const elems = dom.querySelectorAll('pre');
|
|
61
|
+
const toolBockElems = [];
|
|
62
|
+
|
|
63
|
+
for ( let i = 0; i < elems.length; i++ ) {
|
|
64
|
+
const elem = elems[i];
|
|
65
|
+
let content = elem.textContent;
|
|
66
|
+
|
|
67
|
+
// We need to strip out the first two lines since this is not part of the actual code
|
|
68
|
+
// block. These two lines are designed to make it easy to identify the language
|
|
69
|
+
content = content.split('\n').slice(2).join('\n');
|
|
70
|
+
|
|
71
|
+
if (!isToolBlock(content))
|
|
72
|
+
continue;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const data = parseToolBlock(content, { silent: true });
|
|
76
|
+
|
|
77
|
+
if (data.tool === tool)
|
|
78
|
+
toolBockElems.push(elem);
|
|
79
|
+
} catch(error) {
|
|
80
|
+
// FIXME: We need a more elegant way to identify examples.
|
|
81
|
+
if (!content.match(/Internal INTEGER IDs/)) {
|
|
82
|
+
console.warn("getToolBlockElemsByTool: ",error.message);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return toolBockElems;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
31
92
|
/**
|
|
32
93
|
* Parses the content of a GitSense Chat Tool Block to extract the JSON payload.
|
|
33
94
|
* It strips the marker line and any subsequent lines starting with '#' (comments)
|
|
@@ -136,7 +197,7 @@ function replaceToolBlock(markdownContent, toolName, newToolData, CodeBlockUtils
|
|
|
136
197
|
// 2. Iterate through the processed blocks to find the target GitSense Chat Tool Block
|
|
137
198
|
for (let i = 0; i < blocks.length; i++) {
|
|
138
199
|
const block = blocks[i];
|
|
139
|
-
if (block.type ===
|
|
200
|
+
if (block.type === GS_TOOL_BLOCK_TYPE && block.toolData && block.toolData.tool === toolName) {
|
|
140
201
|
targetBlockIndex = i;
|
|
141
202
|
break; // Found the first matching tool block
|
|
142
203
|
}
|
|
@@ -270,10 +331,14 @@ function detectAndFormatUnfencedToolBlock(messageContent) {
|
|
|
270
331
|
};
|
|
271
332
|
}
|
|
272
333
|
|
|
334
|
+
|
|
335
|
+
|
|
273
336
|
module.exports = {
|
|
274
337
|
isToolBlock,
|
|
275
338
|
parseToolBlock,
|
|
276
339
|
formatToolBlock,
|
|
277
340
|
replaceToolBlock,
|
|
341
|
+
getToolBlocksByTool,
|
|
342
|
+
getToolBlockElemsByTool,
|
|
278
343
|
detectAndFormatUnfencedToolBlock,
|
|
279
344
|
}
|