@wix/himalaya-cli 0.1570.0 → 0.1572.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +6 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-feed-viewer/src/index.d.ts +6 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/auth.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core/validation.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth.mjs +7 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-blog.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members-feed-viewer.mjs +18 -0
- package/dist/cli.mjs +2014 -1252
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/base44-app-to-himi-app.md +1 -1
- package/guides/capabilities.md +7 -0
- package/guides/himi-json-output.md +11 -0
- package/guides/index.json +7 -0
- package/guides/map-one-app-coverage.js +117 -0
- package/guides/one-app-to-himi-app.md +209 -11
- package/guides/recipe-one-app-coverage-mapping.md +74 -0
- package/guides/sdk-reference.json +8 -2
- package/guides/splash-screens.md +10 -1
- package/guides/trim-a-template.md +5 -0
- package/guides/wix-business-context.md +11 -2
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +1 -1
- package/templates/beauty-salon/dev/index.ts +31 -1
- package/templates/beauty-salon/tier5-src/_shared/member-read.ts +52 -0
- package/templates/beauty-salon/tier5-src/_shared/program-description.ts +11 -0
- package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/beauty-salon-owner/config.ts +1 -1
- package/templates/beauty-salon-owner/dev/index.ts +2 -3
- package/templates/branded-lite/config.ts +1 -1
- package/templates/branded-lite/dev/index.ts +163 -2
- package/templates/branded-lite/tier5-src/_shared/member-read.ts +52 -0
- package/templates/branded-lite/tier5-src/_shared/program-description.ts +11 -0
- package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +16 -14
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +31 -18
- package/templates/branded-lite/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/branded-lite/tier5-src/wix-spaces-chat-thread/index.ts +101 -34
- package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +23 -6
- package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +26 -11
- package/templates/community-club/config.ts +1 -1
- package/templates/community-club/dev/index.ts +65 -2
- package/templates/community-club/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/community-club/tier5-src/_shared/member-read.ts +52 -0
- package/templates/community-club/tier5-src/_shared/program-description.ts +11 -0
- package/templates/community-club/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/community-club/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/community-club-owner/config.ts +1 -1
- package/templates/community-club-owner/dev/index.ts +2 -3
- package/templates/events-meetups/config.ts +1 -1
- package/templates/events-meetups/dev/index.ts +65 -2
- package/templates/events-meetups/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/events-meetups/tier5-src/_shared/member-read.ts +52 -0
- package/templates/events-meetups/tier5-src/_shared/program-description.ts +11 -0
- package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/events-meetups/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/events-meetups-owner/config.ts +1 -1
- package/templates/events-meetups-owner/dev/index.ts +2 -3
- package/templates/gym-fitness/config.ts +1 -1
- package/templates/gym-fitness/dev/index.ts +65 -2
- package/templates/gym-fitness/tier5-src/_shared/member-read.ts +52 -0
- package/templates/gym-fitness/tier5-src/_shared/program-description.ts +11 -0
- package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/gym-fitness/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/gym-fitness-owner/config.ts +1 -1
- package/templates/gym-fitness-owner/dev/index.ts +2 -3
- package/templates/nonprofit-cause/config.ts +1 -1
- package/templates/nonprofit-cause/dev/index.ts +65 -2
- package/templates/nonprofit-cause/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/nonprofit-cause/tier5-src/_shared/member-read.ts +52 -0
- package/templates/nonprofit-cause/tier5-src/_shared/program-description.ts +11 -0
- package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/nonprofit-cause/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/nonprofit-cause-owner/config.ts +1 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +2 -3
- package/templates/owner-lite/config.ts +1 -1
- package/templates/owner-lite/dev/index.ts +2 -3
- package/templates/podcast-creator/config.ts +1 -1
- package/templates/podcast-creator/dev/index.ts +65 -2
- package/templates/podcast-creator/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/podcast-creator/tier5-src/_shared/member-read.ts +52 -0
- package/templates/podcast-creator/tier5-src/_shared/program-description.ts +11 -0
- package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/podcast-creator/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/podcast-creator-owner/config.ts +1 -1
- package/templates/podcast-creator-owner/dev/index.ts +2 -3
- package/templates/portfolio-creative/config.ts +1 -1
- package/templates/portfolio-creative/dev/index.ts +65 -2
- package/templates/portfolio-creative/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/portfolio-creative/tier5-src/_shared/member-read.ts +52 -0
- package/templates/portfolio-creative/tier5-src/_shared/program-description.ts +11 -0
- package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/portfolio-creative/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/portfolio-creative-owner/config.ts +1 -1
- package/templates/portfolio-creative-owner/dev/index.ts +2 -3
- package/templates/restaurant/config.ts +1 -1
- package/templates/restaurant/dev/index.ts +65 -2
- package/templates/restaurant/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/restaurant/tier5-src/_shared/member-read.ts +52 -0
- package/templates/restaurant/tier5-src/_shared/program-description.ts +11 -0
- package/templates/restaurant/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/restaurant/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/restaurant-owner/config.ts +1 -1
- package/templates/restaurant-owner/dev/index.ts +2 -3
- package/templates/retail-store/config.ts +1 -1
- package/templates/retail-store/dev/index.ts +31 -1
- package/templates/retail-store/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/retail-store/tier5-src/_shared/member-read.ts +52 -0
- package/templates/retail-store/tier5-src/_shared/program-description.ts +11 -0
- package/templates/retail-store/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/retail-store-owner/config.ts +1 -1
- package/templates/retail-store-owner/dev/index.ts +2 -3
- package/templates/yoga-studio/config.ts +1 -1
- package/templates/yoga-studio/dev/index.ts +93 -2
- package/templates/yoga-studio/tier5-src/_shared/member-read.ts +52 -0
- package/templates/yoga-studio/tier5-src/_shared/program-description.ts +11 -0
- package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +16 -14
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +31 -18
- package/templates/yoga-studio/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/yoga-studio-owner/config.ts +1 -1
- package/templates/yoga-studio-owner/dev/index.ts +2 -3
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme"],
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme","wix_cashier"],
|
|
41
41
|
cache: {
|
|
42
42
|
"rules": [
|
|
43
43
|
{
|
|
@@ -40229,6 +40229,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
40229
40229
|
"style": "body",
|
|
40230
40230
|
"color": "textSecondary"
|
|
40231
40231
|
}
|
|
40232
|
+
},
|
|
40233
|
+
"modifiers": {
|
|
40234
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
40232
40235
|
}
|
|
40233
40236
|
},
|
|
40234
40237
|
{
|
|
@@ -40243,6 +40246,32 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
40243
40246
|
"actionId": "tier5.himiBlogPost.retry"
|
|
40244
40247
|
}
|
|
40245
40248
|
}
|
|
40249
|
+
},
|
|
40250
|
+
"modifiers": {
|
|
40251
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
40252
|
+
}
|
|
40253
|
+
},
|
|
40254
|
+
{
|
|
40255
|
+
"id": "himi_blog_post.retry.sign_in",
|
|
40256
|
+
"body": {
|
|
40257
|
+
"package": "himalaya",
|
|
40258
|
+
"type": "Button",
|
|
40259
|
+
"props": {
|
|
40260
|
+
"title": "Sign in",
|
|
40261
|
+
"style": "primary",
|
|
40262
|
+
"action": {
|
|
40263
|
+
"actionId": "himi.open",
|
|
40264
|
+
"params": {
|
|
40265
|
+
"screenId": "himi_sign_in",
|
|
40266
|
+
"mode": "signin",
|
|
40267
|
+
"modal": "1"
|
|
40268
|
+
},
|
|
40269
|
+
"presentation": "sheet"
|
|
40270
|
+
}
|
|
40271
|
+
}
|
|
40272
|
+
},
|
|
40273
|
+
"modifiers": {
|
|
40274
|
+
"hiddenPath": "$service:himiBlogPost.noSignIn"
|
|
40246
40275
|
}
|
|
40247
40276
|
}
|
|
40248
40277
|
]
|
|
@@ -40947,7 +40976,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
40947
40976
|
"onAppear": {
|
|
40948
40977
|
"actionId": "tier5.himiBlogPost.load",
|
|
40949
40978
|
"params": {
|
|
40950
|
-
"id": "__HIMI_ID__"
|
|
40979
|
+
"id": "__HIMI_ID__",
|
|
40980
|
+
"signInScreenId": "himi_sign_in"
|
|
40951
40981
|
}
|
|
40952
40982
|
}
|
|
40953
40983
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { memberAccess, memberAccessChanged, memberAccessDenied, type MemberAccess } from "./member-access.js";
|
|
2
|
+
import { needsSignIn } from "./branded-load-state.js";
|
|
3
|
+
|
|
4
|
+
/** Public reads stay public. Capture their reader only to reject a result that
|
|
5
|
+
* belongs to the identity/site which was active before the request. */
|
|
6
|
+
export interface ReadAccess { member: MemberAccess; tenant: string; }
|
|
7
|
+
export async function readAccess(): Promise<ReadAccess> {
|
|
8
|
+
const member = await memberAccess();
|
|
9
|
+
const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
|
|
10
|
+
return { member, tenant: JSON.stringify([input?.__himiPreferenceAccount ?? "", input?.__himiPreferenceBusiness ?? ""]) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function assertReadAccess(access: ReadAccess): Promise<void> {
|
|
14
|
+
const current = await readAccess();
|
|
15
|
+
if (current.member.email !== access.member.email || current.member.scope !== access.member.scope || current.tenant !== access.tenant) throw memberAccessChanged();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Bridge errors put the actual status first; codes quoted inside the body are data. */
|
|
19
|
+
function httpStatus(error: unknown): string | undefined {
|
|
20
|
+
return /^HTTP\s*(\d{3})\b/i.exec(String((error as { message?: unknown })?.message ?? error))?.[1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const NO_READ_SIGN_IN = { signInRequired: false, noSignIn: true };
|
|
24
|
+
|
|
25
|
+
/** An authorization refusal must not fall back to a previously cached article. */
|
|
26
|
+
export function readAccessDenied(error: unknown): boolean {
|
|
27
|
+
let cause = error;
|
|
28
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
29
|
+
const status = httpStatus(cause);
|
|
30
|
+
if (status ? status === "401" || status === "403" : memberAccessDenied(cause) || needsSignIn(cause)) return true;
|
|
31
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A permission failure alone says nothing about whether signing in can help.
|
|
37
|
+
* In particular, a private member profile can be unavailable to members too.
|
|
38
|
+
* Only an authentication refusal for a current visitor offers Sign in. Follow
|
|
39
|
+
* SDK wrapper causes (blog getPost preserves its original read error there). */
|
|
40
|
+
export async function readSignInFlags(error: unknown): Promise<typeof NO_READ_SIGN_IN> {
|
|
41
|
+
let authenticationRequired = false;
|
|
42
|
+
let permissionRefused = false;
|
|
43
|
+
let cause = error;
|
|
44
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
45
|
+
const status = httpStatus(cause);
|
|
46
|
+
permissionRefused ||= status === "403";
|
|
47
|
+
authenticationRequired ||= status ? status === "401" : needsSignIn(cause);
|
|
48
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
49
|
+
}
|
|
50
|
+
const signInRequired = !permissionRefused && authenticationRequired && !(await memberAccess()).email;
|
|
51
|
+
return { signInRequired, noSignIn: !signInRequired };
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
2
|
+
|
|
3
|
+
/** v3 nests the body in description.details; older responses use settings or a
|
|
4
|
+
* flat description. The body itself may be a Ricos document or serialized text. */
|
|
5
|
+
export function programDescription(program: WixProgram): unknown {
|
|
6
|
+
const raw = (program as { description?: unknown }).description;
|
|
7
|
+
const nested = raw && typeof raw === "object" ? raw as { details?: unknown; nodes?: unknown } : undefined;
|
|
8
|
+
return program.settings?.description ?? nested?.details
|
|
9
|
+
?? (typeof raw === "string" || Array.isArray(nested?.nodes) ? raw : undefined)
|
|
10
|
+
?? program.details?.description;
|
|
11
|
+
}
|
|
@@ -9,6 +9,7 @@ import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
|
9
9
|
import { formatDateMedium, formatMonthDay } from "../_shared/engine-intl.js";
|
|
10
10
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
11
11
|
import { richBody, ROW_IMAGE_WIDTH } from "../_shared/rich-body.js";
|
|
12
|
+
import { readAccess, assertReadAccess, readAccessDenied, readSignInFlags, NO_READ_SIGN_IN } from "../_shared/member-read.js";
|
|
12
13
|
|
|
13
14
|
// Offline-first (data-offline): the post body is a public read-through — when the
|
|
14
15
|
// live fetch fails (offline) fall back to the last-synced row the feed cached under
|
|
@@ -34,6 +35,7 @@ interface CommentRow {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
interface S {
|
|
38
|
+
signInRequired: boolean; noSignIn: boolean; signInScreenId: string;
|
|
37
39
|
status: "loading" | "ready" | "error";
|
|
38
40
|
id: string;
|
|
39
41
|
title: string;
|
|
@@ -214,6 +216,7 @@ function derive(s: S): S {
|
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
const baseState: S = derive({
|
|
219
|
+
...NO_READ_SIGN_IN, signInScreenId: "himi_account",
|
|
217
220
|
status: "loading",
|
|
218
221
|
id: "",
|
|
219
222
|
title: "",
|
|
@@ -280,23 +283,28 @@ async function cachedPostState(s: S, id: string): Promise<S> {
|
|
|
280
283
|
});
|
|
281
284
|
}
|
|
282
285
|
|
|
283
|
-
|
|
286
|
+
let loadVersion = 0;
|
|
287
|
+
async function loadPostState(s: S): Promise<S | undefined> {
|
|
288
|
+
const version = ++loadVersion;
|
|
289
|
+
await svc.prepare();
|
|
290
|
+
const clean = { ...baseState, signInScreenId: s.signInScreenId };
|
|
284
291
|
const id = inputId();
|
|
285
292
|
if (!id) {
|
|
286
|
-
return derive({ ...
|
|
293
|
+
return derive({ ...clean, status: "ready", emptyText: copyParam("blogPost.notFoundText", "Post not found.") });
|
|
287
294
|
}
|
|
288
295
|
try {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
const access = await readAccess();
|
|
297
|
+
const [postResult, commentsResult] = await Promise.allSettled([getPost(id, {}, { preserveAccessErrors: true }), listBlogComments(id)]);
|
|
298
|
+
if (postResult.status === "rejected") throw postResult.reason;
|
|
299
|
+
await assertReadAccess(access);
|
|
300
|
+
if (version !== loadVersion) return undefined;
|
|
293
301
|
const post = (((postResult.value as unknown as { post?: WixPost }).post) ?? postResult.value) as PostWithExtras;
|
|
294
302
|
const commentsUnavailable = commentsResult.status === "rejected";
|
|
295
303
|
const comments = commentsResult.status === "fulfilled" ? commentsResult.value.map(toCommentRow) : [];
|
|
296
304
|
const cover = imageUrl(post);
|
|
297
305
|
const title = post.title || "Untitled post";
|
|
298
306
|
return derive({
|
|
299
|
-
...
|
|
307
|
+
...clean,
|
|
300
308
|
status: "ready",
|
|
301
309
|
id,
|
|
302
310
|
title,
|
|
@@ -319,14 +327,23 @@ async function loadPostState(s: S): Promise<S> {
|
|
|
319
327
|
errorText: "",
|
|
320
328
|
emptyText: "",
|
|
321
329
|
});
|
|
322
|
-
} catch {
|
|
323
|
-
|
|
330
|
+
} catch (error) {
|
|
331
|
+
const flags = await readSignInFlags(error);
|
|
332
|
+
const failed = derive({ ...clean, ...flags, status: "error", id, errorText: copyParam("blogPost.errorText", "This post could not load.") });
|
|
333
|
+
if (version !== loadVersion) return undefined;
|
|
334
|
+
if (readAccessDenied(error)) return failed;
|
|
335
|
+
const access = await readAccess();
|
|
336
|
+
const cached = await cachedPostState(clean, id);
|
|
337
|
+
try { await assertReadAccess(access); }
|
|
338
|
+
catch { return version === loadVersion ? failed : undefined; }
|
|
339
|
+
return version === loadVersion ? cached : undefined;
|
|
324
340
|
}
|
|
325
341
|
}
|
|
326
342
|
|
|
327
343
|
const svc = defineService<S, {
|
|
328
|
-
|
|
329
|
-
|
|
344
|
+
prepare: (s: S) => S;
|
|
345
|
+
load: (s: S, arg?: unknown) => Promise<S | undefined>;
|
|
346
|
+
retry: (s: S) => Promise<S | undefined>;
|
|
330
347
|
like: (s: S) => Promise<S>;
|
|
331
348
|
unlike: (s: S) => Promise<S>;
|
|
332
349
|
toggleSave: (s: S) => S;
|
|
@@ -338,7 +355,12 @@ const svc = defineService<S, {
|
|
|
338
355
|
}>("himiBlogPost", {
|
|
339
356
|
state: baseState,
|
|
340
357
|
actions: {
|
|
341
|
-
|
|
358
|
+
prepare: (s) => ({ ...s, ...baseState }),
|
|
359
|
+
load: (s, arg) => {
|
|
360
|
+
stashCopy(arg);
|
|
361
|
+
const target = (arg as { signInScreenId?: unknown })?.signInScreenId;
|
|
362
|
+
return loadPostState({ ...s, signInScreenId: target === "himi_sign_in" ? target : s.signInScreenId });
|
|
363
|
+
},
|
|
342
364
|
retry: (s) => loadPostState({ ...s, status: "loading", errorText: "" }),
|
|
343
365
|
like: async (s) => {
|
|
344
366
|
if (!s.id) return derive({ ...s, actionMessage: copyParam("blogPost.noPostMessage", "No post selected.") });
|
|
@@ -409,5 +431,6 @@ const svc = defineService<S, {
|
|
|
409
431
|
},
|
|
410
432
|
});
|
|
411
433
|
|
|
434
|
+
svc.prepare();
|
|
412
435
|
svc.load();
|
|
413
436
|
export { svc };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { descriptionBody } from "../_shared/rich-body.js";
|
|
2
|
+
import { programDescription } from "../_shared/program-description.js";
|
|
1
3
|
import { fetchMemberBookings, type MemberBookingRow } from "../_shared/member-bookings.js";
|
|
2
4
|
import { hostSiteFromInput, hostedExploreCards, hostedHiddenFlags, type HostedExploreCard } from "../_shared/branded-lite-hosted.js";
|
|
3
5
|
import { memberAccess, assertMemberAccess } from "../_shared/member-access.js";
|
|
@@ -675,11 +677,9 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
675
677
|
.map((p: WixProgram) => {
|
|
676
678
|
const desc = (p as { description?: unknown }).description;
|
|
677
679
|
const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
|
|
678
|
-
const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
|
|
679
680
|
const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
|
|
680
681
|
const image = programImage(p);
|
|
681
|
-
const details = (p.
|
|
682
|
-
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
682
|
+
const details = descriptionBody(programDescription(p)).bodyText.replace(/\s+/g, " ").trim();
|
|
683
683
|
return {
|
|
684
684
|
id: p.id || name,
|
|
685
685
|
name,
|
|
@@ -15,7 +15,7 @@ import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
|
15
15
|
import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
|
|
16
16
|
import { isProductSaved, toggleSavedProduct } from "../_shared/himi-store-saved.js";
|
|
17
17
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
18
|
-
import {
|
|
18
|
+
import { descriptionBody } from "../_shared/rich-body.js";
|
|
19
19
|
|
|
20
20
|
// Offline-first (data-offline): the detail is a public READ-THROUGH — when the live
|
|
21
21
|
// product fetch fails (offline) fall back to the last-synced row the catalog cached
|
|
@@ -85,9 +85,7 @@ function productPolicyBody(product: StoreProduct, kind: "returns" | "shipping"):
|
|
|
85
85
|
});
|
|
86
86
|
if (!section) return "";
|
|
87
87
|
const html = "plainDescription" in section ? section.plainDescription : undefined;
|
|
88
|
-
|
|
89
|
-
if (typeof section.description === "string") return plainText(section.description.replace(/ /gi, " "));
|
|
90
|
-
return richBody(section.description).bodyText;
|
|
88
|
+
return descriptionBody(html || section.description).bodyText;
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
/** The product id `lastProduct` was read for. A pooled screen re-appearing calls
|
|
@@ -170,13 +168,12 @@ function optionsHint(groups: StoreOption[], selection: StoreOptionSelection): st
|
|
|
170
168
|
return copyTemplate("product.optionsChoose", "Choose {options}", { options: missing.join(", ") });
|
|
171
169
|
}
|
|
172
170
|
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
* `description` is an HTML string: stripped text, no document. */
|
|
171
|
+
/** Catalog V3 returns Ricos objects or serialized documents; V1 returns HTML.
|
|
172
|
+
* Keep plainDescription as the independent fallback, never raw document JSON. */
|
|
176
173
|
function productBody(p: StoreProduct): { description: string; descriptionJson: string } {
|
|
177
174
|
const raw = (p as { description?: unknown }).description;
|
|
178
|
-
const html =
|
|
179
|
-
const { bodyJson, bodyText } =
|
|
175
|
+
const html = descriptionBody(p.plainDescription).bodyText;
|
|
176
|
+
const { bodyJson, bodyText } = descriptionBody(raw, html);
|
|
180
177
|
return { description: html || bodyText, descriptionJson: bodyJson };
|
|
181
178
|
}
|
|
182
179
|
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","stream","wix_api"],
|
|
41
41
|
designTokensPath: "./tokens.json",
|
|
42
42
|
// Native fallback; the T5 splash descriptor owns the editable launch appearance.
|
|
43
43
|
splash: {
|
|
@@ -52829,8 +52829,7 @@ const SCREENS = runtimeSetting({
|
|
|
52829
52829
|
"params": {
|
|
52830
52830
|
"screenId": "himi_owner_lite_search",
|
|
52831
52831
|
"title": "Search records",
|
|
52832
|
-
"subtitle": "Across your business areas"
|
|
52833
|
-
"search": "drawer"
|
|
52832
|
+
"subtitle": "Across your business areas"
|
|
52834
52833
|
}
|
|
52835
52834
|
},
|
|
52836
52835
|
"pressFeedback": "scale",
|
|
@@ -54349,7 +54348,7 @@ const SCREENS = runtimeSetting({
|
|
|
54349
54348
|
"actionId": "tier5.himiOwnerLiteSearch.search"
|
|
54350
54349
|
},
|
|
54351
54350
|
"debounceMs": 200,
|
|
54352
|
-
"
|
|
54351
|
+
"loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
|
|
54353
54352
|
}
|
|
54354
54353
|
}
|
|
54355
54354
|
},
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","media_playback","wix_api","wix_platform","app_info","iap","release_control","clipboard","motion","wix_chat","theme"],
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","media_playback","wix_api","wix_platform","app_info","iap","release_control","clipboard","motion","wix_chat","theme","wix_cashier"],
|
|
41
41
|
cache: {
|
|
42
42
|
"rules": [
|
|
43
43
|
{
|
|
@@ -37014,6 +37014,29 @@ const SCREENS = runtimeSetting({
|
|
|
37014
37014
|
]
|
|
37015
37015
|
}
|
|
37016
37016
|
}
|
|
37017
|
+
},
|
|
37018
|
+
{
|
|
37019
|
+
"id": "himi_member.sign_in",
|
|
37020
|
+
"body": {
|
|
37021
|
+
"package": "himalaya",
|
|
37022
|
+
"type": "Button",
|
|
37023
|
+
"props": {
|
|
37024
|
+
"title": "Sign in",
|
|
37025
|
+
"style": "primary",
|
|
37026
|
+
"action": {
|
|
37027
|
+
"actionId": "himi.open",
|
|
37028
|
+
"params": {
|
|
37029
|
+
"screenId": "himi_sign_in",
|
|
37030
|
+
"mode": "signin",
|
|
37031
|
+
"modal": "1"
|
|
37032
|
+
},
|
|
37033
|
+
"presentation": "sheet"
|
|
37034
|
+
}
|
|
37035
|
+
}
|
|
37036
|
+
},
|
|
37037
|
+
"modifiers": {
|
|
37038
|
+
"hiddenPath": "$service:spacesMemberDetail.noSignIn"
|
|
37039
|
+
}
|
|
37017
37040
|
}
|
|
37018
37041
|
]
|
|
37019
37042
|
}
|
|
@@ -37531,6 +37554,7 @@ const SCREENS = runtimeSetting({
|
|
|
37531
37554
|
}
|
|
37532
37555
|
},
|
|
37533
37556
|
"modifiers": {
|
|
37557
|
+
"hiddenPath": "$service:spacesChatThread.signInRequired",
|
|
37534
37558
|
"padding": {
|
|
37535
37559
|
"leading": 16,
|
|
37536
37560
|
"trailing": 16,
|
|
@@ -37538,6 +37562,29 @@ const SCREENS = runtimeSetting({
|
|
|
37538
37562
|
"bottom": 0
|
|
37539
37563
|
}
|
|
37540
37564
|
}
|
|
37565
|
+
},
|
|
37566
|
+
{
|
|
37567
|
+
"id": "himi_chat_thread.sign_in",
|
|
37568
|
+
"body": {
|
|
37569
|
+
"package": "himalaya",
|
|
37570
|
+
"type": "Button",
|
|
37571
|
+
"props": {
|
|
37572
|
+
"title": "Sign in",
|
|
37573
|
+
"style": "primary",
|
|
37574
|
+
"action": {
|
|
37575
|
+
"actionId": "himi.open",
|
|
37576
|
+
"params": {
|
|
37577
|
+
"screenId": "himi_sign_in",
|
|
37578
|
+
"mode": "signin",
|
|
37579
|
+
"modal": "1"
|
|
37580
|
+
},
|
|
37581
|
+
"presentation": "sheet"
|
|
37582
|
+
}
|
|
37583
|
+
}
|
|
37584
|
+
},
|
|
37585
|
+
"modifiers": {
|
|
37586
|
+
"hiddenPath": "$service:spacesChatThread.noSignIn"
|
|
37587
|
+
}
|
|
37541
37588
|
}
|
|
37542
37589
|
]
|
|
37543
37590
|
}
|
|
@@ -57216,6 +57263,29 @@ const SCREENS = runtimeSetting({
|
|
|
57216
57263
|
]
|
|
57217
57264
|
}
|
|
57218
57265
|
}
|
|
57266
|
+
},
|
|
57267
|
+
{
|
|
57268
|
+
"id": "himi_forum_post.sign_in",
|
|
57269
|
+
"body": {
|
|
57270
|
+
"package": "himalaya",
|
|
57271
|
+
"type": "Button",
|
|
57272
|
+
"props": {
|
|
57273
|
+
"title": "Sign in",
|
|
57274
|
+
"style": "primary",
|
|
57275
|
+
"action": {
|
|
57276
|
+
"actionId": "himi.open",
|
|
57277
|
+
"params": {
|
|
57278
|
+
"screenId": "himi_sign_in",
|
|
57279
|
+
"mode": "signin",
|
|
57280
|
+
"modal": "1"
|
|
57281
|
+
},
|
|
57282
|
+
"presentation": "sheet"
|
|
57283
|
+
}
|
|
57284
|
+
}
|
|
57285
|
+
},
|
|
57286
|
+
"modifiers": {
|
|
57287
|
+
"hiddenPath": "$service:spacesForumPost.noSignIn"
|
|
57288
|
+
}
|
|
57219
57289
|
}
|
|
57220
57290
|
]
|
|
57221
57291
|
}
|
|
@@ -60393,6 +60463,9 @@ const SCREENS = runtimeSetting({
|
|
|
60393
60463
|
"style": "body",
|
|
60394
60464
|
"color": "textSecondary"
|
|
60395
60465
|
}
|
|
60466
|
+
},
|
|
60467
|
+
"modifiers": {
|
|
60468
|
+
"hiddenPath": "$service:himiMyEvents.signInRequired"
|
|
60396
60469
|
}
|
|
60397
60470
|
},
|
|
60398
60471
|
{
|
|
@@ -60407,6 +60480,32 @@ const SCREENS = runtimeSetting({
|
|
|
60407
60480
|
"actionId": "tier5.himiMyEvents.load"
|
|
60408
60481
|
}
|
|
60409
60482
|
}
|
|
60483
|
+
},
|
|
60484
|
+
"modifiers": {
|
|
60485
|
+
"hiddenPath": "$service:himiMyEvents.signInRequired"
|
|
60486
|
+
}
|
|
60487
|
+
},
|
|
60488
|
+
{
|
|
60489
|
+
"id": "himi_my_events.retry.sign_in",
|
|
60490
|
+
"body": {
|
|
60491
|
+
"package": "himalaya",
|
|
60492
|
+
"type": "Button",
|
|
60493
|
+
"props": {
|
|
60494
|
+
"title": "Sign in",
|
|
60495
|
+
"style": "primary",
|
|
60496
|
+
"action": {
|
|
60497
|
+
"actionId": "himi.open",
|
|
60498
|
+
"params": {
|
|
60499
|
+
"screenId": "himi_sign_in",
|
|
60500
|
+
"mode": "signin",
|
|
60501
|
+
"modal": "1"
|
|
60502
|
+
},
|
|
60503
|
+
"presentation": "sheet"
|
|
60504
|
+
}
|
|
60505
|
+
}
|
|
60506
|
+
},
|
|
60507
|
+
"modifiers": {
|
|
60508
|
+
"hiddenPath": "$service:himiMyEvents.noSignIn"
|
|
60410
60509
|
}
|
|
60411
60510
|
}
|
|
60412
60511
|
]
|
|
@@ -60837,7 +60936,11 @@ const SCREENS = runtimeSetting({
|
|
|
60837
60936
|
"bottom": 24
|
|
60838
60937
|
},
|
|
60839
60938
|
"onAppear": {
|
|
60840
|
-
"actionId": "tier5.himiMyEvents.load"
|
|
60939
|
+
"actionId": "tier5.himiMyEvents.load",
|
|
60940
|
+
"params": {
|
|
60941
|
+
"signInScreenId": "himi_sign_in",
|
|
60942
|
+
"memberOnly": "1"
|
|
60943
|
+
}
|
|
60841
60944
|
}
|
|
60842
60945
|
}
|
|
60843
60946
|
},
|
|
@@ -99067,6 +99170,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
99067
99170
|
"hiddenPath": "$service:himiBrandedProgram.hidePriceRetry"
|
|
99068
99171
|
}
|
|
99069
99172
|
},
|
|
99173
|
+
{
|
|
99174
|
+
"id": "himi_program.description_rich",
|
|
99175
|
+
"body": {
|
|
99176
|
+
"package": "himalaya",
|
|
99177
|
+
"type": "RichContent",
|
|
99178
|
+
"props": {
|
|
99179
|
+
"documentJsonPath": "$service:himiBrandedProgram.descriptionJson",
|
|
99180
|
+
"onLink": {
|
|
99181
|
+
"actionId": "tier5.himiBrandedProgram.openLink"
|
|
99182
|
+
},
|
|
99183
|
+
"fallback": {
|
|
99184
|
+
"id": "",
|
|
99185
|
+
"body": {
|
|
99186
|
+
"package": "himalaya",
|
|
99187
|
+
"type": "Text",
|
|
99188
|
+
"props": {
|
|
99189
|
+
"text": "$service:himiBrandedProgram.description",
|
|
99190
|
+
"style": "body",
|
|
99191
|
+
"color": "textSecondary"
|
|
99192
|
+
}
|
|
99193
|
+
}
|
|
99194
|
+
}
|
|
99195
|
+
}
|
|
99196
|
+
}
|
|
99197
|
+
},
|
|
99070
99198
|
{
|
|
99071
99199
|
"id": "",
|
|
99072
99200
|
"body": {
|
|
@@ -99078,6 +99206,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
99078
99206
|
"color": "textSecondary",
|
|
99079
99207
|
"collapseWhenEmpty": true
|
|
99080
99208
|
}
|
|
99209
|
+
},
|
|
99210
|
+
"modifiers": {
|
|
99211
|
+
"hiddenPath": "$service:himiBrandedProgram.hasDescriptionJson"
|
|
99081
99212
|
}
|
|
99082
99213
|
}
|
|
99083
99214
|
]
|
|
@@ -102873,6 +103004,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
102873
103004
|
"style": "body",
|
|
102874
103005
|
"color": "textSecondary"
|
|
102875
103006
|
}
|
|
103007
|
+
},
|
|
103008
|
+
"modifiers": {
|
|
103009
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
102876
103010
|
}
|
|
102877
103011
|
},
|
|
102878
103012
|
{
|
|
@@ -102887,6 +103021,32 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
102887
103021
|
"actionId": "tier5.himiBlogPost.retry"
|
|
102888
103022
|
}
|
|
102889
103023
|
}
|
|
103024
|
+
},
|
|
103025
|
+
"modifiers": {
|
|
103026
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
103027
|
+
}
|
|
103028
|
+
},
|
|
103029
|
+
{
|
|
103030
|
+
"id": "himi_blog_post.retry.sign_in",
|
|
103031
|
+
"body": {
|
|
103032
|
+
"package": "himalaya",
|
|
103033
|
+
"type": "Button",
|
|
103034
|
+
"props": {
|
|
103035
|
+
"title": "Sign in",
|
|
103036
|
+
"style": "primary",
|
|
103037
|
+
"action": {
|
|
103038
|
+
"actionId": "himi.open",
|
|
103039
|
+
"params": {
|
|
103040
|
+
"screenId": "himi_sign_in",
|
|
103041
|
+
"mode": "signin",
|
|
103042
|
+
"modal": "1"
|
|
103043
|
+
},
|
|
103044
|
+
"presentation": "sheet"
|
|
103045
|
+
}
|
|
103046
|
+
}
|
|
103047
|
+
},
|
|
103048
|
+
"modifiers": {
|
|
103049
|
+
"hiddenPath": "$service:himiBlogPost.noSignIn"
|
|
102890
103050
|
}
|
|
102891
103051
|
}
|
|
102892
103052
|
]
|
|
@@ -103591,7 +103751,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
103591
103751
|
"onAppear": {
|
|
103592
103752
|
"actionId": "tier5.himiBlogPost.load",
|
|
103593
103753
|
"params": {
|
|
103594
|
-
"id": "__HIMI_ID__"
|
|
103754
|
+
"id": "__HIMI_ID__",
|
|
103755
|
+
"signInScreenId": "himi_sign_in"
|
|
103595
103756
|
}
|
|
103596
103757
|
}
|
|
103597
103758
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { memberAccess, memberAccessChanged, memberAccessDenied, type MemberAccess } from "./member-access.js";
|
|
2
|
+
import { needsSignIn } from "./branded-load-state.js";
|
|
3
|
+
|
|
4
|
+
/** Public reads stay public. Capture their reader only to reject a result that
|
|
5
|
+
* belongs to the identity/site which was active before the request. */
|
|
6
|
+
export interface ReadAccess { member: MemberAccess; tenant: string; }
|
|
7
|
+
export async function readAccess(): Promise<ReadAccess> {
|
|
8
|
+
const member = await memberAccess();
|
|
9
|
+
const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
|
|
10
|
+
return { member, tenant: JSON.stringify([input?.__himiPreferenceAccount ?? "", input?.__himiPreferenceBusiness ?? ""]) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function assertReadAccess(access: ReadAccess): Promise<void> {
|
|
14
|
+
const current = await readAccess();
|
|
15
|
+
if (current.member.email !== access.member.email || current.member.scope !== access.member.scope || current.tenant !== access.tenant) throw memberAccessChanged();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Bridge errors put the actual status first; codes quoted inside the body are data. */
|
|
19
|
+
function httpStatus(error: unknown): string | undefined {
|
|
20
|
+
return /^HTTP\s*(\d{3})\b/i.exec(String((error as { message?: unknown })?.message ?? error))?.[1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const NO_READ_SIGN_IN = { signInRequired: false, noSignIn: true };
|
|
24
|
+
|
|
25
|
+
/** An authorization refusal must not fall back to a previously cached article. */
|
|
26
|
+
export function readAccessDenied(error: unknown): boolean {
|
|
27
|
+
let cause = error;
|
|
28
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
29
|
+
const status = httpStatus(cause);
|
|
30
|
+
if (status ? status === "401" || status === "403" : memberAccessDenied(cause) || needsSignIn(cause)) return true;
|
|
31
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A permission failure alone says nothing about whether signing in can help.
|
|
37
|
+
* In particular, a private member profile can be unavailable to members too.
|
|
38
|
+
* Only an authentication refusal for a current visitor offers Sign in. Follow
|
|
39
|
+
* SDK wrapper causes (blog getPost preserves its original read error there). */
|
|
40
|
+
export async function readSignInFlags(error: unknown): Promise<typeof NO_READ_SIGN_IN> {
|
|
41
|
+
let authenticationRequired = false;
|
|
42
|
+
let permissionRefused = false;
|
|
43
|
+
let cause = error;
|
|
44
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
45
|
+
const status = httpStatus(cause);
|
|
46
|
+
permissionRefused ||= status === "403";
|
|
47
|
+
authenticationRequired ||= status ? status === "401" : needsSignIn(cause);
|
|
48
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
49
|
+
}
|
|
50
|
+
const signInRequired = !permissionRefused && authenticationRequired && !(await memberAccess()).email;
|
|
51
|
+
return { signInRequired, noSignIn: !signInRequired };
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
2
|
+
|
|
3
|
+
/** v3 nests the body in description.details; older responses use settings or a
|
|
4
|
+
* flat description. The body itself may be a Ricos document or serialized text. */
|
|
5
|
+
export function programDescription(program: WixProgram): unknown {
|
|
6
|
+
const raw = (program as { description?: unknown }).description;
|
|
7
|
+
const nested = raw && typeof raw === "object" ? raw as { details?: unknown; nodes?: unknown } : undefined;
|
|
8
|
+
return program.settings?.description ?? nested?.details
|
|
9
|
+
?? (typeof raw === "string" || Array.isArray(nested?.nodes) ? raw : undefined)
|
|
10
|
+
?? program.details?.description;
|
|
11
|
+
}
|