slidemind 0.3.17 → 0.3.18
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidemind",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"description": "Connect Claude Code to the slidemind PowerPoint add-in — build and edit native, on-brand PowerPoint decks against the active corporate design, from the terminal.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "statworx",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidemind",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"description": "Connect Codex to the slidemind PowerPoint add-in — build and edit native, on-brand PowerPoint decks against the active corporate design. Requires the slidemind CLI (npm install -g slidemind); the skills explain the install when it is missing.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "statworx",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidemind",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"description": "Connect Claude Code, Codex & opencode to the slidemind PowerPoint add-in — let your coding agent build and edit native, on-brand PowerPoint decks against your active corporate design, from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/builders/build.mjs
CHANGED
|
@@ -106,7 +106,7 @@ function convertedHostStubs(slideFiles, conversions) {
|
|
|
106
106
|
return [...hosts].map(
|
|
107
107
|
([name, { w, h }]) =>
|
|
108
108
|
`<script type="text/jsx-template" data-kind="layout" data-name="${name}" data-binding="${name}">\n` +
|
|
109
|
-
`function ${name}({ background, children }) { return <div style={{ position: "relative", width: "${w}", height: "${h}", overflow: "hidden", background: background || "#FFFFFF" }}>{children}</div>; }\n` +
|
|
109
|
+
`export default function ${name}({ background, children }) { return <div style={{ position: "relative", width: "${w}", height: "${h}", overflow: "hidden", background: background || "#FFFFFF" }}>{children}</div>; }\n` +
|
|
110
110
|
`</script>`,
|
|
111
111
|
);
|
|
112
112
|
}
|
package/src/lib/adopt.mjs
CHANGED
|
@@ -30,7 +30,14 @@ import { decodeSourceTags, mintSlideGuid, readSlideTags } from "../pptx/tags.mjs
|
|
|
30
30
|
import { foldPickedSlides } from "../pptx/slide-swap.mjs";
|
|
31
31
|
import { readDeckBundleRef, writeDeckBundleRef } from "../bridge/bundle.mjs";
|
|
32
32
|
import { loadSlideGuids, saveSlideGuids, slideGuidEntry } from "./slide-guids.mjs";
|
|
33
|
-
import {
|
|
33
|
+
import { writeConversionRecord } from "./conversions.mjs";
|
|
34
|
+
|
|
35
|
+
// Converted JSX needs a fresh baseline against the imported PPTX. Source tags
|
|
36
|
+
// alone cannot recover the original conversion evidence or preserved natives.
|
|
37
|
+
export function needsConversion(decoded) {
|
|
38
|
+
return !decoded || /<ConvertedHost\d+x\d+\b/.test(decoded.jsx) ||
|
|
39
|
+
/^\s*\/\/\s*SLIDEMIND_DIALECT:\s*converted\b/m.test(decoded.jsx);
|
|
40
|
+
}
|
|
34
41
|
|
|
35
42
|
export const LIVE_DECK_UNSUPPORTED =
|
|
36
43
|
"conversion of live presentations requires the add-in path in v1 — " +
|
|
@@ -174,21 +181,13 @@ export function adoptDeckFromPptx({ pptxPath, deckDir, converted = null, source
|
|
|
174
181
|
const native = [];
|
|
175
182
|
let bundle = null;
|
|
176
183
|
|
|
177
|
-
// Fold
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// that stays native, a CONVERTED slide whose preserved natives (OLE /
|
|
181
|
-
// SmartArt / unsupported chart kinds) need their verbatim source package kept
|
|
182
|
-
// for the render-time splice (design D4/D6), and a converted slide carrying
|
|
183
|
-
// the deck's own chart as `<Chart data-sm-source-id>`, which the render
|
|
184
|
-
// patches against that same package (native-charts 5.4a).
|
|
184
|
+
// Fold every converted slide in one pass. The package supplies preserved
|
|
185
|
+
// natives AND the original envelope (layouts, passive third-party tags,
|
|
186
|
+
// relationships). Tagged converted JSX alone cannot restore either safely.
|
|
185
187
|
const picks = [];
|
|
186
188
|
decodedByIndex.forEach((decoded, index) => {
|
|
187
|
-
if (decoded) return;
|
|
188
|
-
|
|
189
|
-
if (!conv || conv.preserved?.length > 0 || hasSourcedChart(conv.jsx)) {
|
|
190
|
-
picks.push(index + 1);
|
|
191
|
-
}
|
|
189
|
+
if (!needsConversion(decoded)) return;
|
|
190
|
+
picks.push(index + 1);
|
|
192
191
|
});
|
|
193
192
|
const foldByPick = new Map(
|
|
194
193
|
(picks.length
|
|
@@ -229,7 +228,7 @@ export function adoptDeckFromPptx({ pptxPath, deckDir, converted = null, source
|
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
decodedByIndex.forEach((decoded, index) => {
|
|
232
|
-
if (decoded) {
|
|
231
|
+
if (decoded && !needsConversion(decoded)) {
|
|
233
232
|
const stem = stemFor(decoded.meta.name, index, taken);
|
|
234
233
|
let jsx = decoded.jsx;
|
|
235
234
|
for (const asset of decoded.assets) {
|
|
@@ -251,11 +250,14 @@ export function adoptDeckFromPptx({ pptxPath, deckDir, converted = null, source
|
|
|
251
250
|
if (conv) {
|
|
252
251
|
// Deterministically converted: editable jsx in the converted dialect,
|
|
253
252
|
// plus the identity/evidence record push threads back to the server.
|
|
254
|
-
const stem = stemFor(conv.name ?? `${pad2(index + 1)}-converted`, index, taken);
|
|
253
|
+
const stem = stemFor(decoded?.meta.name ?? conv.name ?? `${pad2(index + 1)}-converted`, index, taken);
|
|
255
254
|
let jsx = conv.jsx;
|
|
256
255
|
for (const [from, to] of convAssetRenames) jsx = jsx.split(from).join(to);
|
|
257
256
|
fs.writeFileSync(path.join(slidesDir, `${stem}.jsx`), jsx, "utf8");
|
|
258
|
-
const
|
|
257
|
+
const candidateGuid = decoded?.guid ?? conv.guid;
|
|
258
|
+
const owns = !decoded || ownerIndexByGuid.get(decoded.guid)?.index === index;
|
|
259
|
+
const guid = owns && candidateGuid && !seenGuids.has(candidateGuid)
|
|
260
|
+
? candidateGuid : mintSlideGuid();
|
|
259
261
|
seenGuids.add(guid);
|
|
260
262
|
guids[`slides/${stem}.jsx`] = slideGuidEntry(guid, jsx);
|
|
261
263
|
writeConversionRecord(
|
|
@@ -277,6 +279,7 @@ export function adoptDeckFromPptx({ pptxPath, deckDir, converted = null, source
|
|
|
277
279
|
},
|
|
278
280
|
fold?.bytes ?? null,
|
|
279
281
|
);
|
|
282
|
+
bundle ??= decoded?.meta.bundle ?? null;
|
|
280
283
|
convertedFiles.push(`${stem}.jsx`);
|
|
281
284
|
return;
|
|
282
285
|
}
|
|
@@ -329,7 +332,10 @@ export function adoptDeckFromPptx({ pptxPath, deckDir, converted = null, source
|
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
/**
|
|
332
|
-
*
|
|
335
|
+
* Convert foreign slides and re-convert embedded converted slides against the
|
|
336
|
+
* CURRENT imported PPTX. This recovers a fresh baseline/evidence pair without
|
|
337
|
+
* pretending the original conversion was reviewed. Ordinary authored JSX is
|
|
338
|
+
* restored verbatim. Requests go through the add-in's
|
|
333
339
|
* `POST /api/cli/brands/:brandId/convert-pptx` route. Returns the route result
|
|
334
340
|
* (`{ slides, assets, skipped, warnings }` + the `brandId` it ran against) to
|
|
335
341
|
* hand to {@link adoptDeckFromPptx}, or null when conversion is not available
|
|
@@ -349,7 +355,7 @@ export async function convertUntaggedSlides({
|
|
|
349
355
|
const { buffer, decodedByIndex } = source ?? readSourceDeck(pptxPath);
|
|
350
356
|
const slides = [];
|
|
351
357
|
decodedByIndex.forEach((decoded, i) => {
|
|
352
|
-
if (
|
|
358
|
+
if (needsConversion(decoded)) slides.push(i + 1);
|
|
353
359
|
});
|
|
354
360
|
if (slides.length === 0) return null;
|
|
355
361
|
|
|
@@ -372,7 +378,7 @@ export async function convertUntaggedSlides({
|
|
|
372
378
|
if (brands.length !== 1) {
|
|
373
379
|
note(
|
|
374
380
|
brands.length === 0
|
|
375
|
-
? "no corporate design available to convert against —
|
|
381
|
+
? "no corporate design available to convert against — slides requiring conversion stay native"
|
|
376
382
|
: "several corporate designs available — pass `--brand <presFP>` to convert the untagged slides",
|
|
377
383
|
);
|
|
378
384
|
return null;
|
|
@@ -383,7 +389,7 @@ export async function convertUntaggedSlides({
|
|
|
383
389
|
// capability probe — do it here, so a whole .pptx is never shipped to a
|
|
384
390
|
// backend that will just 404 the route.
|
|
385
391
|
if (backendURL && !(await client.backendSupportsConvertPptx(backendURL))) {
|
|
386
|
-
note("this add-in does not convert foreign slides —
|
|
392
|
+
note("this add-in does not convert foreign slides — slides requiring conversion stay native");
|
|
387
393
|
return null;
|
|
388
394
|
}
|
|
389
395
|
}
|
|
@@ -396,15 +402,15 @@ export async function convertUntaggedSlides({
|
|
|
396
402
|
backendURL,
|
|
397
403
|
});
|
|
398
404
|
if (result?.ok === false) {
|
|
399
|
-
note("the add-in declined to convert this file —
|
|
405
|
+
note("the add-in declined to convert this file — slides requiring conversion stay native");
|
|
400
406
|
return null;
|
|
401
407
|
}
|
|
402
408
|
return { ...result, brandId: resolved };
|
|
403
409
|
} catch (err) {
|
|
404
410
|
note(
|
|
405
411
|
err?.code === "convert_route_unavailable"
|
|
406
|
-
? "this add-in does not convert foreign slides —
|
|
407
|
-
: `slide conversion unavailable (${err?.message ?? err}) —
|
|
412
|
+
? "this add-in does not convert foreign slides — slides requiring conversion stay native"
|
|
413
|
+
: `slide conversion unavailable (${err?.message ?? err}) — slides requiring conversion stay native`,
|
|
408
414
|
);
|
|
409
415
|
return null;
|
|
410
416
|
}
|
package/src/pptx/app-props.mjs
CHANGED
|
@@ -20,7 +20,6 @@ import { unzip, zip, readText } from "./zip.mjs";
|
|
|
20
20
|
import { slideOrderPaths, decodeXmlEntities } from "./ooxml.mjs";
|
|
21
21
|
import { slideText } from "./read-local.mjs";
|
|
22
22
|
import { stripHiddenOleGraphicFrames } from "./hidden-ole.mjs";
|
|
23
|
-
import { stripRiskyImageExtensions } from "./image-extensions.mjs";
|
|
24
23
|
|
|
25
24
|
const APP_PROPS = "docProps/app.xml";
|
|
26
25
|
|
|
@@ -61,9 +60,6 @@ export function normalizePptxAppProperties(buffer) {
|
|
|
61
60
|
if (stripHiddenOleGraphicFrames(entries)) {
|
|
62
61
|
changed = true;
|
|
63
62
|
}
|
|
64
|
-
if (stripRiskyImageExtensions(entries)) {
|
|
65
|
-
changed = true;
|
|
66
|
-
}
|
|
67
63
|
|
|
68
64
|
return {
|
|
69
65
|
bytes: changed ? zip(entries) : buffer,
|
package/src/pptx/slide-swap.mjs
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
resolvePartPath,
|
|
31
31
|
} from "./ooxml.mjs";
|
|
32
32
|
import { stripHiddenOleGraphicFrames } from "./hidden-ole.mjs";
|
|
33
|
-
import { stripRiskyImageExtensions } from "./image-extensions.mjs";
|
|
34
33
|
import {
|
|
35
34
|
slideHasForeignTags,
|
|
36
35
|
slideReferencesEmbeddedWorkbook,
|
|
@@ -112,7 +111,6 @@ export function foldPickedSlides(buffer, picks, { sourceName = "reused" } = {})
|
|
|
112
111
|
part === lowest ? picked : part === picked ? lowest : part,
|
|
113
112
|
) ?? false,
|
|
114
113
|
});
|
|
115
|
-
const riskyImagesStripped = stripRiskyImageExtensions(copy);
|
|
116
114
|
|
|
117
115
|
// Prune to the lowest slide's dependency closure + trimmed skeleton.
|
|
118
116
|
const { entries: pruned, changed } = pruneToLowest(copy);
|
|
@@ -123,7 +121,6 @@ export function foldPickedSlides(buffer, picks, { sourceName = "reused" } = {})
|
|
|
123
121
|
!swapped &&
|
|
124
122
|
!notesStripped &&
|
|
125
123
|
!hiddenOleStripped &&
|
|
126
|
-
!riskyImagesStripped &&
|
|
127
124
|
!changed
|
|
128
125
|
) {
|
|
129
126
|
return { selection: n, slideId, bytes: buffer };
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { readText } from "./zip.mjs";
|
|
2
|
-
import { relsFor, relsPathFor, resolvePartPath } from "./ooxml.mjs";
|
|
3
|
-
|
|
4
|
-
const CONTENT_TYPES = "[Content_Types].xml";
|
|
5
|
-
|
|
6
|
-
export function stripRiskyImageExtensions(files) {
|
|
7
|
-
let changed = false;
|
|
8
|
-
const removedTargets = new Set();
|
|
9
|
-
const candidates = Object.keys(files).filter((part) =>
|
|
10
|
-
/^ppt\/(?:slides|slideMasters|slideLayouts)\/[^/]+\.xml$/.test(part),
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
for (const part of candidates) {
|
|
14
|
-
const before = readText(files, part);
|
|
15
|
-
if (!before) continue;
|
|
16
|
-
|
|
17
|
-
const removedIds = new Set();
|
|
18
|
-
const relsById = new Map(relsFor(files, part).map((rel) => [rel.id, rel]));
|
|
19
|
-
const partDir = part.includes("/") ? part.slice(0, part.lastIndexOf("/")) : "";
|
|
20
|
-
let after = before.replace(
|
|
21
|
-
/<a:ext\b[\s\S]*?<\/a:ext>/g,
|
|
22
|
-
(ext) => {
|
|
23
|
-
if (!/<(?:asvg:svgBlip|a14:imgLayer)\b/.test(ext)) return ext;
|
|
24
|
-
for (const id of relationshipIdsIn(ext)) removedIds.add(id);
|
|
25
|
-
return "";
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
after = after.replace(/<a:extLst\b[^>]*>\s*<\/a:extLst>/g, "");
|
|
29
|
-
after = after.replace(
|
|
30
|
-
/<p:pic\b[\s\S]*?<\/p:pic>/g,
|
|
31
|
-
(pic) => {
|
|
32
|
-
if (!/<a:blip\b(?![^>]*\br:(?:embed|link)=)[^>]*>\s*<\/a:blip>/.test(pic)) {
|
|
33
|
-
return pic;
|
|
34
|
-
}
|
|
35
|
-
for (const id of relationshipIdsIn(pic)) removedIds.add(id);
|
|
36
|
-
return "";
|
|
37
|
-
},
|
|
38
|
-
);
|
|
39
|
-
after = after.replace(
|
|
40
|
-
/<p:pic\b[\s\S]*?<\/p:pic>/g,
|
|
41
|
-
(pic) => {
|
|
42
|
-
for (const id of relationshipIdsIn(pic)) {
|
|
43
|
-
const rel = relsById.get(id);
|
|
44
|
-
if (
|
|
45
|
-
rel &&
|
|
46
|
-
rel.targetMode !== "External" &&
|
|
47
|
-
/\.svg(?:$|[?#])/i.test(rel.target ?? "")
|
|
48
|
-
) {
|
|
49
|
-
removedIds.add(id);
|
|
50
|
-
return "";
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return pic;
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
|
-
const relsPath = relsPathFor(part);
|
|
57
|
-
const relsXml = readText(files, relsPath);
|
|
58
|
-
const remainingIds = relationshipIdsIn(after);
|
|
59
|
-
if (relsXml) {
|
|
60
|
-
for (const rel of relsById.values()) {
|
|
61
|
-
if (
|
|
62
|
-
rel.targetMode !== "External" &&
|
|
63
|
-
!remainingIds.has(rel.id) &&
|
|
64
|
-
/\.(?:svg|wdp)(?:$|[?#])/i.test(rel.target ?? "")
|
|
65
|
-
) {
|
|
66
|
-
removedIds.add(rel.id);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (after === before && removedIds.size === 0) continue;
|
|
72
|
-
|
|
73
|
-
if (after !== before) {
|
|
74
|
-
files[part] = new TextEncoder().encode(after);
|
|
75
|
-
changed = true;
|
|
76
|
-
}
|
|
77
|
-
if (!relsXml) continue;
|
|
78
|
-
|
|
79
|
-
let nextRels = relsXml;
|
|
80
|
-
for (const id of removedIds) {
|
|
81
|
-
if (remainingIds.has(id)) continue;
|
|
82
|
-
nextRels = removeRelationshipById(nextRels, id);
|
|
83
|
-
const rel = relsById.get(id);
|
|
84
|
-
if (rel && rel.targetMode !== "External") {
|
|
85
|
-
removedTargets.add(resolvePartPath(partDir, rel.target));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (nextRels !== relsXml) {
|
|
89
|
-
files[relsPath] = new TextEncoder().encode(nextRels);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const deleted = deleteUnreferencedTargets(files, removedTargets);
|
|
94
|
-
if (deleted.size > 0) {
|
|
95
|
-
removeContentTypeOverrides(files, deleted);
|
|
96
|
-
changed = true;
|
|
97
|
-
}
|
|
98
|
-
if (removeUnusedMediaDefaults(files, ["svg", "wdp"])) {
|
|
99
|
-
changed = true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return changed;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function deleteUnreferencedTargets(files, removedTargets) {
|
|
106
|
-
const remainingRefs = referencedInternalTargets(files);
|
|
107
|
-
const deleted = new Set();
|
|
108
|
-
for (const part of removedTargets) {
|
|
109
|
-
if (!part || remainingRefs.has(part) || files[part] === undefined) continue;
|
|
110
|
-
delete files[part];
|
|
111
|
-
const sidecar = relsPathFor(part);
|
|
112
|
-
if (files[sidecar] !== undefined) delete files[sidecar];
|
|
113
|
-
deleted.add(part);
|
|
114
|
-
}
|
|
115
|
-
return deleted;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function referencedInternalTargets(files) {
|
|
119
|
-
const refs = new Set();
|
|
120
|
-
for (const relsPath of Object.keys(files)) {
|
|
121
|
-
if (!relsPath.endsWith(".rels")) continue;
|
|
122
|
-
const dir = relsPath.replace(/\/?_rels\/[^/]+$/, "");
|
|
123
|
-
const owner =
|
|
124
|
-
dir === ""
|
|
125
|
-
? relsPath.replace(/^_rels\/(.*)\.rels$/, "$1")
|
|
126
|
-
: `${dir}/${relsPath.replace(/^.*_rels\/(.*)\.rels$/, "$1")}`;
|
|
127
|
-
for (const rel of relsFor(files, owner)) {
|
|
128
|
-
if (rel.targetMode === "External") continue;
|
|
129
|
-
refs.add(resolvePartPath(dir, rel.target));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return refs;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function removeContentTypeOverrides(files, deletedParts) {
|
|
136
|
-
const before = readText(files, CONTENT_TYPES);
|
|
137
|
-
if (!before) return;
|
|
138
|
-
let after = before;
|
|
139
|
-
for (const part of deletedParts) {
|
|
140
|
-
after = after.replace(
|
|
141
|
-
new RegExp(
|
|
142
|
-
`<Override\\b(?=[^>]*\\bPartName="/${escapeRe(part)}")[^>]*?/>`,
|
|
143
|
-
"g",
|
|
144
|
-
),
|
|
145
|
-
"",
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
if (after !== before) files[CONTENT_TYPES] = new TextEncoder().encode(after);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function removeUnusedMediaDefaults(files, exts) {
|
|
152
|
-
const before = readText(files, CONTENT_TYPES);
|
|
153
|
-
if (!before) return false;
|
|
154
|
-
let after = before;
|
|
155
|
-
for (const ext of exts) {
|
|
156
|
-
const hasPart = Object.keys(files).some((part) =>
|
|
157
|
-
part.toLowerCase().endsWith(`.${ext}`),
|
|
158
|
-
);
|
|
159
|
-
if (hasPart) continue;
|
|
160
|
-
after = after.replace(
|
|
161
|
-
new RegExp(`<Default\\b(?=[^>]*\\bExtension="${escapeRe(ext)}")[^>]*?/>`, "g"),
|
|
162
|
-
"",
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
if (after !== before) {
|
|
166
|
-
files[CONTENT_TYPES] = new TextEncoder().encode(after);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function relationshipIdsIn(xml) {
|
|
173
|
-
const ids = new Set();
|
|
174
|
-
for (const m of String(xml).matchAll(/\br:(?:id|embed|link)\s*=\s*"([^"]+)"/g)) {
|
|
175
|
-
ids.add(m[1]);
|
|
176
|
-
}
|
|
177
|
-
return ids;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function removeRelationshipById(xml, id) {
|
|
181
|
-
return xml.replace(
|
|
182
|
-
new RegExp(
|
|
183
|
-
`<Relationship\\b(?=[^>]*\\bId="${escapeRe(id)}")[^>]*?(?:/>|>[\\s\\S]*?</Relationship>)`,
|
|
184
|
-
"g",
|
|
185
|
-
),
|
|
186
|
-
"",
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|