@wix/himalaya-cli 0.1046.0 → 0.1047.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs
CHANGED
|
@@ -42838,7 +42838,7 @@ function serializeAt(node, src, open) {
|
|
|
42838
42838
|
const indent = lineIndent(src, open);
|
|
42839
42839
|
return JSON.stringify(node, null, 2).split("\n").map((line, i) => i === 0 ? line : indent + line).join("\n");
|
|
42840
42840
|
}
|
|
42841
|
-
function patchGrids(src, installed, patch) {
|
|
42841
|
+
function patchGrids(src, installed, patch, keepFaq = false, dropFaq = false) {
|
|
42842
42842
|
const OPEN = /"id": "([A-Za-z_0-9]+(?:\.[A-Za-z_0-9]+)*\.verticals)"/g;
|
|
42843
42843
|
let out = "";
|
|
42844
42844
|
let cursor = 0;
|
|
@@ -42859,12 +42859,17 @@ function patchGrids(src, installed, patch) {
|
|
|
42859
42859
|
const prefix = m[1].slice(0, -".verticals".length);
|
|
42860
42860
|
const baked = /* @__PURE__ */ new Map();
|
|
42861
42861
|
const foreignCells = [];
|
|
42862
|
-
|
|
42863
|
-
|
|
42864
|
-
const
|
|
42862
|
+
let faqCell;
|
|
42863
|
+
const claimOf = (item) => {
|
|
42864
|
+
const id = item?.id;
|
|
42865
|
+
const legacy = item?.modifiers?.accessibilityLabel;
|
|
42865
42866
|
const claim = typeof id === "string" && id ? id : typeof legacy === "string" ? legacy : "";
|
|
42866
|
-
|
|
42867
|
+
return claim.startsWith(`${prefix}.`) ? claim.slice(prefix.length + 1) : "";
|
|
42868
|
+
};
|
|
42869
|
+
for (const item of items) {
|
|
42870
|
+
const vertical = claimOf(item);
|
|
42867
42871
|
if (isKnown(vertical)) baked.set(vertical, item);
|
|
42872
|
+
else if (vertical === "faq" && keepFaq) faqCell = item;
|
|
42868
42873
|
else if (!GRID_VERTICAL_LABELS.has(vertical)) foreignCells.push(item);
|
|
42869
42874
|
}
|
|
42870
42875
|
const wanted = [];
|
|
@@ -42882,10 +42887,36 @@ function patchGrids(src, installed, patch) {
|
|
|
42882
42887
|
wanted.push(vertical);
|
|
42883
42888
|
}
|
|
42884
42889
|
const shape = baked.values().next().value ?? items[0];
|
|
42890
|
+
const bakedFaq = faqCell;
|
|
42891
|
+
if (keepFaq && !faqCell && hasScreen(src, FAQ_CARD.screenId)) {
|
|
42892
|
+
faqCell = cloneCell(shape, prefix, "faq", FAQ_CARD);
|
|
42893
|
+
}
|
|
42894
|
+
if (!installed.size) {
|
|
42895
|
+
if (dropFaq) {
|
|
42896
|
+
const kept = items.filter((item) => claimOf(item) !== "faq");
|
|
42897
|
+
if (kept.length !== items.length && kept.length > 0) {
|
|
42898
|
+
node.body.props.items = kept;
|
|
42899
|
+
out += src.slice(cursor, open) + serializeAt(node, src, open);
|
|
42900
|
+
cursor = close;
|
|
42901
|
+
OPEN.lastIndex = close;
|
|
42902
|
+
}
|
|
42903
|
+
continue;
|
|
42904
|
+
}
|
|
42905
|
+
const lastIsVertical = GRID_VERTICAL_LABELS.has(claimOf(items[items.length - 1]));
|
|
42906
|
+
if (!bakedFaq && faqCell && (items.length < EXPLORE_MAX_CELLS || lastIsVertical)) {
|
|
42907
|
+
if (items.length >= EXPLORE_MAX_CELLS) items.pop();
|
|
42908
|
+
items.push(faqCell);
|
|
42909
|
+
out += src.slice(cursor, open) + serializeAt(node, src, open);
|
|
42910
|
+
cursor = close;
|
|
42911
|
+
OPEN.lastIndex = close;
|
|
42912
|
+
}
|
|
42913
|
+
continue;
|
|
42914
|
+
}
|
|
42915
|
+
const cap = faqCell ? EXPLORE_MAX_CELLS - 1 : EXPLORE_MAX_CELLS;
|
|
42885
42916
|
const next = [...foreignCells];
|
|
42886
42917
|
const cells = [];
|
|
42887
42918
|
for (const vertical of wanted) {
|
|
42888
|
-
if (next.length >=
|
|
42919
|
+
if (next.length >= cap) break;
|
|
42889
42920
|
const bakedCell = baked.get(vertical);
|
|
42890
42921
|
if (bakedCell) {
|
|
42891
42922
|
next.push(bakedCell);
|
|
@@ -42901,6 +42932,7 @@ function patchGrids(src, installed, patch) {
|
|
|
42901
42932
|
cells.push(vertical);
|
|
42902
42933
|
patch.added.push(vertical);
|
|
42903
42934
|
}
|
|
42935
|
+
if (faqCell && next.length < EXPLORE_MAX_CELLS) next.push(faqCell);
|
|
42904
42936
|
if (!cells.length) continue;
|
|
42905
42937
|
patch.cells = cells;
|
|
42906
42938
|
node.body.props.items = next;
|
|
@@ -43015,16 +43047,19 @@ function patchStoreCtas(src, verticals, homeActionId) {
|
|
|
43015
43047
|
dropped
|
|
43016
43048
|
};
|
|
43017
43049
|
}
|
|
43018
|
-
function patchHomeForVerticals(src, verticals, homeActionId) {
|
|
43050
|
+
function patchHomeForVerticals(src, verticals, homeActionId, opts = {}) {
|
|
43019
43051
|
const installed = new Set(verticals.filter(isKnown));
|
|
43020
43052
|
const patch = { src, cells: [], added: [], droppedModules: [], droppedCtas: [], unreachable: [] };
|
|
43021
|
-
if (!installed.size)
|
|
43022
|
-
|
|
43053
|
+
if (!installed.size) {
|
|
43054
|
+
if (opts.keepFaq || opts.dropFaq) patch.src = patchGrids(src, installed, patch, !!opts.keepFaq, !opts.keepFaq && !!opts.dropFaq);
|
|
43055
|
+
return patch;
|
|
43056
|
+
}
|
|
43057
|
+
const ctas = patchStoreCtas(patchModules(patchGrids(src, installed, patch, opts.keepFaq), installed, patch), [...installed], homeActionId);
|
|
43023
43058
|
patch.src = ctas.src;
|
|
43024
43059
|
patch.droppedCtas = ctas.dropped;
|
|
43025
43060
|
return patch;
|
|
43026
43061
|
}
|
|
43027
|
-
var EXPLORE_CARDS, EXPLORE_ORDER, EXPLORE_MAX_CELLS, MODULE_SOURCE_VERTICALS, isKnown, GRID_VERTICAL_LABELS, escapeRe2, textProps, STORE_SCREENS, isStoreScreen, screenDeclarations, isNode2;
|
|
43062
|
+
var EXPLORE_CARDS, EXPLORE_ORDER, EXPLORE_MAX_CELLS, MODULE_SOURCE_VERTICALS, isKnown, GRID_VERTICAL_LABELS, FAQ_CARD, escapeRe2, textProps, STORE_SCREENS, isStoreScreen, screenDeclarations, isNode2;
|
|
43028
43063
|
var init_home_descriptor = __esm({
|
|
43029
43064
|
"src/site-analyze/home-descriptor.ts"() {
|
|
43030
43065
|
init_types2();
|
|
@@ -43096,6 +43131,7 @@ var init_home_descriptor = __esm({
|
|
|
43096
43131
|
};
|
|
43097
43132
|
isKnown = (v) => KNOWN_VERTICALS.includes(v);
|
|
43098
43133
|
GRID_VERTICAL_LABELS = /* @__PURE__ */ new Set([...KNOWN_VERTICALS, "faq"]);
|
|
43134
|
+
FAQ_CARD = { icon: "help", screenId: "himi_faq", title: "FAQ", body: "Find answers to common questions." };
|
|
43099
43135
|
escapeRe2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43100
43136
|
textProps = (n) => n?.body?.props;
|
|
43101
43137
|
STORE_SCREENS = /* @__PURE__ */ new Set([
|
|
@@ -54141,7 +54177,28 @@ function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
|
|
|
54141
54177
|
};
|
|
54142
54178
|
return { appName, iconSource: deriveIconSource(ctx), provisioning, missing };
|
|
54143
54179
|
}
|
|
54144
|
-
|
|
54180
|
+
function bounded(s, max) {
|
|
54181
|
+
if (s.length <= max) return s;
|
|
54182
|
+
const cut = s.slice(0, max - 1);
|
|
54183
|
+
const space = cut.lastIndexOf(" ");
|
|
54184
|
+
return `${(space > max * 0.6 ? cut.slice(0, space) : cut).trimEnd()}\u2026`;
|
|
54185
|
+
}
|
|
54186
|
+
function deriveStaticFaq(faq) {
|
|
54187
|
+
if (!Array.isArray(faq)) return [];
|
|
54188
|
+
const out = [];
|
|
54189
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54190
|
+
for (const raw of faq) {
|
|
54191
|
+
if (out.length >= STATIC_FAQ_LIMITS.pairs) break;
|
|
54192
|
+
const pair = raw && typeof raw === "object" ? raw : {};
|
|
54193
|
+
const q = bounded(oneLine(pair.q), STATIC_FAQ_LIMITS.question);
|
|
54194
|
+
const a = bounded(oneLine(pair.a), STATIC_FAQ_LIMITS.answer);
|
|
54195
|
+
if (!q || !a || seen.has(q.toLowerCase())) continue;
|
|
54196
|
+
seen.add(q.toLowerCase());
|
|
54197
|
+
out.push({ q, a });
|
|
54198
|
+
}
|
|
54199
|
+
return out;
|
|
54200
|
+
}
|
|
54201
|
+
var clean, ID_MAX, NAME_MAX, TYPE_MAX, CONTROL_CHARS, HAS_CONTROL_CHAR, plain, shown, STATIC_FAQ_LIMITS, oneLine;
|
|
54145
54202
|
var init_derive = __esm({
|
|
54146
54203
|
"src/site-analyze/derive.ts"() {
|
|
54147
54204
|
init_types2();
|
|
@@ -54153,6 +54210,8 @@ var init_derive = __esm({
|
|
|
54153
54210
|
HAS_CONTROL_CHAR = /[\u0000-\u001F\u007F]/;
|
|
54154
54211
|
plain = (value, max) => (value ?? "").replace(CONTROL_CHARS, "").trim().slice(0, max);
|
|
54155
54212
|
shown = (value) => (typeof value === "string" ? JSON.stringify(value) : String(value)).slice(0, 64);
|
|
54213
|
+
STATIC_FAQ_LIMITS = { pairs: 20, question: 200, answer: 1500 };
|
|
54214
|
+
oneLine = (s) => typeof s === "string" ? s.replace(/[\u0000-\u001F\u007F\s]+/g, " ").trim() : "";
|
|
54156
54215
|
}
|
|
54157
54216
|
});
|
|
54158
54217
|
|
|
@@ -57211,7 +57270,8 @@ function mergeCustomerOwned(committed, overlay) {
|
|
|
57211
57270
|
"navigation",
|
|
57212
57271
|
"locale",
|
|
57213
57272
|
"wix",
|
|
57214
|
-
"copy"
|
|
57273
|
+
"copy",
|
|
57274
|
+
"faq"
|
|
57215
57275
|
];
|
|
57216
57276
|
const customer = {};
|
|
57217
57277
|
for (const key2 of CUSTOMER_OWNED) {
|
|
@@ -57305,12 +57365,56 @@ function buildMergedContract(options) {
|
|
|
57305
57365
|
staged.push(localPath);
|
|
57306
57366
|
return { path: path2, staged };
|
|
57307
57367
|
}
|
|
57368
|
+
function catalogTypography(overlay, template) {
|
|
57369
|
+
const typography = {};
|
|
57370
|
+
const notes = [];
|
|
57371
|
+
const base = template && typeof template === "object" ? template : {};
|
|
57372
|
+
const usable = (family) => typeof family === "string" && family.trim() !== "" && (family === SYSTEM_FONT_FAMILY || canonicalFamilyName(family) !== void 0);
|
|
57373
|
+
for (const [role, token] of Object.entries(overlay ?? {})) {
|
|
57374
|
+
const family = token?.family;
|
|
57375
|
+
if (typeof family !== "string" || family.trim() === "") {
|
|
57376
|
+
typography[role] = token;
|
|
57377
|
+
continue;
|
|
57378
|
+
}
|
|
57379
|
+
if (family === SYSTEM_FONT_FAMILY) {
|
|
57380
|
+
typography[role] = token;
|
|
57381
|
+
continue;
|
|
57382
|
+
}
|
|
57383
|
+
const canonical3 = canonicalFamilyName(family.trim());
|
|
57384
|
+
if (canonical3) {
|
|
57385
|
+
typography[role] = { ...token, family: canonical3 };
|
|
57386
|
+
continue;
|
|
57387
|
+
}
|
|
57388
|
+
const kept = base[role]?.family;
|
|
57389
|
+
const using = usable(kept) ? `the template's ${kept}` : `the template's default (${TIER1_FLOOR[0]})`;
|
|
57390
|
+
notes.push(`site font "${family}" (${role}) isn't in the font catalog; using ${using}`);
|
|
57391
|
+
}
|
|
57392
|
+
return { typography, notes };
|
|
57393
|
+
}
|
|
57394
|
+
function staticFaqFor(overlay, overlayPath) {
|
|
57395
|
+
const own3 = overlay.faq;
|
|
57396
|
+
if (own3 !== void 0) return deriveStaticFaq(own3);
|
|
57397
|
+
const bookPath = join59(dirname32(resolve45(overlayPath)), "book.json");
|
|
57398
|
+
if (!existsSync61(bookPath)) return void 0;
|
|
57399
|
+
try {
|
|
57400
|
+
const book = JSON.parse(readFileSync72(bookPath, "utf8"));
|
|
57401
|
+
const copy = book?.copy;
|
|
57402
|
+
if (!copy || typeof copy !== "object" || copy.unavailable) return void 0;
|
|
57403
|
+
const pairs = deriveStaticFaq(copy.faq);
|
|
57404
|
+
if (!pairs.length && copy.partial) return void 0;
|
|
57405
|
+
return pairs;
|
|
57406
|
+
} catch {
|
|
57407
|
+
return void 0;
|
|
57408
|
+
}
|
|
57409
|
+
}
|
|
57308
57410
|
async function applyOverlays(input) {
|
|
57309
57411
|
const cwd = resolve45(input.cwd);
|
|
57310
57412
|
const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
|
|
57311
57413
|
assertKnownShell(shell);
|
|
57312
57414
|
const overlay = JSON.parse(readFileSync72(input.overlayPath, "utf8"));
|
|
57313
57415
|
const tokens = JSON.parse(readFileSync72(input.tokensPath, "utf8"));
|
|
57416
|
+
const faq = staticFaqFor(overlay, input.overlayPath);
|
|
57417
|
+
if (faq !== void 0) overlay.faq = faq;
|
|
57314
57418
|
const wrote = [];
|
|
57315
57419
|
const notes = [];
|
|
57316
57420
|
const repoRoot2 = findRepoRoot(cwd);
|
|
@@ -57344,9 +57448,11 @@ async function applyOverlays(input) {
|
|
|
57344
57448
|
}
|
|
57345
57449
|
const pkgProv = join59(cwd, "provisioning.json");
|
|
57346
57450
|
assertNotCommitted(pkgProv, input.forbiddenProvisioning);
|
|
57451
|
+
let packageFaq = faq;
|
|
57347
57452
|
if (existsSync61(pkgProv)) {
|
|
57348
57453
|
const cur = JSON.parse(readFileSync72(pkgProv, "utf8"));
|
|
57349
57454
|
const merged = deepMerge4(cur, overlay);
|
|
57455
|
+
packageFaq = merged.faq;
|
|
57350
57456
|
writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
|
|
57351
57457
|
wrote.push(pkgProv);
|
|
57352
57458
|
} else {
|
|
@@ -57356,7 +57462,13 @@ async function applyOverlays(input) {
|
|
|
57356
57462
|
const pkgTokens = join59(cwd, "tokens.json");
|
|
57357
57463
|
if (existsSync61(pkgTokens) && (tokens.colors || tokens.typography)) {
|
|
57358
57464
|
const cur = JSON.parse(readFileSync72(pkgTokens, "utf8"));
|
|
57359
|
-
|
|
57465
|
+
let tokensIn = tokens;
|
|
57466
|
+
if (tokens.typography) {
|
|
57467
|
+
const fonts = catalogTypography(tokens.typography, cur.typography);
|
|
57468
|
+
notes.push(...fonts.notes);
|
|
57469
|
+
tokensIn = { ...tokens, typography: fonts.typography };
|
|
57470
|
+
}
|
|
57471
|
+
const merged = deepMerge4(cur, tokensIn);
|
|
57360
57472
|
const mergedColors = merged.colors;
|
|
57361
57473
|
if (mergedColors && typeof mergedColors === "object" && !Array.isArray(mergedColors)) {
|
|
57362
57474
|
merged.colors = alignAccentsToSurfaces(mergedColors, tokens.colors);
|
|
@@ -57379,8 +57491,16 @@ async function applyOverlays(input) {
|
|
|
57379
57491
|
const prev = readFileSync72(homeSrc, "utf8");
|
|
57380
57492
|
const patched = patchBrandParams(prev, overlay, homeAction);
|
|
57381
57493
|
let next = patched.src;
|
|
57382
|
-
if (!patched.foreign &&
|
|
57383
|
-
|
|
57494
|
+
if (!patched.foreign && faq?.length && prev.includes('"tier5.himiBrandedFaq.load"') && !prev.includes('"staticFaq"')) {
|
|
57495
|
+
notes.push(
|
|
57496
|
+
"the site's FAQ pairs were saved to provisioning.json, but this package's FAQ screen predates the site-FAQ fallback and will not show them \u2014 re-scaffold from the current template to pick it up"
|
|
57497
|
+
);
|
|
57498
|
+
}
|
|
57499
|
+
const keepFaq = Array.isArray(packageFaq) && packageFaq.length > 0;
|
|
57500
|
+
const dropFaq = Array.isArray(faq) && faq.length === 0;
|
|
57501
|
+
const ownsHome = prev.includes(`"actionId": "${homeAction}"`);
|
|
57502
|
+
if (ownsHome && !patched.foreign && (overlay.installedVerticals?.length || keepFaq || dropFaq)) {
|
|
57503
|
+
const home = patchHomeForVerticals(next, overlay.installedVerticals ?? [], homeAction, { keepFaq, dropFaq });
|
|
57384
57504
|
next = home.src;
|
|
57385
57505
|
if (home.droppedCtas.length) {
|
|
57386
57506
|
notes.push(`removed the home's store call(s) to action (${home.droppedCtas.join(", ")}) \u2014 this site has no store`);
|
|
@@ -57426,10 +57546,18 @@ function patchBrandParams(src, overlay, homeActionId) {
|
|
|
57426
57546
|
const layout = overlay.appearance?.homeLayout;
|
|
57427
57547
|
const accent = overlay.branding?.primaryColor;
|
|
57428
57548
|
const verts = overlay.installedVerticals;
|
|
57549
|
+
const faq = overlay.faq;
|
|
57429
57550
|
const edits = [
|
|
57430
57551
|
...layout ? [{ pattern: /"ownerDefaultLayout": "[^"]*"/g, replacement: `"ownerDefaultLayout": ${JSON.stringify(layout)}` }] : [],
|
|
57431
57552
|
...accent ? [{ pattern: /"brandAccent": "[^"]*"/g, replacement: `"brandAccent": ${JSON.stringify(accent)}` }] : [],
|
|
57432
|
-
...verts?.length ? [{ pattern: /"verticals": "\[[^\]]*\]"/g, replacement: `"verticals": ${JSON.stringify(JSON.stringify(verts))}` }] : []
|
|
57553
|
+
...verts?.length ? [{ pattern: /"verticals": "\[[^\]]*\]"/g, replacement: `"verticals": ${JSON.stringify(JSON.stringify(verts))}` }] : [],
|
|
57554
|
+
// The FAQ screen's fallback pairs (#3276), a JSON STRING like `verticals` because action
|
|
57555
|
+
// params are string-valued. The pattern matches any escaped string, so re-applying a
|
|
57556
|
+
// package whose pairs are already written replaces them instead of missing. A function
|
|
57557
|
+
// replacement, because the pairs are customer text and may contain `$&`. Patched whenever
|
|
57558
|
+
// the analysis SAID something, `[]` included, so a re-apply clears pairs an earlier one
|
|
57559
|
+
// baked in rather than leaving the worker another site's FAQ.
|
|
57560
|
+
...faq ? [{ pattern: /"staticFaq": "(?:[^"\\]|\\.)*"/g, replacement: `"staticFaq": ${JSON.stringify(JSON.stringify(faq))}` }] : []
|
|
57433
57561
|
];
|
|
57434
57562
|
if (!edits.length) return { src, applied: 0, unparsed: 0, foreign: false };
|
|
57435
57563
|
if (!src.includes(`"actionId": "${homeActionId}"`)) {
|
|
@@ -57474,7 +57602,7 @@ function patchBrandParams(src, overlay, homeActionId) {
|
|
|
57474
57602
|
pattern.lastIndex = 0;
|
|
57475
57603
|
if (!pattern.test(block)) continue;
|
|
57476
57604
|
pattern.lastIndex = 0;
|
|
57477
|
-
block = block.replace(pattern, replacement);
|
|
57605
|
+
block = block.replace(pattern, () => replacement);
|
|
57478
57606
|
applied += 1;
|
|
57479
57607
|
}
|
|
57480
57608
|
out += src.slice(cursor, open) + block;
|
|
@@ -57492,6 +57620,10 @@ var init_apply2 = __esm({
|
|
|
57492
57620
|
init_branding_derive();
|
|
57493
57621
|
init_brand_color_math();
|
|
57494
57622
|
init_home_descriptor();
|
|
57623
|
+
init_derive();
|
|
57624
|
+
init_catalog();
|
|
57625
|
+
init_plan_fonts();
|
|
57626
|
+
init_tokens();
|
|
57495
57627
|
STAGED = ".himi/brand";
|
|
57496
57628
|
}
|
|
57497
57629
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1047.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"artifactId": "himalaya-cli"
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
|
-
"falconPackageHash": "
|
|
68
|
+
"falconPackageHash": "9a5f5cd39f2e85af3f506141935613aebbda4942b8cb91683db7492f"
|
|
69
69
|
}
|
|
@@ -58239,7 +58239,8 @@ const SCREENS = runtimeSetting({
|
|
|
58239
58239
|
"onAppear": {
|
|
58240
58240
|
"actionId": "tier5.himiBrandedFaq.load",
|
|
58241
58241
|
"params": {
|
|
58242
|
-
"otherCategory": "Other"
|
|
58242
|
+
"otherCategory": "Other",
|
|
58243
|
+
"staticFaq": "[]"
|
|
58243
58244
|
}
|
|
58244
58245
|
}
|
|
58245
58246
|
}
|
|
@@ -58,6 +58,55 @@ interface FaqState {
|
|
|
58
58
|
|
|
59
59
|
const PAGE_SIZE = 100;
|
|
60
60
|
|
|
61
|
+
/** Caps on the static pairs, matching `BRANDED_LITE_FAQ_LIMITS` in the app contract. The
|
|
62
|
+
* param is re-bounded here because a worker must not trust a descriptor it did not build. */
|
|
63
|
+
const STATIC_LIMITS = { pairs: 20, question: 200, answer: 1500 } as const;
|
|
64
|
+
const STATIC_CATEGORY: FaqCategory = { id: "himi-static-faq", title: "" };
|
|
65
|
+
|
|
66
|
+
/** The site's own FAQ pairs — questions it publishes as page sections rather than
|
|
67
|
+
* through the Wix FAQ app, carried in by `site analyze --apply` as the `staticFaq` onAppear
|
|
68
|
+
* param (a JSON string: action params are string-valued). They are the FALLBACK: shown only
|
|
69
|
+
* when the FAQ app yields no visible question, so an owner-edited Wix FAQ always wins. */
|
|
70
|
+
let staticQuestions: FaqQuestion[] = [];
|
|
71
|
+
|
|
72
|
+
function parseStaticFaq(raw: unknown): FaqQuestion[] {
|
|
73
|
+
let pairs: unknown = raw;
|
|
74
|
+
if (typeof raw === "string") {
|
|
75
|
+
try {
|
|
76
|
+
pairs = JSON.parse(raw);
|
|
77
|
+
} catch {
|
|
78
|
+
// A malformed param is a DESCRIPTOR bug, not an empty FAQ: there is no fallback to
|
|
79
|
+
// offer, so the screen shows exactly what the Wix FAQ read says — its content, its
|
|
80
|
+
// empty card, or its error card — rather than pretending the site has no questions.
|
|
81
|
+
pairs = undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(pairs)) return [];
|
|
85
|
+
const out: FaqQuestion[] = [];
|
|
86
|
+
for (const pair of pairs) {
|
|
87
|
+
if (out.length >= STATIC_LIMITS.pairs) break;
|
|
88
|
+
const row = pair && typeof pair === "object" ? pair as { q?: unknown; a?: unknown } : {};
|
|
89
|
+
const question = typeof row.q === "string" ? row.q.trim().slice(0, STATIC_LIMITS.question) : "";
|
|
90
|
+
const answer = typeof row.a === "string" ? row.a.trim().slice(0, STATIC_LIMITS.answer) : "";
|
|
91
|
+
if (!question || !answer) continue;
|
|
92
|
+
out.push({
|
|
93
|
+
id: `himi-static-faq-${out.length}`,
|
|
94
|
+
categoryId: STATIC_CATEGORY.id,
|
|
95
|
+
question,
|
|
96
|
+
answer,
|
|
97
|
+
sortOrder: out.length,
|
|
98
|
+
status: "VISIBLE",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The site's pairs as a ready state, or undefined when there are none to fall back to. */
|
|
105
|
+
function staticFallback(state: FaqState): FaqState | undefined {
|
|
106
|
+
if (!staticQuestions.length) return undefined;
|
|
107
|
+
return ready({ ...state, categories: [STATIC_CATEGORY], questions: staticQuestions });
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
function sortByWixOrder<T extends { id: string; sortOrder?: number }>(left: T, right: T): number {
|
|
62
111
|
const a = Number.isFinite(left.sortOrder) ? left.sortOrder! : Number.POSITIVE_INFINITY;
|
|
63
112
|
const b = Number.isFinite(right.sortOrder) ? right.sortOrder! : Number.POSITIVE_INFINITY;
|
|
@@ -101,7 +150,8 @@ function ready(state: FaqState): FaqState {
|
|
|
101
150
|
const rows = sections.flatMap((section) => section.questions.map((question, index) => ({
|
|
102
151
|
...question,
|
|
103
152
|
categoryTitle: section.title,
|
|
104
|
-
|
|
153
|
+
// An untitled section (the static fallback's) has no header to show.
|
|
154
|
+
categoryHeaderHidden: index !== 0 || !section.title,
|
|
105
155
|
})));
|
|
106
156
|
return {
|
|
107
157
|
...state,
|
|
@@ -218,17 +268,28 @@ function commit(state: FaqState): FaqState {
|
|
|
218
268
|
return state;
|
|
219
269
|
}
|
|
220
270
|
|
|
221
|
-
async function loadFaq(state: FaqState, arg?: { otherCategory?: string }): Promise<FaqState> {
|
|
271
|
+
async function loadFaq(state: FaqState, arg?: { otherCategory?: string; staticFaq?: unknown }): Promise<FaqState> {
|
|
222
272
|
latestState = state;
|
|
273
|
+
// Only when the caller passed the param: `retry` is dispatched with no params and must keep
|
|
274
|
+
// the pairs the screen's onAppear delivered.
|
|
275
|
+
if (arg && "staticFaq" in arg) staticQuestions = parseStaticFaq(arg.staticFaq);
|
|
223
276
|
const generation = ++loadGeneration;
|
|
224
277
|
try {
|
|
225
278
|
const [categories, questions] = await Promise.all([allCategories(), allQuestions()]);
|
|
226
279
|
if (generation !== loadGeneration) return latestState;
|
|
227
280
|
const otherTitle = String(arg?.otherCategory ?? latestState.otherTitle).trim() || latestState.otherTitle;
|
|
228
|
-
|
|
281
|
+
const live = ready({ ...latestState, categories, questions, otherTitle });
|
|
282
|
+
// The Wix FAQ app answered but holds nothing a visitor can see — the site's own pairs,
|
|
283
|
+
// when it has any, instead of an empty state over a site whose FAQ is right there.
|
|
284
|
+
if (questions.length === 0) return commit(staticFallback(live) ?? live);
|
|
285
|
+
return commit(live);
|
|
229
286
|
} catch (error) {
|
|
230
287
|
if (generation !== loadGeneration) return latestState;
|
|
231
288
|
const status = httpStatus(error);
|
|
289
|
+
// No FAQ app (428), no visitor access (403) or a failed read: a site whose FAQ is page
|
|
290
|
+
// content still has an FAQ to show, so its pairs replace the failure card.
|
|
291
|
+
const fallback = staticFallback(latestState);
|
|
292
|
+
if (fallback) return commit(fallback);
|
|
232
293
|
return commit(failure(latestState, status === 403 ? "forbidden" : status === 428 ? "notInstalled" : "error"));
|
|
233
294
|
}
|
|
234
295
|
}
|
|
@@ -250,4 +311,4 @@ const svc = defineService("himiBrandedFaq", {
|
|
|
250
311
|
},
|
|
251
312
|
});
|
|
252
313
|
|
|
253
|
-
export { svc, sectionsFor, httpStatus };
|
|
314
|
+
export { svc, sectionsFor, httpStatus, parseStaticFaq };
|