@gitsense/gsc-utils 0.2.24 → 0.2.25
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/dist/gsc-utils.cjs.js +12074 -199
- package/dist/gsc-utils.esm.js +12074 -199
- package/package.json +1 -1
- package/src/CodeBlockUtils/constants.js +8 -4
- package/src/CodeBlockUtils/headerUtils.js +19 -3
- package/src/ContextUtils.js +1 -1
- package/src/DomUtils.js +485 -0
- package/src/GitSenseChatUtils.js +55 -16
- package/src/LanguageNameUtils.js +171 -0
- package/src/MarkdownUtils.js +124 -0
- package/src/ObjectUtils.js +54 -0
- package/src/PatchUtils/patchParser.js +1 -2
- package/src/PatchUtils/patchProcessor.js +8 -5
- package/src/SVGUtils.js +1410 -0
package/package.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Component: CodeBlockUtils Constants
|
|
3
3
|
* Block-UUID: 01b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
|
|
4
4
|
* Parent-UUID: 144d19a5-139a-4e80-8abe-84965583e35e
|
|
5
|
-
* Version: 1.0.
|
|
5
|
+
* Version: 1.0.2
|
|
6
6
|
* Description: Defines constants used across the CodeBlockUtils modules, such as comment styles for various languages.
|
|
7
7
|
* Language: JavaScript
|
|
8
8
|
* Created-at: 2025-04-15T15:51:16.973Z
|
|
9
|
-
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.0.1)
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.0.1), Qwen 3 Coder 480B - Cerebras (v1.0.2)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
|
|
@@ -40,6 +40,12 @@ const COMMENT_STYLES = {
|
|
|
40
40
|
'r': { type: 'hash' },
|
|
41
41
|
'julia': { type: 'hash' },
|
|
42
42
|
|
|
43
|
+
// HTML/XML/SVG/Markdown comment style
|
|
44
|
+
'html': { type: 'html' },
|
|
45
|
+
'xml': { type: 'html' },
|
|
46
|
+
'svg': { type: 'html' },
|
|
47
|
+
'markdown': { type: 'html' }, // For metadata in Markdown files
|
|
48
|
+
|
|
43
49
|
// Other language styles
|
|
44
50
|
'lisp': { type: 'semicolon' },
|
|
45
51
|
'clojure': { type: 'semicolon' },
|
|
@@ -58,5 +64,3 @@ const COMMENT_STYLES = {
|
|
|
58
64
|
module.exports = {
|
|
59
65
|
COMMENT_STYLES
|
|
60
66
|
};
|
|
61
|
-
|
|
62
|
-
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Component: CodeBlockUtils Header Utilities
|
|
3
3
|
* Block-UUID: 2565f708-feec-4b59-88fd-984084410fd4
|
|
4
4
|
* Parent-UUID: 01147ddf-320f-498a-a744-198d42a9d2ee
|
|
5
|
-
* Version: 1.2.
|
|
5
|
+
* Version: 1.2.1
|
|
6
6
|
* Description: Provides utility functions for parsing metadata headers from code blocks, validating timestamps, and calculating header line counts.
|
|
7
7
|
* Language: JavaScript
|
|
8
8
|
* Created-at: 2025-07-21T00:23:28.169Z
|
|
9
|
-
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.1.0), Gemini 2.5 Flash Thinking (v1.2.0)
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash Thinking (v1.1.0), Gemini 2.5 Flash Thinking (v1.2.0), Qwen 3 Coder 480B - Cerebras (v1.2.1)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
|
|
@@ -113,6 +113,22 @@ function parseHeader(header, language) {
|
|
|
113
113
|
.map(line => line.replace(/^\s*'\s?/, ''))
|
|
114
114
|
.join('\n');
|
|
115
115
|
break;
|
|
116
|
+
|
|
117
|
+
// --- New Case for HTML/XML/Markdown Comments ---
|
|
118
|
+
case 'html':
|
|
119
|
+
// Remove opening <!-- and closing --> tags.
|
|
120
|
+
// The regex handles cases where the tags might be on the same line as content
|
|
121
|
+
// or on their own lines.
|
|
122
|
+
cleanHeader = header.replace(/^<!--\s*/, '')
|
|
123
|
+
.replace(/\s*-->$/, '')
|
|
124
|
+
.split('\n')
|
|
125
|
+
// Remove leading whitespace and potential dashes (common in HTML comments)
|
|
126
|
+
// This is a heuristic, as HTML comments don't typically have line prefixes.
|
|
127
|
+
// It's included to be robust against minor formatting variations.
|
|
128
|
+
.map(line => line.replace(/^\s*(-|\*)\s?/, ''))
|
|
129
|
+
.join('\n');
|
|
130
|
+
break;
|
|
131
|
+
// --- End of New Case ---
|
|
116
132
|
|
|
117
133
|
case 'unknown':
|
|
118
134
|
// Attempt basic detection if language wasn't found in COMMENT_STYLES
|
|
@@ -170,7 +186,7 @@ function parseHeader(header, language) {
|
|
|
170
186
|
const requiredFields = [
|
|
171
187
|
'Component',
|
|
172
188
|
'Block-UUID',
|
|
173
|
-
'Version',
|
|
189
|
+
'Version',
|
|
174
190
|
'Language',
|
|
175
191
|
'Created-at',
|
|
176
192
|
'Authors'
|
package/src/ContextUtils.js
CHANGED
|
@@ -141,7 +141,7 @@ function parseContextSection(sectionText) {
|
|
|
141
141
|
if (properties.includes(property)) {
|
|
142
142
|
let value = trimmedLine.split(':').slice(1).join(':').trim();
|
|
143
143
|
|
|
144
|
-
if (property.match(/id/))
|
|
144
|
+
if (property.match(/id/) || property.match(/tokens/))
|
|
145
145
|
value = parseInt(value);
|
|
146
146
|
|
|
147
147
|
contextSection[property] = value;
|
package/src/DomUtils.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component: DomUtils Helper Functions
|
|
3
|
+
* Block-UUID: e85d4e1d-f45e-450b-a0f2-941af362c2be
|
|
4
|
+
* Parent-UUID: fe462d17-e1c8-45d6-9fb0-f53322c9cda9
|
|
5
|
+
* Version: 1.1.0
|
|
6
|
+
* Description: Provides helper functions for creating and manipulating DOM elements.
|
|
7
|
+
* Language: JavaScript
|
|
8
|
+
* Created-at: 2025-09-11T19:03:50.026Z
|
|
9
|
+
* Authors: Gemini 2.5 Pro (v1.0.0), Gemini 2.5 Flash (v1.1.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
|
+
role,
|
|
23
|
+
html,
|
|
24
|
+
text,
|
|
25
|
+
cls,
|
|
26
|
+
className,
|
|
27
|
+
colSpan,
|
|
28
|
+
pointerEvents,
|
|
29
|
+
title,
|
|
30
|
+
style,
|
|
31
|
+
append,
|
|
32
|
+
type: inputType,
|
|
33
|
+
value,
|
|
34
|
+
name,
|
|
35
|
+
checked,
|
|
36
|
+
selected,
|
|
37
|
+
disabled,
|
|
38
|
+
placeholder,
|
|
39
|
+
"for": _for,
|
|
40
|
+
"data-message-id": dmi,
|
|
41
|
+
"data-message-role": dmr,
|
|
42
|
+
attrs = {},
|
|
43
|
+
dataset,
|
|
44
|
+
} = params;
|
|
45
|
+
|
|
46
|
+
// Set standard attributes
|
|
47
|
+
if (id != null) elem.id = id;
|
|
48
|
+
if (ariaLabel) elem.setAttribute("aria-label", ariaLabel);
|
|
49
|
+
if (role) elem.setAttribute("role", role);
|
|
50
|
+
if (placeholder) elem.setAttribute("placeholder", placeholder);
|
|
51
|
+
if (cls || className) elem.setAttribute("class", cls || className);
|
|
52
|
+
if (pointerEvents) elem.setAttribute("pointer-events", pointerEvents);
|
|
53
|
+
if (title != null) elem.setAttribute("title", title);
|
|
54
|
+
if (inputType) elem.setAttribute("type", inputType);
|
|
55
|
+
if (name != null) elem.setAttribute("name", name);
|
|
56
|
+
if (value != null) elem.value = value;
|
|
57
|
+
if (checked != null && checked) elem.checked = true; // Use property for checked
|
|
58
|
+
if (selected != null && selected) elem.selected = true; // Use property for selected
|
|
59
|
+
if (disabled != null) elem.disabled = disabled; // Use property for disabled
|
|
60
|
+
if (dmi != null) elem.setAttribute("data-message-id", dmi);
|
|
61
|
+
if (dmr != null) elem.setAttribute("data-message-role", dmr);
|
|
62
|
+
if (_for != null ) elem.setAttribute("for", _for);
|
|
63
|
+
if (colSpan != null) elem.colSpan = colSpan;
|
|
64
|
+
|
|
65
|
+
// Set content
|
|
66
|
+
if (html != null) {
|
|
67
|
+
if (typeof html === "object" && html instanceof Node) { // Ensure html is a Node
|
|
68
|
+
elem.appendChild(html);
|
|
69
|
+
} else if (typeof html === "string") {
|
|
70
|
+
elem.innerHTML = html;
|
|
71
|
+
}
|
|
72
|
+
} else if (text != null) {
|
|
73
|
+
elem.innerText = text;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Set style properties
|
|
77
|
+
if (style) {
|
|
78
|
+
for (const name in style) {
|
|
79
|
+
elem.style[name] = style[name];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle append (now supports both arrays and single elements)
|
|
84
|
+
if (append) {
|
|
85
|
+
const appendItems = Array.isArray(append) ? append : [append];
|
|
86
|
+
appendItems.forEach(e => {
|
|
87
|
+
if (e instanceof Node) { // Ensure item to append is a Node
|
|
88
|
+
elem.appendChild(e);
|
|
89
|
+
} else if (e !== undefined && e !== null) {
|
|
90
|
+
console.warn("Attempted to append non-Node item:", e);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// NEW: Set additional attributes from attrs object
|
|
96
|
+
if (attrs && typeof attrs === 'object') {
|
|
97
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
98
|
+
if (value !== undefined && value !== null) {
|
|
99
|
+
// Skip if already set by standard properties or handled directly
|
|
100
|
+
if (!['id', 'class', 'role', 'aria-label', 'title', 'style', 'checked', 'selected', 'disabled', 'value', 'name', 'type', 'for'].includes(key) && !key.startsWith('data-')) {
|
|
101
|
+
elem.setAttribute(key, value);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Handle dataset properties
|
|
108
|
+
if (dataset && typeof dataset === 'object') {
|
|
109
|
+
for (const [key, value] of Object.entries(dataset)) {
|
|
110
|
+
if (value !== undefined && value !== null) {
|
|
111
|
+
elem.dataset[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle event handlers (like onclick, onchange, etc.)
|
|
117
|
+
for (const [key, value] of Object.entries(params)) {
|
|
118
|
+
// Check if the property is an event handler (starts with 'on')
|
|
119
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
120
|
+
const eventName = key.slice(2).toLowerCase(); // Remove 'on' prefix and convert to lowercase
|
|
121
|
+
elem.addEventListener(eventName, value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return elem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Update all creator functions to pass through attrs:
|
|
129
|
+
const h = {
|
|
130
|
+
elemById: (id) => {
|
|
131
|
+
return document.getElementById(id);
|
|
132
|
+
},
|
|
133
|
+
createA: (params) => {
|
|
134
|
+
return h.createLink(params);
|
|
135
|
+
},
|
|
136
|
+
createArticle: (params) => {
|
|
137
|
+
return createElement("article", params);
|
|
138
|
+
},
|
|
139
|
+
createBr: () => {
|
|
140
|
+
return createElement("br");
|
|
141
|
+
},
|
|
142
|
+
createBreak: () => {
|
|
143
|
+
return createElement("br");
|
|
144
|
+
},
|
|
145
|
+
createButton: (params) => {
|
|
146
|
+
return createElement("button", params);
|
|
147
|
+
},
|
|
148
|
+
createCheckbox: (params) => {
|
|
149
|
+
// Use createElement and let it handle attributes like checked/disabled
|
|
150
|
+
return createElement("input", { ...params, type: "checkbox" });
|
|
151
|
+
},
|
|
152
|
+
createCode: (params) => {
|
|
153
|
+
return createElement("code", params);
|
|
154
|
+
},
|
|
155
|
+
createDetail: (params) => {
|
|
156
|
+
return createElement("details", params);
|
|
157
|
+
},
|
|
158
|
+
createDiv: (params) => {
|
|
159
|
+
return createElement("div", params);
|
|
160
|
+
},
|
|
161
|
+
createEm: (params) => { // Added
|
|
162
|
+
return createElement("em", params);
|
|
163
|
+
},
|
|
164
|
+
createForm: (params) => {
|
|
165
|
+
return createElement("form", params);
|
|
166
|
+
},
|
|
167
|
+
createInput: (params) => {
|
|
168
|
+
return createElement("input", params);
|
|
169
|
+
},
|
|
170
|
+
createImg : (params) => {
|
|
171
|
+
const img = createElement("img", params);
|
|
172
|
+
// src is handled by createElement via attrs or direct property if needed
|
|
173
|
+
return img;
|
|
174
|
+
},
|
|
175
|
+
createH1: (params) => {
|
|
176
|
+
return createElement("h1", params);
|
|
177
|
+
},
|
|
178
|
+
createH2: (params) => {
|
|
179
|
+
return createElement("h2", params);
|
|
180
|
+
},
|
|
181
|
+
createH3: (params) => {
|
|
182
|
+
return createElement("h3", params);
|
|
183
|
+
},
|
|
184
|
+
createH4: (params) => {
|
|
185
|
+
return createElement("h4", params);
|
|
186
|
+
},
|
|
187
|
+
createH5: (params) => {
|
|
188
|
+
return createElement("h5", params);
|
|
189
|
+
},
|
|
190
|
+
createHeader: (header, params) => {
|
|
191
|
+
// Ensure header is a string like 'h1', 'h2' etc.
|
|
192
|
+
if (typeof header === 'string' && /^h[1-6]$/i.test(header)) {
|
|
193
|
+
return createElement(header, params);
|
|
194
|
+
}
|
|
195
|
+
console.error("Invalid header type provided to createHeader:", header);
|
|
196
|
+
return createElement("div", params); // Fallback or throw error
|
|
197
|
+
},
|
|
198
|
+
createHr: (params) => {
|
|
199
|
+
return createElement("hr", params);
|
|
200
|
+
},
|
|
201
|
+
createLabel : (params) => {
|
|
202
|
+
// 'for' is handled by createElement
|
|
203
|
+
return createElement("label", params);
|
|
204
|
+
},
|
|
205
|
+
createLI: (params) => {
|
|
206
|
+
return createElement("li", params);
|
|
207
|
+
},
|
|
208
|
+
createLi: (params) => { // Alias for createLI
|
|
209
|
+
return createElement("li", params);
|
|
210
|
+
},
|
|
211
|
+
createLink: (params) => {
|
|
212
|
+
const link = createElement("a", params);
|
|
213
|
+
// href and target are handled by createElement
|
|
214
|
+
if (params?.href) link.href = params.href; // Ensure href is set if provided
|
|
215
|
+
return link;
|
|
216
|
+
},
|
|
217
|
+
createNav: (params) => {
|
|
218
|
+
return createElement("nav", params);
|
|
219
|
+
},
|
|
220
|
+
createOL: (params) => { // Added
|
|
221
|
+
return createElement("ol", params);
|
|
222
|
+
},
|
|
223
|
+
createOption: (params) => { // Added
|
|
224
|
+
// value, text, selected are handled by createElement
|
|
225
|
+
return createElement("option", params);
|
|
226
|
+
},
|
|
227
|
+
createP: (params) => {
|
|
228
|
+
return createElement("p", params);
|
|
229
|
+
},
|
|
230
|
+
createParagraph: (params) => { // Alias for createP
|
|
231
|
+
return createElement("p", params);
|
|
232
|
+
},
|
|
233
|
+
createPre: (params) => {
|
|
234
|
+
return createElement("pre", params);
|
|
235
|
+
},
|
|
236
|
+
createSelect: (params) => { // Added
|
|
237
|
+
// name, disabled etc handled by createElement
|
|
238
|
+
return createElement("select", params);
|
|
239
|
+
},
|
|
240
|
+
createSpan: (params) => {
|
|
241
|
+
return createElement("span", params);
|
|
242
|
+
},
|
|
243
|
+
createStrong: (params) => { // Added
|
|
244
|
+
return createElement("strong", params);
|
|
245
|
+
},
|
|
246
|
+
createSummary: (params) => {
|
|
247
|
+
return createElement("summary", params);
|
|
248
|
+
},
|
|
249
|
+
createSup: (params) => {
|
|
250
|
+
return createElement("sup", params);
|
|
251
|
+
},
|
|
252
|
+
createText: (text) => {
|
|
253
|
+
return document.createTextNode(text);
|
|
254
|
+
},
|
|
255
|
+
createTextArea: (params) => {
|
|
256
|
+
const textArea = createElement("textarea", params);
|
|
257
|
+
return textArea;
|
|
258
|
+
},
|
|
259
|
+
createTextNode: (text) => {
|
|
260
|
+
return document.createTextNode(text);
|
|
261
|
+
},
|
|
262
|
+
createTable: (params) => {
|
|
263
|
+
return createElement("table", params);
|
|
264
|
+
},
|
|
265
|
+
createTableBody: (params) => {
|
|
266
|
+
return createElement("tbody", params);
|
|
267
|
+
},
|
|
268
|
+
createTableCell: (params) => {
|
|
269
|
+
return createElement("td", params);
|
|
270
|
+
},
|
|
271
|
+
createTableHead: (params) => {
|
|
272
|
+
return createElement("thead", params);
|
|
273
|
+
},
|
|
274
|
+
createTableHeadCell: (params) => {
|
|
275
|
+
return createElement("th", params);
|
|
276
|
+
},
|
|
277
|
+
createTbody: (params) => {
|
|
278
|
+
return createElement("tbody", params);
|
|
279
|
+
},
|
|
280
|
+
createTd: (params) => {
|
|
281
|
+
return createElement("td", params);
|
|
282
|
+
},
|
|
283
|
+
createTh: (params) => {
|
|
284
|
+
return createElement("th", params);
|
|
285
|
+
},
|
|
286
|
+
createThead: (params) => {
|
|
287
|
+
return createElement("thead", params);
|
|
288
|
+
},
|
|
289
|
+
createTr: (params) => {
|
|
290
|
+
return createElement("tr", params);
|
|
291
|
+
},
|
|
292
|
+
createTableRow: (params) => {
|
|
293
|
+
return createElement("tr", params);
|
|
294
|
+
},
|
|
295
|
+
createTextInput: (params) => {
|
|
296
|
+
// Use createElement and let it handle attributes like value/disabled
|
|
297
|
+
return createElement("input", { ...params, type: "text" });
|
|
298
|
+
},
|
|
299
|
+
createUl: (params) => {
|
|
300
|
+
return createElement("ul", params);
|
|
301
|
+
},
|
|
302
|
+
createUL: (params) => {
|
|
303
|
+
return createElement("ul", params);
|
|
304
|
+
},
|
|
305
|
+
hide: (elem) => {
|
|
306
|
+
if (elem && elem.style) {
|
|
307
|
+
elem.style.display = "none";
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
show: (elem) => {
|
|
311
|
+
if (elem && elem.style) {
|
|
312
|
+
elem.style.display = ""; // Reset to default display
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
updateDOM: (sourceNode, targetNode) => {
|
|
316
|
+
// Step 1: Check if the node type or tag name is different
|
|
317
|
+
if (!sourceNode || !targetNode) return; // Basic guard
|
|
318
|
+
if (sourceNode.nodeType !== targetNode.nodeType || sourceNode.nodeName !== targetNode.nodeName) {
|
|
319
|
+
targetNode.replaceWith(sourceNode.cloneNode(true));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Step 2: Check if it's an element node
|
|
324
|
+
if (sourceNode.nodeType === Node.ELEMENT_NODE) {
|
|
325
|
+
// Step 3: Compare and update attributes
|
|
326
|
+
let sourceAttributes = sourceNode.attributes;
|
|
327
|
+
let targetAttributes = targetNode.attributes;
|
|
328
|
+
const targetAttrMap = new Map();
|
|
329
|
+
for (let i = 0; i < targetAttributes.length; i++) {
|
|
330
|
+
targetAttrMap.set(targetAttributes[i].name, targetAttributes[i].value);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Update or add attributes from sourceNode to targetNode
|
|
334
|
+
for (let i = 0; i < sourceAttributes.length; i++) {
|
|
335
|
+
let attr = sourceAttributes[i];
|
|
336
|
+
if (targetAttrMap.get(attr.name) !== attr.value) {
|
|
337
|
+
targetNode.setAttribute(attr.name, attr.value);
|
|
338
|
+
}
|
|
339
|
+
targetAttrMap.delete(attr.name); // Mark as processed
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Remove attributes that are in target but not in source
|
|
343
|
+
for (const attrName of targetAttrMap.keys()) {
|
|
344
|
+
targetNode.removeAttribute(attrName);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Special handling for properties like value, checked, selected, disabled
|
|
348
|
+
if (sourceNode.value !== targetNode.value) {
|
|
349
|
+
targetNode.value = sourceNode.value;
|
|
350
|
+
}
|
|
351
|
+
if (sourceNode.checked !== targetNode.checked) {
|
|
352
|
+
targetNode.checked = sourceNode.checked;
|
|
353
|
+
}
|
|
354
|
+
if (sourceNode.selected !== targetNode.selected) {
|
|
355
|
+
targetNode.selected = sourceNode.selected;
|
|
356
|
+
}
|
|
357
|
+
if (sourceNode.disabled !== targetNode.disabled) {
|
|
358
|
+
targetNode.disabled = sourceNode.disabled;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
// Step 4: Recursively compare and update children
|
|
363
|
+
let sourceChildren = sourceNode.childNodes;
|
|
364
|
+
let targetChildren = targetNode.childNodes;
|
|
365
|
+
let maxLen = Math.max(sourceChildren.length, targetChildren.length);
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < maxLen; i++) {
|
|
368
|
+
if (i >= targetChildren.length) {
|
|
369
|
+
// If the target has fewer children, append the new child
|
|
370
|
+
if (sourceChildren[i]) {
|
|
371
|
+
targetNode.appendChild(sourceChildren[i].cloneNode(true));
|
|
372
|
+
}
|
|
373
|
+
} else if (i >= sourceChildren.length) {
|
|
374
|
+
// If the source has fewer children, remove extra target child
|
|
375
|
+
targetNode.removeChild(targetChildren[i]);
|
|
376
|
+
} else {
|
|
377
|
+
// Otherwise, update the existing child
|
|
378
|
+
h.updateDOM(sourceChildren[i], targetChildren[i]);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
} else if (sourceNode.nodeType === Node.TEXT_NODE) {
|
|
383
|
+
// Step 5: Update text content if the text nodes are different
|
|
384
|
+
if (sourceNode.nodeValue !== targetNode.nodeValue) {
|
|
385
|
+
targetNode.nodeValue = sourceNode.nodeValue;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Other node types (comments, etc.) are ignored for updates in this version
|
|
389
|
+
},
|
|
390
|
+
safeEscape: (html, x) => {
|
|
391
|
+
if ( x )
|
|
392
|
+
console.log(x);
|
|
393
|
+
|
|
394
|
+
if (typeof html !== 'string') return html; // Return non-strings as-is
|
|
395
|
+
|
|
396
|
+
// Generated by Claude 3.5 Sonnet
|
|
397
|
+
const preserveTags = [
|
|
398
|
+
'pre',
|
|
399
|
+
'code',
|
|
400
|
+
'script',
|
|
401
|
+
'style',
|
|
402
|
+
'textarea',
|
|
403
|
+
'math',
|
|
404
|
+
'mi',
|
|
405
|
+
'mo',
|
|
406
|
+
'mn',
|
|
407
|
+
'ms',
|
|
408
|
+
'mtext',
|
|
409
|
+
'samp',
|
|
410
|
+
'kbd',
|
|
411
|
+
'var',
|
|
412
|
+
'svg',
|
|
413
|
+
'xmp'
|
|
414
|
+
].join('|');
|
|
415
|
+
|
|
416
|
+
// Track whether we're inside a preserve tag
|
|
417
|
+
let isInPreserveTag = false;
|
|
418
|
+
let currentTag = '';
|
|
419
|
+
|
|
420
|
+
// Split the input into tokens that are either tags or text
|
|
421
|
+
// Improved regex to handle self-closing tags and attributes better
|
|
422
|
+
return html.split(/(<\/?(?:${preserveTags})(?:\s+[^>]*)?\/?>|<\/?[a-zA-Z][^>]*>)/i)
|
|
423
|
+
.filter(part => part) // Remove empty strings from split
|
|
424
|
+
.map(part => {
|
|
425
|
+
// Check if this part is a preserve tag
|
|
426
|
+
const preserveTagMatch = part.match(new RegExp(`^<(/?)(${preserveTags})([\\s>/])`, 'i'));
|
|
427
|
+
|
|
428
|
+
if (preserveTagMatch) {
|
|
429
|
+
// Toggle the preserve state based on opening/closing tags
|
|
430
|
+
if (!preserveTagMatch[1] && !part.endsWith('/>')) { // Opening tag, not self-closing
|
|
431
|
+
isInPreserveTag = true;
|
|
432
|
+
currentTag = preserveTagMatch[2];
|
|
433
|
+
} else if (preserveTagMatch[1]) { // Closing tag
|
|
434
|
+
// Only close if it matches the current open tag
|
|
435
|
+
if (isInPreserveTag && currentTag.toLowerCase() === preserveTagMatch[2].toLowerCase()) {
|
|
436
|
+
isInPreserveTag = false;
|
|
437
|
+
currentTag = '';
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return part; // Return the tag itself unmodified
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// If we're not in a preserve tag and this isn't an HTML tag, escape <
|
|
444
|
+
if (!isInPreserveTag && !part.startsWith('<')) {
|
|
445
|
+
return part.replace(/</g, '<');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Return other tags or content within preserve tags unmodified
|
|
449
|
+
return part;
|
|
450
|
+
})
|
|
451
|
+
.join('');
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Calculates the distance between an element's edge and the viewport's corresponding edge
|
|
456
|
+
* @param {HTMLElement} elem - The element to measure from
|
|
457
|
+
* @param {"left"|"right"} edge - Which edge to measure from
|
|
458
|
+
* @returns {number} The distance in pixels
|
|
459
|
+
* @throws {Error} If invalid edge parameter is provided or element not found
|
|
460
|
+
*/
|
|
461
|
+
getDistanceFromViewportEdge: (elem, edge = "right") => {
|
|
462
|
+
// Input validation
|
|
463
|
+
if (!elem || typeof elem.getBoundingClientRect !== 'function') {
|
|
464
|
+
throw new Error('Invalid element provided.');
|
|
465
|
+
}
|
|
466
|
+
if ( !["left", "right"].includes(edge) ) {
|
|
467
|
+
throw new Error('Edge parameter must be either "left" or "right"');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Get the bounding rectangle of the elem
|
|
471
|
+
const rect = elem.getBoundingClientRect();
|
|
472
|
+
|
|
473
|
+
// Get the width of the viewport
|
|
474
|
+
const viewportWidth = window.innerWidth;
|
|
475
|
+
|
|
476
|
+
// Calculate distance based on specified edge
|
|
477
|
+
if (edge === "right") {
|
|
478
|
+
return viewportWidth - rect.right;
|
|
479
|
+
} else { // edge === "left"
|
|
480
|
+
return rect.left;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
module.exports = { h };
|