@wix/himalaya-cli 0.1557.0 → 0.1558.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/schema.d.ts +6 -4
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/index.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +2 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-media.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-rich-content.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores-collections.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores.mjs +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-video.mjs +1 -0
- package/dist/cli.mjs +164 -61
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/getting-started.md +4 -1
- package/guides/himi-json-output.md +8 -0
- package/guides/himi-skill.md +4 -1
- package/guides/one-app-to-himi-app.md +18 -6
- package/guides/sdk-reference.json +2 -2
- package/guides/skill.md +4 -1
- package/guides/splash-screens.md +22 -1
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/branded-lite/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-group/index.ts +3 -6
- package/templates/branded-lite/tier5-src/himi-branded-lite-groups/index.ts +2 -15
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +3 -9
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +1 -9
- package/templates/community-club/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/community-club/tier5-src/himi-branded-lite-group/index.ts +3 -6
- package/templates/community-club/tier5-src/himi-branded-lite-groups/index.ts +2 -15
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/events-meetups/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-group/index.ts +3 -6
- package/templates/events-meetups/tier5-src/himi-branded-lite-groups/index.ts +2 -15
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/gym-fitness/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/nonprofit-cause/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group/index.ts +3 -6
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-groups/index.ts +2 -15
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/podcast-creator/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/portfolio-creative/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/restaurant/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/retail-store/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/yoga-studio/tier5-src/_shared/himi-media.ts +29 -0
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +3 -4
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +3 -9
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +1 -9
|
@@ -1167,7 +1167,7 @@
|
|
|
1167
1167
|
"name": "SplashConfig",
|
|
1168
1168
|
"kind": "interface",
|
|
1169
1169
|
"jsdoc": "/**\n * The IN-APP splash — the brand surface between the OS splash handing off and the\n * app being ready. The *OS* splash is generated at build time from the same\n * `splash` block in config.ts and can never be OTA (iOS reads `UILaunchScreen`\n * before any app code runs); this layer is OTA. Artwork stays baked in the binary\n * — `logoAsset` / `backgroundImageAsset` name a baked asset, so only the\n * composition travels.\n */",
|
|
1170
|
-
"decl": "export interface SplashConfig {\n /** Master switch / kill-switch. Absent or false ⇒ no surface. */\n enabled?: boolean;\n /** Background color TOKEN name (e.g. \"accentPrimary\"). */\n backgroundColor?: TokenName;\n /** Gradient token name; wins over `backgroundColor`. */\n backgroundGradient?: TokenName;\n /** BAKED asset name for a full-bleed background image; wins over color/gradient. */\n backgroundImageAsset?: string;\n /** BAKED asset name for the centered logo (e.g. \"LaunchLogo\"). */\n logoAsset?: string;\n /** Logo width in pt/dp. 0 / absent ⇒ 96. */\n logoWidth?: number;\n /** Optional wordmark line. NOTE: a literal string, so an OTA edit bypasses\n * localization (same caveat as `NavTabItem.label`). */\n caption?: string;\n /** Typography token for the caption. Absent ⇒ \"title1\". */\n captionStyle?: TokenName;\n /** Color token for the caption. Absent ⇒ \"textOnAccent\". */\n captionColor?: TokenName;\n /** Absent / unknown ⇒ \"centered\". */\n layout?: SplashLayout;\n /** Anti-flash floor, ms. Clamped client-side to [0, 2000]. */\n minVisibleMs?: number;\n /** Hard ceiling on the readiness hold, ms — a stuck boot can never wedge the\n * UI. 0 / absent ⇒ 3000. Clamped client-side to [0, 8000]. */\n maxHoldMs?: number;\n /** What readiness waits for beyond the seed. Absent / \"\" ⇒
|
|
1170
|
+
"decl": "export interface SplashConfig {\n /** Master switch / kill-switch. Absent or false ⇒ no surface. */\n enabled?: boolean;\n /** Background color TOKEN name (e.g. \"accentPrimary\"). */\n backgroundColor?: TokenName;\n /** Gradient token name; wins over `backgroundColor`. */\n backgroundGradient?: TokenName;\n /** BAKED asset name for a full-bleed background image; wins over color/gradient. */\n backgroundImageAsset?: string;\n /** BAKED asset name for the centered logo (e.g. \"LaunchLogo\"). */\n logoAsset?: string;\n /** Logo width in pt/dp. 0 / absent ⇒ 96. */\n logoWidth?: number;\n /** Optional wordmark line. NOTE: a literal string, so an OTA edit bypasses\n * localization (same caveat as `NavTabItem.label`). */\n caption?: string;\n /** Typography token for the caption. Absent ⇒ \"title1\". */\n captionStyle?: TokenName;\n /** Color token for the caption. Absent ⇒ \"textOnAccent\". */\n captionColor?: TokenName;\n /** Absent / unknown ⇒ \"centered\". */\n layout?: SplashLayout;\n /** Anti-flash floor, ms. Clamped client-side to [0, 2000]. */\n minVisibleMs?: number;\n /** Hard ceiling on the readiness hold, ms — a stuck boot can never wedge the\n * UI. 0 / absent ⇒ 3000. Clamped client-side to [0, 8000]. */\n maxHoldMs?: number;\n /** What readiness waits for beyond the seed. Absent / \"\" ⇒ the first\n * screen's paint, where the shell mounts its content under the splash (every shell\n * built on `HimalayaLaunchGate` / `awaitingFirstScreen`), else the seed alone.\n * \"seeded\" ⇒ the seed alone, always. \"first_content\" ⇒ the first WebView page (a\n * site shell). All still under `maxHoldMs`. Unknown coalesces to \"\" on device. */\n readySignal?: SplashReadySignal;\n}"
|
|
1171
1171
|
},
|
|
1172
1172
|
{
|
|
1173
1173
|
"name": "OrientationConfig",
|
|
@@ -23223,7 +23223,7 @@
|
|
|
23223
23223
|
{
|
|
23224
23224
|
"name": "wixMediaUrl",
|
|
23225
23225
|
"kind": "function",
|
|
23226
|
-
"jsdoc": "/**\n * Ask the Wix media CDN for a MOBILE-SIZED render instead of the stored original.\n *\n * `.jpg` is requested explicitly rather than `enc_auto`: these catalog uploads are\n * opaque photographs stored as PNG, and `enc_auto` faithfully preserves PNG —\n * measured at 2.4MB versus 220KB for the same 1000px render. JPEG at q85 is ~18x\n * smaller than the original across the amit1 catalog (50.5MB -> 2.7MB).\n *\n * Wix image handles and bare media ids are resolved before sizing. Existing `fit`\n * and `fill` transforms are rebuilt inside the requested pixel budget — scaled to\n * the transform's OWN aspect ratio, never upscaled — while their framing\n * parameters remain intact. Other transform modes and non-Wix HTTP(S) URLs stay\n * untouched. Unsupported references normalize to an empty string.\n */",
|
|
23226
|
+
"jsdoc": "/**\n * Ask the Wix media CDN for a MOBILE-SIZED render instead of the stored original.\n *\n * `.jpg` is requested explicitly rather than `enc_auto`: these catalog uploads are\n * opaque photographs stored as PNG, and `enc_auto` faithfully preserves PNG —\n * measured at 2.4MB versus 220KB for the same 1000px render. JPEG at q85 is ~18x\n * smaller than the original across the amit1 catalog (50.5MB -> 2.7MB).\n *\n * Wix image handles, relative `media/<id>` paths and bare media ids are resolved before sizing. Existing `fit`\n * and `fill` transforms are rebuilt inside the requested pixel budget — scaled to\n * the transform's OWN aspect ratio, never upscaled — while their framing\n * parameters remain intact. Other transform modes and non-Wix HTTP(S) URLs stay\n * untouched. Unsupported references normalize to an empty string.\n */",
|
|
23227
23227
|
"decl": "export function wixMediaUrl(value: unknown, size: WixImageSize | null = WIX_LIST_IMAGE): string;"
|
|
23228
23228
|
},
|
|
23229
23229
|
{
|
package/guides/skill.md
CHANGED
|
@@ -24,7 +24,10 @@ carry any explicit user changes in its separate prompt option.
|
|
|
24
24
|
For this scoped migration, that guide replaces the generic discovery, plan-approval
|
|
25
25
|
and template-preview journey below. Write the reconstruction plan, `SPEC.md`,
|
|
26
26
|
`MOBILE-UX.md` and migration report, then continue implementation and the authorized
|
|
27
|
-
same-site
|
|
27
|
+
same-site One App visitor binding, checks and hosted draft. Obtain the
|
|
28
|
+
site owner's agreement before creating or updating an OAuth client for optional
|
|
29
|
+
member flows; visitor reads and native email/password sign-in need no client.
|
|
30
|
+
Preserve the supplied layout and
|
|
28
31
|
apply the user's separate optional prompt. Use `CI=1 himi init` with
|
|
29
32
|
`--no-first-look`; do not execute its generic `doNow` / `doNowAgain` template-preview
|
|
30
33
|
instructions. Show the reconstructed app after adapting it and checking its data.
|
package/guides/splash-screens.md
CHANGED
|
@@ -148,7 +148,7 @@ splash: {
|
|
|
148
148
|
layout: "centeredStacked", // centered | centeredStacked | logoOnly
|
|
149
149
|
minVisibleMs: 600, // anti-flash floor, clamped ≤ 2000
|
|
150
150
|
maxHoldMs: 3000, // hard ceiling, clamped ≤ 8000
|
|
151
|
-
readySignal: "", // "" | "first_content" — see "Readiness, not a timer"
|
|
151
|
+
readySignal: "", // "" | "seeded" | "first_content" — see "Readiness, not a timer"
|
|
152
152
|
}
|
|
153
153
|
```
|
|
154
154
|
|
|
@@ -172,6 +172,27 @@ strict-decode throw disables **all** OTA. Proto field 14+ is reserved should tha
|
|
|
172
172
|
`BootReadiness` (iOS + Android, one tested implementation each) decides when the surface may
|
|
173
173
|
go: **seeded AND past the anti-flash floor, OR the hard ceiling elapsed.**
|
|
174
174
|
|
|
175
|
+
**The splash waits for the first screen.** Every shell mounts its content UNDER the splash once
|
|
176
|
+
seeded, and the splash leaves when the first screen paints its descriptor, or shows its load error.
|
|
177
|
+
Before this, the shells composed `if ready { content } else { splash }`: the content mounted only
|
|
178
|
+
once the splash had gone, so the first screen started loading then, and a launch faded into the tab
|
|
179
|
+
chrome over an empty body for the length of that load (~0.4 s in demo-app). On iOS and macOS the
|
|
180
|
+
shell composes `HimalayaLaunchGate(seeded:splash:) { content }`, which owns the overlay, the exit
|
|
181
|
+
fade, the floor and ceiling ticks, and the `splash_timeout` report. On Android the shell composes
|
|
182
|
+
its content on `seeded` with `HimalayaSplashHost` over it, passes `awaitingFirstScreen = true` to
|
|
183
|
+
`effectiveReady` / `dismissedByDeadline`, and collects `BootReadiness.firstScreen` so the paint
|
|
184
|
+
re-evaluates readiness. Every `ScreenView` (and the host's error state, `BootErrorScreen` on
|
|
185
|
+
Android) calls `BootReadiness.markFirstScreen()`. The ceiling still applies. `readySignal: "seeded"`
|
|
186
|
+
releases the splash on the seed alone. Only a shell that mounts its content under the splash may
|
|
187
|
+
wait for the first screen: one that mounts it after the splash leaves would hold to the ceiling,
|
|
188
|
+
which is why `effectiveReady(_:)` without `awaitingFirstScreen` still means the seed alone.
|
|
189
|
+
`splash-parity.test.ts` fails a shell that shows its splash instead of its content.
|
|
190
|
+
|
|
191
|
+
The splash surface eases its logo and caption in once per launch. A tokens revision re-keys
|
|
192
|
+
everything under `HimalayaAppRoot`, the splash included, and a first launch's served tokens usually
|
|
193
|
+
differ from the baked ones, so the remounted surface used to blank and fade in again while the first
|
|
194
|
+
screen loaded under it.
|
|
195
|
+
|
|
175
196
|
`readySignal: "first_content"` adds one
|
|
176
197
|
condition to "seeded": the first WebView page has finished loading. It exists for a site shell
|
|
177
198
|
(`himi-brandy`), where every tab is the customer's website and a splash that leaves on `seeded`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1558.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": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "3e25c3d6c9c9f3a1d3abe87df97e8a1f848ce1a712d321126ce42454"
|
|
75
75
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Cover selection is shared by list, detail and home so the same Wix object never
|
|
2
|
+
// loses its image on navigation. Relative SDK imports are rewritten by the template generator.
|
|
3
|
+
import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
|
|
4
|
+
import type { WixGroup } from "@himalaya/wix-groups";
|
|
5
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
6
|
+
|
|
7
|
+
function firstImage(references: unknown[], size: WixImageSize): string {
|
|
8
|
+
for (const reference of references) {
|
|
9
|
+
const image = wixMediaUrl(reference, size);
|
|
10
|
+
if (image) return image;
|
|
11
|
+
}
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
16
|
+
const cover = group.coverImage as {
|
|
17
|
+
url?: unknown;
|
|
18
|
+
image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
|
|
19
|
+
} | undefined;
|
|
20
|
+
return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
24
|
+
const cover = (program as { coverImage?: unknown }).coverImage;
|
|
25
|
+
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
|
|
26
|
+
const description = (program as { description?: unknown }).description;
|
|
27
|
+
const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
|
|
28
|
+
return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
|
|
29
|
+
}
|
|
@@ -24,6 +24,7 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
|
|
|
24
24
|
import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
|
|
25
25
|
import { readSavedIds } from "../_shared/himi-blog-saved.js";
|
|
26
26
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
27
|
+
import { groupImage, programImage } from "../_shared/himi-media.js";
|
|
27
28
|
import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
|
|
28
29
|
import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
|
|
29
30
|
|
|
@@ -676,8 +677,7 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
676
677
|
const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
|
|
677
678
|
const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
|
|
678
679
|
const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
|
|
679
|
-
const
|
|
680
|
-
const image = (typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url) || p.image || "";
|
|
680
|
+
const image = programImage(p);
|
|
681
681
|
const details = (p.settings?.description ?? nestedDetails ?? (typeof desc === "string" ? desc : "") ?? "")
|
|
682
682
|
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
683
683
|
return {
|
|
@@ -732,8 +732,7 @@ function homeTimeLabel(value?: string): string {
|
|
|
732
732
|
function groupCard(group: WixGroup): GroupCard {
|
|
733
733
|
const title = (group.name || copyParam("home.groupFallbackTitle", "Group")).trim();
|
|
734
734
|
const count = group.membersCount ?? group.memberCount;
|
|
735
|
-
const
|
|
736
|
-
const image = cover?.image?.url ?? cover?.url ?? "";
|
|
735
|
+
const image = groupImage(group);
|
|
737
736
|
return {
|
|
738
737
|
id: group.id || title,
|
|
739
738
|
title,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Cover selection is shared by list, detail and home so the same Wix object never
|
|
2
|
+
// loses its image on navigation. Relative SDK imports are rewritten by the template generator.
|
|
3
|
+
import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
|
|
4
|
+
import type { WixGroup } from "@himalaya/wix-groups";
|
|
5
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
6
|
+
|
|
7
|
+
function firstImage(references: unknown[], size: WixImageSize): string {
|
|
8
|
+
for (const reference of references) {
|
|
9
|
+
const image = wixMediaUrl(reference, size);
|
|
10
|
+
if (image) return image;
|
|
11
|
+
}
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
16
|
+
const cover = group.coverImage as {
|
|
17
|
+
url?: unknown;
|
|
18
|
+
image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
|
|
19
|
+
} | undefined;
|
|
20
|
+
return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
24
|
+
const cover = (program as { coverImage?: unknown }).coverImage;
|
|
25
|
+
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
|
|
26
|
+
const description = (program as { description?: unknown }).description;
|
|
27
|
+
const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
|
|
28
|
+
return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
|
|
29
|
+
}
|
|
@@ -4,6 +4,8 @@ import { getGroup, joinGroup, leaveGroup, queryMembers as queryGroupMembers, typ
|
|
|
4
4
|
import { getMember } from "@himalaya/wix-members";
|
|
5
5
|
import { signedInEmail } from "../_shared/himi-account";
|
|
6
6
|
import { initials } from "../_shared/himi-text.js";
|
|
7
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
8
|
+
import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
|
|
7
9
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
8
10
|
import { get as dbGet, upsert as dbUpsert, type DbEntity } from "@himalaya/local-db";
|
|
9
11
|
import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error";
|
|
@@ -87,11 +89,6 @@ function inputId(): string {
|
|
|
87
89
|
return input?.screenId?.startsWith("himi_group_") ? input.screenId.slice("himi_group_".length) : "";
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
function coverImage(group: WixGroup): string {
|
|
91
|
-
const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
|
|
92
|
-
return cover?.image?.url ?? cover?.url ?? "";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
92
|
function membershipState(state: State, joined: boolean, signedIn: boolean): State {
|
|
96
93
|
const isPublic = state.privacy === "Public" || state.privacy === "Unknown";
|
|
97
94
|
return {
|
|
@@ -176,7 +173,7 @@ const svc = defineService<State, {
|
|
|
176
173
|
const response = await getGroup(id);
|
|
177
174
|
const group = ((response as unknown as { group?: WixGroup }).group ?? response) as WixGroup;
|
|
178
175
|
const name = (group.name || copyParam("group.groupFallbackName", "Group")).trim();
|
|
179
|
-
const image =
|
|
176
|
+
const image = groupImage(group, WIX_DETAIL_IMAGE);
|
|
180
177
|
const count = group.membersCount ?? group.memberCount;
|
|
181
178
|
const privacy = (group.privacyStatus || "UNKNOWN").toLowerCase();
|
|
182
179
|
const privacyLabel = privacy[0].toUpperCase() + privacy.slice(1);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineService } from "../_shared/collection-service.js";
|
|
2
2
|
import { queryGroups, type WixGroup } from "@himalaya/wix-groups";
|
|
3
|
-
import {
|
|
3
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
4
4
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
5
5
|
import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
6
6
|
|
|
@@ -45,23 +45,10 @@ interface State {
|
|
|
45
45
|
noEmpty: boolean;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Social Groups stores a cover as `coverImage.image.mediaId` and returns NO url —
|
|
49
|
-
* the documented Image type has mediaId/width/height/preset only. Probing just
|
|
50
|
-
* `.url` therefore left every real cover unrendered (accent band + initials).
|
|
51
|
-
* Keep the url probes for other shapes, then derive from the media id. */
|
|
52
|
-
function coverImage(group: WixGroup): string {
|
|
53
|
-
const cover = group.coverImage as
|
|
54
|
-
| { url?: string; image?: { url?: string; mediaId?: string; id?: string } }
|
|
55
|
-
| undefined;
|
|
56
|
-
return wixMediaUrl(
|
|
57
|
-
cover?.image?.url ?? cover?.url ?? cover?.image?.mediaId ?? cover?.image?.id,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
48
|
function groupRow(group: WixGroup): GroupRow {
|
|
62
49
|
const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
|
|
63
50
|
const count = group.membersCount ?? group.memberCount;
|
|
64
|
-
const image =
|
|
51
|
+
const image = groupImage(group);
|
|
65
52
|
return {
|
|
66
53
|
id: group.id || title,
|
|
67
54
|
title,
|
|
@@ -24,6 +24,7 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
|
|
|
24
24
|
import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
|
|
25
25
|
import { readSavedIds } from "../_shared/himi-blog-saved.js";
|
|
26
26
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
27
|
+
import { groupImage, programImage } from "../_shared/himi-media.js";
|
|
27
28
|
import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
|
|
28
29
|
import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
|
|
29
30
|
|
|
@@ -676,8 +677,7 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
676
677
|
const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
|
|
677
678
|
const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
|
|
678
679
|
const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
|
|
679
|
-
const
|
|
680
|
-
const image = (typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url) || p.image || "";
|
|
680
|
+
const image = programImage(p);
|
|
681
681
|
const details = (p.settings?.description ?? nestedDetails ?? (typeof desc === "string" ? desc : "") ?? "")
|
|
682
682
|
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
683
683
|
return {
|
|
@@ -732,8 +732,7 @@ function homeTimeLabel(value?: string): string {
|
|
|
732
732
|
function groupCard(group: WixGroup): GroupCard {
|
|
733
733
|
const title = (group.name || copyParam("home.groupFallbackTitle", "Group")).trim();
|
|
734
734
|
const count = group.membersCount ?? group.memberCount;
|
|
735
|
-
const
|
|
736
|
-
const image = cover?.image?.url ?? cover?.url ?? "";
|
|
735
|
+
const image = groupImage(group);
|
|
737
736
|
return {
|
|
738
737
|
id: group.id || title,
|
|
739
738
|
title,
|
|
@@ -21,6 +21,8 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
|
21
21
|
// offers Get access in place of Enrol and sells only through the shared checkout, never through the
|
|
22
22
|
// Wix payment rail.
|
|
23
23
|
import { defineService as registerService } from "@himalaya/state";
|
|
24
|
+
import { programImage } from "../_shared/himi-media.js";
|
|
25
|
+
import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
|
|
24
26
|
import {
|
|
25
27
|
getProgram,
|
|
26
28
|
joinProgram,
|
|
@@ -122,14 +124,6 @@ function detailsOf(p: WixProgram): string {
|
|
|
122
124
|
.trim();
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
function imageOf(p: WixProgram): string {
|
|
126
|
-
const desc = (p as { description?: unknown }).description;
|
|
127
|
-
const nested = desc && typeof desc === "object" ? (desc as { image?: { url?: string } }).image?.url : undefined;
|
|
128
|
-
const cover = (p as { coverImage?: unknown }).coverImage;
|
|
129
|
-
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url;
|
|
130
|
-
return (coverUrl || p.image || nested || "").trim();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
127
|
function metaOf(p: WixProgram): string {
|
|
134
128
|
const summary = (p as { contentSummary?: { stepCount?: number; sectionCount?: number } }).contentSummary;
|
|
135
129
|
const bits: string[] = [];
|
|
@@ -160,7 +154,7 @@ function ready(state: State, p: WixProgram | undefined): State {
|
|
|
160
154
|
return derive({ ...state, status: "ready", name: "", description: "", meta: "", priceLine: "", image: "", initial: "", emptyText: copyParam("program.emptyText", "This program is no longer available.") });
|
|
161
155
|
}
|
|
162
156
|
const name = titleOf(p);
|
|
163
|
-
const image =
|
|
157
|
+
const image = programImage(p, WIX_DETAIL_IMAGE);
|
|
164
158
|
return derive({
|
|
165
159
|
...state,
|
|
166
160
|
status: "ready",
|
|
@@ -14,7 +14,7 @@ import { decorateCollection } from "../_shared/collection-service.js";
|
|
|
14
14
|
import { queryPrograms, type WixProgram } from "@himalaya/wix-online-programs";
|
|
15
15
|
import { iapPriceFields, onlineProgramItem, type IapPollDirective } from "@himalaya/iap";
|
|
16
16
|
import { createIapCommerce } from "@himalaya/iap-checkout";
|
|
17
|
-
import {
|
|
17
|
+
import { programImage } from "../_shared/himi-media.js";
|
|
18
18
|
import { cleanTitle, initials } from "../_shared/himi-text.js";
|
|
19
19
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
20
20
|
import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
|
|
@@ -86,14 +86,6 @@ function programDetails(p: WixProgram): string {
|
|
|
86
86
|
return raw.length > 140 ? `${raw.slice(0, 139).trimEnd()}…` : raw;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function programImage(p: WixProgram): string {
|
|
90
|
-
const desc = (p as { description?: unknown }).description;
|
|
91
|
-
const nested = desc && typeof desc === "object" ? (desc as { image?: { url?: string } }).image?.url : undefined;
|
|
92
|
-
const cover = (p as { coverImage?: unknown }).coverImage;
|
|
93
|
-
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url;
|
|
94
|
-
return wixMediaUrl(coverUrl || p.image || nested);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
89
|
function programMeta(p: WixProgram): string {
|
|
98
90
|
const summary = (p as { contentSummary?: { stepCount?: number; sectionCount?: number } }).contentSummary;
|
|
99
91
|
const bits: string[] = [];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Cover selection is shared by list, detail and home so the same Wix object never
|
|
2
|
+
// loses its image on navigation. Relative SDK imports are rewritten by the template generator.
|
|
3
|
+
import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
|
|
4
|
+
import type { WixGroup } from "@himalaya/wix-groups";
|
|
5
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
6
|
+
|
|
7
|
+
function firstImage(references: unknown[], size: WixImageSize): string {
|
|
8
|
+
for (const reference of references) {
|
|
9
|
+
const image = wixMediaUrl(reference, size);
|
|
10
|
+
if (image) return image;
|
|
11
|
+
}
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
16
|
+
const cover = group.coverImage as {
|
|
17
|
+
url?: unknown;
|
|
18
|
+
image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
|
|
19
|
+
} | undefined;
|
|
20
|
+
return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
24
|
+
const cover = (program as { coverImage?: unknown }).coverImage;
|
|
25
|
+
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
|
|
26
|
+
const description = (program as { description?: unknown }).description;
|
|
27
|
+
const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
|
|
28
|
+
return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
|
|
29
|
+
}
|
|
@@ -4,6 +4,8 @@ import { getGroup, joinGroup, leaveGroup, queryMembers as queryGroupMembers, typ
|
|
|
4
4
|
import { getMember } from "@himalaya/wix-members";
|
|
5
5
|
import { signedInEmail } from "../_shared/himi-account";
|
|
6
6
|
import { initials } from "../_shared/himi-text.js";
|
|
7
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
8
|
+
import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
|
|
7
9
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
8
10
|
import { get as dbGet, upsert as dbUpsert, type DbEntity } from "@himalaya/local-db";
|
|
9
11
|
import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error";
|
|
@@ -87,11 +89,6 @@ function inputId(): string {
|
|
|
87
89
|
return input?.screenId?.startsWith("himi_group_") ? input.screenId.slice("himi_group_".length) : "";
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
function coverImage(group: WixGroup): string {
|
|
91
|
-
const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
|
|
92
|
-
return cover?.image?.url ?? cover?.url ?? "";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
92
|
function membershipState(state: State, joined: boolean, signedIn: boolean): State {
|
|
96
93
|
const isPublic = state.privacy === "Public" || state.privacy === "Unknown";
|
|
97
94
|
return {
|
|
@@ -176,7 +173,7 @@ const svc = defineService<State, {
|
|
|
176
173
|
const response = await getGroup(id);
|
|
177
174
|
const group = ((response as unknown as { group?: WixGroup }).group ?? response) as WixGroup;
|
|
178
175
|
const name = (group.name || copyParam("group.groupFallbackName", "Group")).trim();
|
|
179
|
-
const image =
|
|
176
|
+
const image = groupImage(group, WIX_DETAIL_IMAGE);
|
|
180
177
|
const count = group.membersCount ?? group.memberCount;
|
|
181
178
|
const privacy = (group.privacyStatus || "UNKNOWN").toLowerCase();
|
|
182
179
|
const privacyLabel = privacy[0].toUpperCase() + privacy.slice(1);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineService } from "../_shared/collection-service.js";
|
|
2
2
|
import { queryGroups, type WixGroup } from "@himalaya/wix-groups";
|
|
3
|
-
import {
|
|
3
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
4
4
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
5
5
|
import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
6
6
|
|
|
@@ -45,23 +45,10 @@ interface State {
|
|
|
45
45
|
noEmpty: boolean;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Social Groups stores a cover as `coverImage.image.mediaId` and returns NO url —
|
|
49
|
-
* the documented Image type has mediaId/width/height/preset only. Probing just
|
|
50
|
-
* `.url` therefore left every real cover unrendered (accent band + initials).
|
|
51
|
-
* Keep the url probes for other shapes, then derive from the media id. */
|
|
52
|
-
function coverImage(group: WixGroup): string {
|
|
53
|
-
const cover = group.coverImage as
|
|
54
|
-
| { url?: string; image?: { url?: string; mediaId?: string; id?: string } }
|
|
55
|
-
| undefined;
|
|
56
|
-
return wixMediaUrl(
|
|
57
|
-
cover?.image?.url ?? cover?.url ?? cover?.image?.mediaId ?? cover?.image?.id,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
48
|
function groupRow(group: WixGroup): GroupRow {
|
|
62
49
|
const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
|
|
63
50
|
const count = group.membersCount ?? group.memberCount;
|
|
64
|
-
const image =
|
|
51
|
+
const image = groupImage(group);
|
|
65
52
|
return {
|
|
66
53
|
id: group.id || title,
|
|
67
54
|
title,
|
|
@@ -24,6 +24,7 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
|
|
|
24
24
|
import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
|
|
25
25
|
import { readSavedIds } from "../_shared/himi-blog-saved.js";
|
|
26
26
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
27
|
+
import { groupImage, programImage } from "../_shared/himi-media.js";
|
|
27
28
|
import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
|
|
28
29
|
import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
|
|
29
30
|
|
|
@@ -676,8 +677,7 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
676
677
|
const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
|
|
677
678
|
const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
|
|
678
679
|
const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
|
|
679
|
-
const
|
|
680
|
-
const image = (typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url) || p.image || "";
|
|
680
|
+
const image = programImage(p);
|
|
681
681
|
const details = (p.settings?.description ?? nestedDetails ?? (typeof desc === "string" ? desc : "") ?? "")
|
|
682
682
|
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
683
683
|
return {
|
|
@@ -732,8 +732,7 @@ function homeTimeLabel(value?: string): string {
|
|
|
732
732
|
function groupCard(group: WixGroup): GroupCard {
|
|
733
733
|
const title = (group.name || copyParam("home.groupFallbackTitle", "Group")).trim();
|
|
734
734
|
const count = group.membersCount ?? group.memberCount;
|
|
735
|
-
const
|
|
736
|
-
const image = cover?.image?.url ?? cover?.url ?? "";
|
|
735
|
+
const image = groupImage(group);
|
|
737
736
|
return {
|
|
738
737
|
id: group.id || title,
|
|
739
738
|
title,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Cover selection is shared by list, detail and home so the same Wix object never
|
|
2
|
+
// loses its image on navigation. Relative SDK imports are rewritten by the template generator.
|
|
3
|
+
import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
|
|
4
|
+
import type { WixGroup } from "@himalaya/wix-groups";
|
|
5
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
6
|
+
|
|
7
|
+
function firstImage(references: unknown[], size: WixImageSize): string {
|
|
8
|
+
for (const reference of references) {
|
|
9
|
+
const image = wixMediaUrl(reference, size);
|
|
10
|
+
if (image) return image;
|
|
11
|
+
}
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
16
|
+
const cover = group.coverImage as {
|
|
17
|
+
url?: unknown;
|
|
18
|
+
image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
|
|
19
|
+
} | undefined;
|
|
20
|
+
return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
24
|
+
const cover = (program as { coverImage?: unknown }).coverImage;
|
|
25
|
+
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
|
|
26
|
+
const description = (program as { description?: unknown }).description;
|
|
27
|
+
const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
|
|
28
|
+
return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
|
|
29
|
+
}
|
|
@@ -4,6 +4,8 @@ import { getGroup, joinGroup, leaveGroup, queryMembers as queryGroupMembers, typ
|
|
|
4
4
|
import { getMember } from "@himalaya/wix-members";
|
|
5
5
|
import { signedInEmail } from "../_shared/himi-account";
|
|
6
6
|
import { initials } from "../_shared/himi-text.js";
|
|
7
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
8
|
+
import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
|
|
7
9
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
8
10
|
import { get as dbGet, upsert as dbUpsert, type DbEntity } from "@himalaya/local-db";
|
|
9
11
|
import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error";
|
|
@@ -87,11 +89,6 @@ function inputId(): string {
|
|
|
87
89
|
return input?.screenId?.startsWith("himi_group_") ? input.screenId.slice("himi_group_".length) : "";
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
function coverImage(group: WixGroup): string {
|
|
91
|
-
const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
|
|
92
|
-
return cover?.image?.url ?? cover?.url ?? "";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
92
|
function membershipState(state: State, joined: boolean, signedIn: boolean): State {
|
|
96
93
|
const isPublic = state.privacy === "Public" || state.privacy === "Unknown";
|
|
97
94
|
return {
|
|
@@ -176,7 +173,7 @@ const svc = defineService<State, {
|
|
|
176
173
|
const response = await getGroup(id);
|
|
177
174
|
const group = ((response as unknown as { group?: WixGroup }).group ?? response) as WixGroup;
|
|
178
175
|
const name = (group.name || copyParam("group.groupFallbackName", "Group")).trim();
|
|
179
|
-
const image =
|
|
176
|
+
const image = groupImage(group, WIX_DETAIL_IMAGE);
|
|
180
177
|
const count = group.membersCount ?? group.memberCount;
|
|
181
178
|
const privacy = (group.privacyStatus || "UNKNOWN").toLowerCase();
|
|
182
179
|
const privacyLabel = privacy[0].toUpperCase() + privacy.slice(1);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineService } from "../_shared/collection-service.js";
|
|
2
2
|
import { queryGroups, type WixGroup } from "@himalaya/wix-groups";
|
|
3
|
-
import {
|
|
3
|
+
import { groupImage } from "../_shared/himi-media.js";
|
|
4
4
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
5
5
|
import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
6
6
|
|
|
@@ -45,23 +45,10 @@ interface State {
|
|
|
45
45
|
noEmpty: boolean;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Social Groups stores a cover as `coverImage.image.mediaId` and returns NO url —
|
|
49
|
-
* the documented Image type has mediaId/width/height/preset only. Probing just
|
|
50
|
-
* `.url` therefore left every real cover unrendered (accent band + initials).
|
|
51
|
-
* Keep the url probes for other shapes, then derive from the media id. */
|
|
52
|
-
function coverImage(group: WixGroup): string {
|
|
53
|
-
const cover = group.coverImage as
|
|
54
|
-
| { url?: string; image?: { url?: string; mediaId?: string; id?: string } }
|
|
55
|
-
| undefined;
|
|
56
|
-
return wixMediaUrl(
|
|
57
|
-
cover?.image?.url ?? cover?.url ?? cover?.image?.mediaId ?? cover?.image?.id,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
48
|
function groupRow(group: WixGroup): GroupRow {
|
|
62
49
|
const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
|
|
63
50
|
const count = group.membersCount ?? group.memberCount;
|
|
64
|
-
const image =
|
|
51
|
+
const image = groupImage(group);
|
|
65
52
|
return {
|
|
66
53
|
id: group.id || title,
|
|
67
54
|
title,
|
|
@@ -24,6 +24,7 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
|
|
|
24
24
|
import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
|
|
25
25
|
import { readSavedIds } from "../_shared/himi-blog-saved.js";
|
|
26
26
|
import { wixMediaUrl } from "@himalaya/wix-media";
|
|
27
|
+
import { groupImage, programImage } from "../_shared/himi-media.js";
|
|
27
28
|
import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
|
|
28
29
|
import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
|
|
29
30
|
|
|
@@ -676,8 +677,7 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
676
677
|
const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
|
|
677
678
|
const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
|
|
678
679
|
const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
|
|
679
|
-
const
|
|
680
|
-
const image = (typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url) || p.image || "";
|
|
680
|
+
const image = programImage(p);
|
|
681
681
|
const details = (p.settings?.description ?? nestedDetails ?? (typeof desc === "string" ? desc : "") ?? "")
|
|
682
682
|
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
683
683
|
return {
|
|
@@ -732,8 +732,7 @@ function homeTimeLabel(value?: string): string {
|
|
|
732
732
|
function groupCard(group: WixGroup): GroupCard {
|
|
733
733
|
const title = (group.name || copyParam("home.groupFallbackTitle", "Group")).trim();
|
|
734
734
|
const count = group.membersCount ?? group.memberCount;
|
|
735
|
-
const
|
|
736
|
-
const image = cover?.image?.url ?? cover?.url ?? "";
|
|
735
|
+
const image = groupImage(group);
|
|
737
736
|
return {
|
|
738
737
|
id: group.id || title,
|
|
739
738
|
title,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Cover selection is shared by list, detail and home so the same Wix object never
|
|
2
|
+
// loses its image on navigation. Relative SDK imports are rewritten by the template generator.
|
|
3
|
+
import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
|
|
4
|
+
import type { WixGroup } from "@himalaya/wix-groups";
|
|
5
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
6
|
+
|
|
7
|
+
function firstImage(references: unknown[], size: WixImageSize): string {
|
|
8
|
+
for (const reference of references) {
|
|
9
|
+
const image = wixMediaUrl(reference, size);
|
|
10
|
+
if (image) return image;
|
|
11
|
+
}
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
16
|
+
const cover = group.coverImage as {
|
|
17
|
+
url?: unknown;
|
|
18
|
+
image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
|
|
19
|
+
} | undefined;
|
|
20
|
+
return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
|
|
24
|
+
const cover = (program as { coverImage?: unknown }).coverImage;
|
|
25
|
+
const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
|
|
26
|
+
const description = (program as { description?: unknown }).description;
|
|
27
|
+
const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
|
|
28
|
+
return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
|
|
29
|
+
}
|