@cplace/mcp-server 1.7.11 → 1.8.6
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/searchConversion.js +1 -1
- package/dist/searchConversion.js.map +1 -1
- package/dist/tools/board-widget.d.ts +50 -10
- package/dist/tools/board-widget.d.ts.map +1 -1
- package/dist/tools/common-schemas.d.ts +4 -5
- package/dist/tools/common-schemas.d.ts.map +1 -1
- package/dist/tools/common-schemas.js +39 -21
- package/dist/tools/common-schemas.js.map +1 -1
- package/dist/tools/csv-export.d.ts +472 -4
- package/dist/tools/csv-export.d.ts.map +1 -1
- package/dist/tools/csv-export.js +4 -8
- package/dist/tools/csv-export.js.map +1 -1
- package/dist/tools/jobs.d.ts +2 -2
- package/dist/tools/jobs.d.ts.map +1 -1
- package/dist/tools/jobs.js +3 -2
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/layout-richstring-shared.d.ts +7 -0
- package/dist/tools/layout-richstring-shared.d.ts.map +1 -0
- package/dist/tools/layout-richstring-shared.js +45 -0
- package/dist/tools/layout-richstring-shared.js.map +1 -0
- package/dist/tools/layout-richstring-translator.d.ts +36 -0
- package/dist/tools/layout-richstring-translator.d.ts.map +1 -0
- package/dist/tools/layout-richstring-translator.js +712 -0
- package/dist/tools/layout-richstring-translator.js.map +1 -0
- package/dist/tools/layout-richstring-widgets.d.ts +6 -112
- package/dist/tools/layout-richstring-widgets.d.ts.map +1 -1
- package/dist/tools/layout-richstring-widgets.js +9 -204
- package/dist/tools/layout-richstring-widgets.js.map +1 -1
- package/dist/tools/layout.d.ts +64 -16
- package/dist/tools/layout.d.ts.map +1 -1
- package/dist/tools/layout.js +77 -16
- package/dist/tools/layout.js.map +1 -1
- package/dist/tools/pages.d.ts +2 -2
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +3 -12
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/resource-planner-widget.d.ts +54 -14
- package/dist/tools/resource-planner-widget.d.ts.map +1 -1
- package/dist/tools/resource-planner-widget.js +52 -13
- package/dist/tools/resource-planner-widget.js.map +1 -1
- package/dist/tools/richstring-widgets.d.ts.map +1 -1
- package/dist/tools/richstring-widgets.js +2 -0
- package/dist/tools/richstring-widgets.js.map +1 -1
- package/dist/tools/search.d.ts +476 -8
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +8 -12
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/system.d.ts.map +1 -1
- package/dist/tools/system.js +2 -1
- package/dist/tools/system.js.map +1 -1
- package/dist/tools/type-management.js +1 -1
- package/dist/tools/type-management.js.map +1 -1
- package/dist/tools/version-history.d.ts +4 -4
- package/dist/tools/version-history.d.ts.map +1 -1
- package/dist/tools/version-history.js +5 -4
- package/dist/tools/version-history.js.map +1 -1
- package/dist/tools/widgets.d.ts +2 -2
- package/dist/tools/widgets.d.ts.map +1 -1
- package/dist/tools/widgets.js +3 -2
- package/dist/tools/widgets.js.map +1 -1
- package/dist/tools/workspace-core.d.ts +2 -2
- package/dist/tools/workspace-core.d.ts.map +1 -1
- package/dist/tools/workspace-core.js +3 -2
- package/dist/tools/workspace-core.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
import { richstringExtractWidgets } from './richstring-widgets.js';
|
|
2
|
+
import { isRichStringWidgetType, extractStringConfigValue, getScriptPropertyKey } from './layout-richstring-shared.js';
|
|
3
|
+
import { determineApiEndpoint, buildRequestParams, findWidgetById } from './widget-tool-helpers.js';
|
|
4
|
+
export class DecodedWidgetParseError extends Error {
|
|
5
|
+
constructor(msg) {
|
|
6
|
+
super(msg);
|
|
7
|
+
this.name = 'DecodedWidgetParseError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const DECODED_TAG_REGEX_SRC = '<embeddedwidget\\b([^>]*)>([\\s\\S]*?)<\\/embeddedwidget>';
|
|
11
|
+
const RAW_BASE64_TAG_REGEX = /<embeddedwidget>[-_a-zA-Z0-9]+<\/embeddedwidget>/;
|
|
12
|
+
const RAW_BASE64_TAG_REGEX_GLOBAL = /<embeddedwidget>[-_a-zA-Z0-9]+<\/embeddedwidget>/g;
|
|
13
|
+
export function formatDecodedTag(data) {
|
|
14
|
+
const idAttr = data.id ? ` id="${escapeAttr(data.id)}"` : '';
|
|
15
|
+
const typeAttr = ` type="${escapeAttr(data.widgetType)}"`;
|
|
16
|
+
const body = JSON.stringify({ properties: data.properties ?? {} });
|
|
17
|
+
return `<embeddedwidget${idAttr}${typeAttr}>${body}</embeddedwidget>`;
|
|
18
|
+
}
|
|
19
|
+
export function parseDecodedTag(tagText) {
|
|
20
|
+
const m = tagText.match(new RegExp(`^${DECODED_TAG_REGEX_SRC}$`));
|
|
21
|
+
if (!m)
|
|
22
|
+
throw new DecodedWidgetParseError('Not an embeddedwidget tag');
|
|
23
|
+
const attrs = parseAttrs(m[1]);
|
|
24
|
+
if (!attrs.type) {
|
|
25
|
+
throw new DecodedWidgetParseError('<embeddedwidget> requires type attribute in decoded form');
|
|
26
|
+
}
|
|
27
|
+
let body;
|
|
28
|
+
try {
|
|
29
|
+
body = JSON.parse(m[2]);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
throw new DecodedWidgetParseError(`Invalid JSON in embeddedwidget body (id=${attrs.id ?? 'new'}): ${e.message}`);
|
|
33
|
+
}
|
|
34
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
35
|
+
throw new DecodedWidgetParseError(`embeddedwidget body must be {"properties": {...}} (id=${attrs.id ?? 'new'})`);
|
|
36
|
+
}
|
|
37
|
+
const keys = Object.keys(body);
|
|
38
|
+
if (keys.length !== 1 || keys[0] !== 'properties') {
|
|
39
|
+
throw new DecodedWidgetParseError(`embeddedwidget body must have exactly one top-level key 'properties' (id=${attrs.id ?? 'new'})`);
|
|
40
|
+
}
|
|
41
|
+
if (!body.properties || typeof body.properties !== 'object' || Array.isArray(body.properties)) {
|
|
42
|
+
throw new DecodedWidgetParseError(`embeddedwidget body.properties must be an object (id=${attrs.id ?? 'new'})`);
|
|
43
|
+
}
|
|
44
|
+
return { id: attrs.id, widgetType: attrs.type, properties: body.properties };
|
|
45
|
+
}
|
|
46
|
+
export function findAllDecodedTags(content) {
|
|
47
|
+
const out = [];
|
|
48
|
+
if (!content)
|
|
49
|
+
return out;
|
|
50
|
+
const re = new RegExp(DECODED_TAG_REGEX_SRC, 'g');
|
|
51
|
+
let m;
|
|
52
|
+
while ((m = re.exec(content)) !== null) {
|
|
53
|
+
const raw = m[0];
|
|
54
|
+
out.push({
|
|
55
|
+
data: parseDecodedTag(raw),
|
|
56
|
+
raw,
|
|
57
|
+
start: m.index,
|
|
58
|
+
end: m.index + raw.length
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
export function containsRawBase64Tag(content) {
|
|
64
|
+
if (!content)
|
|
65
|
+
return false;
|
|
66
|
+
return RAW_BASE64_TAG_REGEX.test(content);
|
|
67
|
+
}
|
|
68
|
+
function parseAttrs(raw) {
|
|
69
|
+
const out = {};
|
|
70
|
+
const re = /(\w+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
71
|
+
let m;
|
|
72
|
+
while ((m = re.exec(raw)) !== null) {
|
|
73
|
+
const key = m[1].toLowerCase();
|
|
74
|
+
const val = m[2] ?? m[3] ?? '';
|
|
75
|
+
if (key === 'id')
|
|
76
|
+
out.id = val;
|
|
77
|
+
else if (key === 'type')
|
|
78
|
+
out.type = val;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function escapeAttr(v) {
|
|
83
|
+
return v.replace(/&/g, '&').replace(/"/g, '"');
|
|
84
|
+
}
|
|
85
|
+
export async function decodeRichStringsInLayout(client, layoutJson) {
|
|
86
|
+
if (!layoutJson || !Array.isArray(layoutJson.rows))
|
|
87
|
+
return layoutJson;
|
|
88
|
+
await walkLayoutRows(client, layoutJson.rows);
|
|
89
|
+
return layoutJson;
|
|
90
|
+
}
|
|
91
|
+
export async function decodeRichStringsInWidget(client, widget) {
|
|
92
|
+
if (!widget)
|
|
93
|
+
return widget;
|
|
94
|
+
if (isRichStringWidgetType(widget.widgetType)) {
|
|
95
|
+
await decodeConfigurationContent(client, widget.configuration);
|
|
96
|
+
}
|
|
97
|
+
if (widget.widgetsLayout?.rows) {
|
|
98
|
+
await walkLayoutRows(client, widget.widgetsLayout.rows);
|
|
99
|
+
}
|
|
100
|
+
return widget;
|
|
101
|
+
}
|
|
102
|
+
export async function decodeRichStringsInScript(client, script) {
|
|
103
|
+
if (!script)
|
|
104
|
+
return script;
|
|
105
|
+
if (!RAW_BASE64_TAG_REGEX.test(script))
|
|
106
|
+
return script;
|
|
107
|
+
return await decodeBase64TagsInScript(client, script);
|
|
108
|
+
}
|
|
109
|
+
async function walkLayoutRows(client, rows) {
|
|
110
|
+
for (const row of rows ?? []) {
|
|
111
|
+
for (const column of row?.columns ?? []) {
|
|
112
|
+
for (const widget of column?.widgets ?? []) {
|
|
113
|
+
await decodeRichStringsInWidget(client, widget);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function decodeConfigurationContent(client, configuration) {
|
|
119
|
+
if (!configuration)
|
|
120
|
+
return;
|
|
121
|
+
const content = extractStringConfigValue(configuration, 'content');
|
|
122
|
+
if (!content || !RAW_BASE64_TAG_REGEX.test(content))
|
|
123
|
+
return;
|
|
124
|
+
const decoded = await decodeBase64TagsInString(client, content);
|
|
125
|
+
writeBackContent(configuration, 'content', decoded);
|
|
126
|
+
}
|
|
127
|
+
function writeBackContent(configuration, key, value) {
|
|
128
|
+
if (Array.isArray(configuration)) {
|
|
129
|
+
const entry = configuration.find((e) => e?.name === key);
|
|
130
|
+
if (entry?.values?.[0]) {
|
|
131
|
+
if (typeof entry.values[0] === 'string') {
|
|
132
|
+
entry.values[0] = value;
|
|
133
|
+
}
|
|
134
|
+
else if (entry.values[0].string !== undefined) {
|
|
135
|
+
entry.values[0].string = value;
|
|
136
|
+
}
|
|
137
|
+
else if (entry.values[0].markup !== undefined) {
|
|
138
|
+
entry.values[0].markup = value;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
entry.values[0].string = value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const existing = configuration[key];
|
|
147
|
+
if (existing && typeof existing === 'object' && 'markup' in existing) {
|
|
148
|
+
existing.markup = value;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
configuration[key] = value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function decodeBase64TagsInString(client, content) {
|
|
155
|
+
const extract = await richstringExtractWidgets(client, content);
|
|
156
|
+
const widgets = extract?.embeddedWidgets ?? [];
|
|
157
|
+
if (widgets.length === 0)
|
|
158
|
+
return content;
|
|
159
|
+
const matches = [...content.matchAll(RAW_BASE64_TAG_REGEX_GLOBAL)];
|
|
160
|
+
if (matches.length !== widgets.length) {
|
|
161
|
+
throw new Error(`richstring decode mismatch: ${matches.length} base64 tags vs ${widgets.length} extracted widgets`);
|
|
162
|
+
}
|
|
163
|
+
let out = '';
|
|
164
|
+
let cursor = 0;
|
|
165
|
+
for (let i = 0; i < matches.length; i++) {
|
|
166
|
+
const m = matches[i];
|
|
167
|
+
const w = widgets[i];
|
|
168
|
+
out += content.substring(cursor, m.index);
|
|
169
|
+
out += formatDecodedTag({
|
|
170
|
+
id: w.widgetId,
|
|
171
|
+
widgetType: w.widgetType,
|
|
172
|
+
properties: w.properties ?? {}
|
|
173
|
+
});
|
|
174
|
+
cursor = m.index + m[0].length;
|
|
175
|
+
}
|
|
176
|
+
out += content.substring(cursor);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
async function decodeBase64TagsInScript(client, script) {
|
|
180
|
+
const extract = await richstringExtractWidgets(client, script);
|
|
181
|
+
const widgets = extract?.embeddedWidgets ?? [];
|
|
182
|
+
if (widgets.length === 0)
|
|
183
|
+
return script;
|
|
184
|
+
const matches = [...script.matchAll(RAW_BASE64_TAG_REGEX_GLOBAL)];
|
|
185
|
+
if (matches.length !== widgets.length) {
|
|
186
|
+
throw new Error(`richstring decode mismatch: ${matches.length} base64 tags vs ${widgets.length} extracted widgets`);
|
|
187
|
+
}
|
|
188
|
+
const mask = buildScriptMask(script);
|
|
189
|
+
let out = '';
|
|
190
|
+
let cursor = 0;
|
|
191
|
+
for (let i = 0; i < matches.length; i++) {
|
|
192
|
+
const m = matches[i];
|
|
193
|
+
const w = widgets[i];
|
|
194
|
+
const idx = m.index;
|
|
195
|
+
const decoded = formatDecodedTag({
|
|
196
|
+
id: w.widgetId,
|
|
197
|
+
widgetType: w.widgetType,
|
|
198
|
+
properties: w.properties ?? {}
|
|
199
|
+
});
|
|
200
|
+
let replacement = decoded;
|
|
201
|
+
if (mask[idx] === 1) {
|
|
202
|
+
const quote = findEnclosingLiteralQuote(script, mask, idx);
|
|
203
|
+
if (quote === '"' || quote === "'") {
|
|
204
|
+
replacement = escapeJsString(decoded, quote);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
out += script.substring(cursor, idx) + replacement;
|
|
208
|
+
cursor = idx + m[0].length;
|
|
209
|
+
}
|
|
210
|
+
out += script.substring(cursor);
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
function findEnclosingLiteralQuote(script, mask, idx) {
|
|
214
|
+
let i = idx - 1;
|
|
215
|
+
while (i >= 0 && mask[i] === 1)
|
|
216
|
+
i--;
|
|
217
|
+
const q = i + 1;
|
|
218
|
+
if (q >= script.length)
|
|
219
|
+
return null;
|
|
220
|
+
const c = script[q];
|
|
221
|
+
return c === '"' || c === "'" || c === '`' ? c : null;
|
|
222
|
+
}
|
|
223
|
+
function buildScriptMask(script) {
|
|
224
|
+
const mask = new Uint8Array(script.length);
|
|
225
|
+
let i = 0;
|
|
226
|
+
while (i < script.length) {
|
|
227
|
+
const c = script[i];
|
|
228
|
+
if (c === '/' && script[i + 1] === '/') {
|
|
229
|
+
while (i < script.length && script[i] !== '\n') {
|
|
230
|
+
mask[i++] = 2;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (c === '/' && script[i + 1] === '*') {
|
|
234
|
+
mask[i++] = 2;
|
|
235
|
+
mask[i++] = 2;
|
|
236
|
+
while (i < script.length && !(script[i] === '*' && script[i + 1] === '/')) {
|
|
237
|
+
mask[i++] = 2;
|
|
238
|
+
}
|
|
239
|
+
if (i < script.length) {
|
|
240
|
+
mask[i++] = 2;
|
|
241
|
+
mask[i++] = 2;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (c === '"' || c === "'" || c === '`') {
|
|
245
|
+
const quote = c;
|
|
246
|
+
mask[i++] = 1;
|
|
247
|
+
while (i < script.length) {
|
|
248
|
+
mask[i] = 1;
|
|
249
|
+
if (script[i] === '\\') {
|
|
250
|
+
i++;
|
|
251
|
+
if (i < script.length) {
|
|
252
|
+
mask[i] = 1;
|
|
253
|
+
i++;
|
|
254
|
+
}
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (script[i] === quote) {
|
|
258
|
+
i++;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
i++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
i++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return mask;
|
|
269
|
+
}
|
|
270
|
+
export function unescapeJsString(raw, _quote) {
|
|
271
|
+
let out = '';
|
|
272
|
+
let i = 0;
|
|
273
|
+
while (i < raw.length) {
|
|
274
|
+
const c = raw[i];
|
|
275
|
+
if (c === '\\' && i + 1 < raw.length) {
|
|
276
|
+
const n = raw[i + 1];
|
|
277
|
+
switch (n) {
|
|
278
|
+
case 'n':
|
|
279
|
+
out += '\n';
|
|
280
|
+
break;
|
|
281
|
+
case 'r':
|
|
282
|
+
out += '\r';
|
|
283
|
+
break;
|
|
284
|
+
case 't':
|
|
285
|
+
out += '\t';
|
|
286
|
+
break;
|
|
287
|
+
case 'b':
|
|
288
|
+
out += '\b';
|
|
289
|
+
break;
|
|
290
|
+
case 'f':
|
|
291
|
+
out += '\f';
|
|
292
|
+
break;
|
|
293
|
+
case 'v':
|
|
294
|
+
out += '\v';
|
|
295
|
+
break;
|
|
296
|
+
case '0':
|
|
297
|
+
out += '\0';
|
|
298
|
+
break;
|
|
299
|
+
case '\\':
|
|
300
|
+
out += '\\';
|
|
301
|
+
break;
|
|
302
|
+
case '"':
|
|
303
|
+
out += '"';
|
|
304
|
+
break;
|
|
305
|
+
case "'":
|
|
306
|
+
out += "'";
|
|
307
|
+
break;
|
|
308
|
+
case '`':
|
|
309
|
+
out += '`';
|
|
310
|
+
break;
|
|
311
|
+
case 'x': {
|
|
312
|
+
const hex = raw.substr(i + 2, 2);
|
|
313
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
314
|
+
i += 4;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
case 'u': {
|
|
318
|
+
if (raw[i + 2] === '{') {
|
|
319
|
+
const end = raw.indexOf('}', i + 3);
|
|
320
|
+
const hex = raw.substring(i + 3, end);
|
|
321
|
+
out += String.fromCodePoint(parseInt(hex, 16));
|
|
322
|
+
i = end + 1;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const hex = raw.substr(i + 2, 4);
|
|
327
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
328
|
+
i += 6;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
default:
|
|
333
|
+
out += n;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
i += 2;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
out += c;
|
|
340
|
+
i++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
export function escapeJsString(value, quote) {
|
|
346
|
+
let out = '';
|
|
347
|
+
for (let i = 0; i < value.length; i++) {
|
|
348
|
+
const c = value[i];
|
|
349
|
+
if (c === '\\')
|
|
350
|
+
out += '\\\\';
|
|
351
|
+
else if (c === quote)
|
|
352
|
+
out += '\\' + quote;
|
|
353
|
+
else if (c === '\n')
|
|
354
|
+
out += '\\n';
|
|
355
|
+
else if (c === '\r')
|
|
356
|
+
out += '\\r';
|
|
357
|
+
else if (c === '\t')
|
|
358
|
+
out += '\\t';
|
|
359
|
+
else if (c === '\b')
|
|
360
|
+
out += '\\b';
|
|
361
|
+
else if (c === '\f')
|
|
362
|
+
out += '\\f';
|
|
363
|
+
else if (c.charCodeAt(0) < 0x20) {
|
|
364
|
+
out += '\\x' + c.charCodeAt(0).toString(16).padStart(2, '0');
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
out += c;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
function scanSimpleStringKeys(script, mask, bodyStart, bodyEnd) {
|
|
373
|
+
const out = {};
|
|
374
|
+
let depth = 0;
|
|
375
|
+
let i = bodyStart;
|
|
376
|
+
while (i < bodyEnd) {
|
|
377
|
+
if (mask[i] === 1 || mask[i] === 2) {
|
|
378
|
+
i++;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const c = script[i];
|
|
382
|
+
if (c === '{') {
|
|
383
|
+
depth++;
|
|
384
|
+
i++;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (c === '}') {
|
|
388
|
+
depth--;
|
|
389
|
+
i++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (depth !== 0) {
|
|
393
|
+
i++;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (/[A-Za-z_$]/.test(c)) {
|
|
397
|
+
let j = i;
|
|
398
|
+
while (j < bodyEnd && /[A-Za-z0-9_$.]/.test(script[j]))
|
|
399
|
+
j++;
|
|
400
|
+
const key = script.substring(i, j);
|
|
401
|
+
let k = j;
|
|
402
|
+
while (k < bodyEnd && /\s/.test(script[k]))
|
|
403
|
+
k++;
|
|
404
|
+
if (script[k] === ':') {
|
|
405
|
+
k++;
|
|
406
|
+
while (k < bodyEnd && /\s/.test(script[k]))
|
|
407
|
+
k++;
|
|
408
|
+
if (script[k] === '"' || script[k] === "'") {
|
|
409
|
+
const quote = script[k];
|
|
410
|
+
const strStart = k + 1;
|
|
411
|
+
let e = strStart;
|
|
412
|
+
while (e < bodyEnd) {
|
|
413
|
+
if (script[e] === '\\') {
|
|
414
|
+
e += 2;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (script[e] === quote)
|
|
418
|
+
break;
|
|
419
|
+
e++;
|
|
420
|
+
}
|
|
421
|
+
out[key] = unescapeJsString(script.substring(strStart, e), quote);
|
|
422
|
+
i = e + 1;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
i = j;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
i++;
|
|
430
|
+
}
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
function findEnclosingObject(script, mask, anchor) {
|
|
434
|
+
let depth = 0;
|
|
435
|
+
let open = -1;
|
|
436
|
+
for (let i = anchor - 1; i >= 0; i--) {
|
|
437
|
+
if (mask[i] === 1 || mask[i] === 2)
|
|
438
|
+
continue;
|
|
439
|
+
const c = script[i];
|
|
440
|
+
if (c === '}')
|
|
441
|
+
depth++;
|
|
442
|
+
else if (c === '{') {
|
|
443
|
+
if (depth === 0) {
|
|
444
|
+
open = i;
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
depth--;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (open < 0)
|
|
451
|
+
return null;
|
|
452
|
+
let d = 1;
|
|
453
|
+
let close = -1;
|
|
454
|
+
for (let i = open + 1; i < script.length; i++) {
|
|
455
|
+
if (mask[i] === 1 || mask[i] === 2)
|
|
456
|
+
continue;
|
|
457
|
+
const c = script[i];
|
|
458
|
+
if (c === '{')
|
|
459
|
+
d++;
|
|
460
|
+
else if (c === '}') {
|
|
461
|
+
d--;
|
|
462
|
+
if (d === 0) {
|
|
463
|
+
close = i;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (close < 0)
|
|
469
|
+
return null;
|
|
470
|
+
return { open, close };
|
|
471
|
+
}
|
|
472
|
+
export function findRichStringContentLiterals(script) {
|
|
473
|
+
const mask = buildScriptMask(script);
|
|
474
|
+
const locations = [];
|
|
475
|
+
const seenLiteralStarts = new Set();
|
|
476
|
+
const keyRegex = /(?:['"]content['"]\s*:\s*|\bcontent\s*:\s*)/g;
|
|
477
|
+
let km;
|
|
478
|
+
while ((km = keyRegex.exec(script)) !== null) {
|
|
479
|
+
const matchStart = km.index;
|
|
480
|
+
const keyPos = (km[0][0] === "'" || km[0][0] === '"') ? matchStart + 1 : matchStart;
|
|
481
|
+
if (mask[keyPos] !== 0) {
|
|
482
|
+
const isQuotedKey = mask[keyPos] === 1 &&
|
|
483
|
+
keyPos >= 2 &&
|
|
484
|
+
mask[keyPos - 2] === 0 &&
|
|
485
|
+
(script[keyPos - 1] === "'" || script[keyPos - 1] === '"') &&
|
|
486
|
+
mask[keyPos - 1] === 1 &&
|
|
487
|
+
keyPos + 7 < script.length &&
|
|
488
|
+
script[keyPos + 7] === script[keyPos - 1] &&
|
|
489
|
+
mask[keyPos + 7] === 1;
|
|
490
|
+
if (!isQuotedKey)
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const afterKey = matchStart + km[0].length;
|
|
494
|
+
const nextCh = script[afterKey];
|
|
495
|
+
if (nextCh !== '"' && nextCh !== "'") {
|
|
496
|
+
if (nextCh === '`') {
|
|
497
|
+
throw new DecodedWidgetParseError("richstring widget `content` must use a plain string literal, not a template literal.\n" +
|
|
498
|
+
" ✅ content: '<embeddedwidget type=\"...\">...</embeddedwidget>'\n" +
|
|
499
|
+
" ❌ content: `<embeddedwidget type=\"...\">...</embeddedwidget>`\n" +
|
|
500
|
+
"Or use cplace_layout_richstring_* tools for programmatic edits.");
|
|
501
|
+
}
|
|
502
|
+
const enclosing = findEnclosingObject(script, mask, keyPos);
|
|
503
|
+
if (!enclosing)
|
|
504
|
+
continue;
|
|
505
|
+
const keys = scanSimpleStringKeys(script, mask, enclosing.open + 1, enclosing.close);
|
|
506
|
+
const widgetType = keys.type || keys.widgetType;
|
|
507
|
+
if (widgetType && isRichStringWidgetType(widgetType)) {
|
|
508
|
+
throw new DecodedWidgetParseError("richstring widget `content` must be a plain string literal — variables, concatenation, and expressions are not supported.\n" +
|
|
509
|
+
" ✅ content: '<embeddedwidget type=\"...\">...</embeddedwidget>'\n" +
|
|
510
|
+
" ❌ content: myVar\n" +
|
|
511
|
+
"Or use cplace_layout_richstring_* tools for programmatic edits.");
|
|
512
|
+
}
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const quote = nextCh;
|
|
516
|
+
const literalStart = afterKey;
|
|
517
|
+
let e = literalStart + 1;
|
|
518
|
+
while (e < script.length) {
|
|
519
|
+
if (script[e] === '\\') {
|
|
520
|
+
e += 2;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (script[e] === quote)
|
|
524
|
+
break;
|
|
525
|
+
e++;
|
|
526
|
+
}
|
|
527
|
+
const literalEnd = e + 1;
|
|
528
|
+
if (seenLiteralStarts.has(literalStart))
|
|
529
|
+
continue;
|
|
530
|
+
let currentAnchor = keyPos;
|
|
531
|
+
let widgetType;
|
|
532
|
+
let widgetId;
|
|
533
|
+
let viaUpdateWidget = false;
|
|
534
|
+
let foundContext = false;
|
|
535
|
+
for (let hop = 0; hop < 6; hop++) {
|
|
536
|
+
const enclosing = findEnclosingObject(script, mask, currentAnchor);
|
|
537
|
+
if (!enclosing)
|
|
538
|
+
break;
|
|
539
|
+
const keys = scanSimpleStringKeys(script, mask, enclosing.open + 1, enclosing.close);
|
|
540
|
+
const ty = keys.type || keys.widgetType;
|
|
541
|
+
if (ty) {
|
|
542
|
+
if (isRichStringWidgetType(ty)) {
|
|
543
|
+
widgetType = ty;
|
|
544
|
+
widgetId = keys.id;
|
|
545
|
+
foundContext = true;
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
const uw = detectUpdateWidgetCall(script, mask, enclosing.open);
|
|
550
|
+
if (uw) {
|
|
551
|
+
widgetId = uw.widgetId;
|
|
552
|
+
viaUpdateWidget = true;
|
|
553
|
+
foundContext = true;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
currentAnchor = enclosing.open;
|
|
557
|
+
}
|
|
558
|
+
if (!foundContext) {
|
|
559
|
+
const literalText = unescapeJsString(script.substring(literalStart + 1, e), quote);
|
|
560
|
+
if (literalText.includes('<embeddedwidget')) {
|
|
561
|
+
throw new DecodedWidgetParseError(`Found a \`content:\` string literal at position ${literalStart} with a decoded <embeddedwidget> ` +
|
|
562
|
+
`tag but could not resolve its richstring widget context. ` +
|
|
563
|
+
`The enclosing object must have a \`type:\` or \`widgetType:\` key matching a richstring widget, ` +
|
|
564
|
+
`or the literal must be the second argument of \`layout.updateWidget("<id>", { content: "..." })\`.`);
|
|
565
|
+
}
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
locations.push({
|
|
569
|
+
widgetId,
|
|
570
|
+
widgetType,
|
|
571
|
+
viaUpdateWidget,
|
|
572
|
+
literalStart,
|
|
573
|
+
literalEnd,
|
|
574
|
+
quote,
|
|
575
|
+
content: unescapeJsString(script.substring(literalStart + 1, e), quote)
|
|
576
|
+
});
|
|
577
|
+
seenLiteralStarts.add(literalStart);
|
|
578
|
+
}
|
|
579
|
+
return locations;
|
|
580
|
+
}
|
|
581
|
+
function detectUpdateWidgetCall(script, mask, objOpen) {
|
|
582
|
+
let i = objOpen - 1;
|
|
583
|
+
while (i >= 0 && (mask[i] === 1 || mask[i] === 2 || /\s/.test(script[i])))
|
|
584
|
+
i--;
|
|
585
|
+
if (i < 0 || script[i] !== ',')
|
|
586
|
+
return null;
|
|
587
|
+
i--;
|
|
588
|
+
while (i >= 0 && /\s/.test(script[i]) && mask[i] === 0)
|
|
589
|
+
i--;
|
|
590
|
+
if (i < 0 || (script[i] !== '"' && script[i] !== "'"))
|
|
591
|
+
return null;
|
|
592
|
+
const closeQuote = script[i];
|
|
593
|
+
let strStart = i - 1;
|
|
594
|
+
while (strStart >= 0 && !(script[strStart] === closeQuote && script[strStart - 1] !== '\\')) {
|
|
595
|
+
strStart--;
|
|
596
|
+
}
|
|
597
|
+
if (strStart < 0)
|
|
598
|
+
return null;
|
|
599
|
+
const widgetId = unescapeJsString(script.substring(strStart + 1, i), closeQuote);
|
|
600
|
+
i = strStart - 1;
|
|
601
|
+
while (i >= 0 && /\s/.test(script[i]) && mask[i] === 0)
|
|
602
|
+
i--;
|
|
603
|
+
if (i < 0 || script[i] !== '(')
|
|
604
|
+
return null;
|
|
605
|
+
i--;
|
|
606
|
+
const fnName = 'updateWidget';
|
|
607
|
+
const fnEnd = i + 1;
|
|
608
|
+
const fnStart = fnEnd - fnName.length;
|
|
609
|
+
if (fnStart < 0)
|
|
610
|
+
return null;
|
|
611
|
+
if (script.substring(fnStart, fnEnd) !== fnName)
|
|
612
|
+
return null;
|
|
613
|
+
return { widgetId };
|
|
614
|
+
}
|
|
615
|
+
export async function encodeRichStringsInScript(client, context, script) {
|
|
616
|
+
if (!script)
|
|
617
|
+
return script;
|
|
618
|
+
if (containsRawBase64Tag(script)) {
|
|
619
|
+
throw new DecodedWidgetParseError('Raw base64 <embeddedwidget> tags are not accepted on writes. Use the decoded form:\n' +
|
|
620
|
+
' <embeddedwidget id="…" type="…">{"properties":{…}}</embeddedwidget>\n' +
|
|
621
|
+
'To fetch current decoded form, call cplace_get_layout_script or cplace_get_layout_widget_details.');
|
|
622
|
+
}
|
|
623
|
+
const locations = findRichStringContentLiterals(script);
|
|
624
|
+
const mask = buildScriptMask(script);
|
|
625
|
+
assertNoStrayDecodedTag(script, mask, locations);
|
|
626
|
+
if (locations.length === 0)
|
|
627
|
+
return script;
|
|
628
|
+
let cachedLayout = null;
|
|
629
|
+
const getLayout = async () => {
|
|
630
|
+
if (cachedLayout === null) {
|
|
631
|
+
const endpoint = determineApiEndpoint(context);
|
|
632
|
+
cachedLayout = await client.makeApiRequest(endpoint, 'GET', buildRequestParams(context));
|
|
633
|
+
}
|
|
634
|
+
return cachedLayout;
|
|
635
|
+
};
|
|
636
|
+
const sorted = [...locations].sort((a, b) => b.literalStart - a.literalStart);
|
|
637
|
+
let modifiedScript = script;
|
|
638
|
+
for (const loc of sorted) {
|
|
639
|
+
const encoded = await encodeSingleLocation(client, loc, getLayout);
|
|
640
|
+
if (encoded === null)
|
|
641
|
+
continue;
|
|
642
|
+
const replacement = loc.quote + escapeJsString(encoded, loc.quote) + loc.quote;
|
|
643
|
+
modifiedScript =
|
|
644
|
+
modifiedScript.substring(0, loc.literalStart) +
|
|
645
|
+
replacement +
|
|
646
|
+
modifiedScript.substring(loc.literalEnd);
|
|
647
|
+
}
|
|
648
|
+
return modifiedScript;
|
|
649
|
+
}
|
|
650
|
+
async function encodeSingleLocation(client, loc, getLayout) {
|
|
651
|
+
let currentContent;
|
|
652
|
+
if (loc.widgetId) {
|
|
653
|
+
const layout = await getLayout();
|
|
654
|
+
const normalized = loc.widgetId.startsWith('_') ? loc.widgetId : '_' + loc.widgetId;
|
|
655
|
+
const w = findWidgetById(layout.rows ?? [], normalized) ??
|
|
656
|
+
findWidgetById(layout.rows ?? [], loc.widgetId);
|
|
657
|
+
if (w) {
|
|
658
|
+
if (loc.viaUpdateWidget && !isRichStringWidgetType(w.widgetType)) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
currentContent = extractStringConfigValue(w.configuration, 'content') || undefined;
|
|
662
|
+
}
|
|
663
|
+
else if (loc.viaUpdateWidget) {
|
|
664
|
+
throw new DecodedWidgetParseError(`layout.updateWidget references unknown widget id '${loc.widgetId}'`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
for (const match of findAllDecodedTags(loc.content)) {
|
|
668
|
+
const scriptKey = getScriptPropertyKey(match.data.widgetType);
|
|
669
|
+
if (!scriptKey)
|
|
670
|
+
continue;
|
|
671
|
+
const value = match.data.properties[scriptKey];
|
|
672
|
+
if (Array.isArray(value)) {
|
|
673
|
+
const idFragment = match.data.id ? ` id="${match.data.id}"` : '';
|
|
674
|
+
throw new DecodedWidgetParseError(`<embeddedwidget type="${match.data.widgetType}"${idFragment}> has ` +
|
|
675
|
+
`"${scriptKey}" as an array — hand-built embeddedwidget JSON must use ` +
|
|
676
|
+
`a plain string here.\n` +
|
|
677
|
+
`❌ {"properties":{"${scriptKey}":["return 1;"]}}\n` +
|
|
678
|
+
`✅ {"properties":{"${scriptKey}":"return 1;"}}\n` +
|
|
679
|
+
`(The array form is an input convenience for cplace_richstring_insert_widget ` +
|
|
680
|
+
`that the backend unwraps. When you hand-build <embeddedwidget> JSON ` +
|
|
681
|
+
`inside layout scripts, use the plain-string form above.)`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const response = await client.makeApiRequest('json/richstring-embedded-widget/encode-inline', 'POST', undefined, { content: loc.content, currentContent });
|
|
685
|
+
if (response?.error) {
|
|
686
|
+
throw new DecodedWidgetParseError(response.error);
|
|
687
|
+
}
|
|
688
|
+
return response.content;
|
|
689
|
+
}
|
|
690
|
+
function assertNoStrayDecodedTag(script, mask, locations) {
|
|
691
|
+
const ranges = locations
|
|
692
|
+
.map(l => [l.literalStart, l.literalEnd])
|
|
693
|
+
.sort((a, b) => a[0] - b[0]);
|
|
694
|
+
const needle = '<embeddedwidget';
|
|
695
|
+
let from = 0;
|
|
696
|
+
while (true) {
|
|
697
|
+
const idx = script.indexOf(needle, from);
|
|
698
|
+
if (idx < 0)
|
|
699
|
+
return;
|
|
700
|
+
from = idx + needle.length;
|
|
701
|
+
if (mask[idx] === 2)
|
|
702
|
+
continue;
|
|
703
|
+
const insideClassified = ranges.some(([s, e]) => idx >= s && idx < e);
|
|
704
|
+
if (insideClassified)
|
|
705
|
+
continue;
|
|
706
|
+
throw new DecodedWidgetParseError("<embeddedwidget> tag found outside a content: value. Tags must appear directly inline:\n" +
|
|
707
|
+
" ✅ layout.updateWidget(\"id\", { content: '<embeddedwidget type=\"...\">...</embeddedwidget>' });\n" +
|
|
708
|
+
" ❌ var tag = '<embeddedwidget ...>'; layout.updateWidget(\"id\", { content: tag });\n" +
|
|
709
|
+
"Or use cplace_layout_richstring_* tools for programmatic edits.");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
//# sourceMappingURL=layout-richstring-translator.js.map
|