@wix/himalaya-cli 0.1150.0 → 0.1152.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.
@@ -11,7 +11,9 @@ import { normalizeEvent, eventDescription } from "../_shared/himi-events.js";
11
11
  import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
12
12
  import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
13
13
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
14
- import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
14
+ import { openRichLink } from "../_shared/rich-content-link.js";
15
+ import { richBody } from "../_shared/rich-body.js";
16
+ import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
15
17
 
16
18
  // Offline-first (data-offline): the event DETAIL falls back to the last-synced
17
19
  // local `owner_events` row (put by the event form's write-behind) on a failed
@@ -20,6 +22,7 @@ import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity }
20
22
  // ENQUEUED for replay on reconnect. Every local-db call is wrapped so a build
21
23
  // without the `local_database` grant behaves exactly as before (online-only).
22
24
  const ENTITY = "owner_events";
25
+ async function cachePut(row: DbEntity): Promise<void> { try { await dbUpsert(ENTITY, row); } catch { /* capability not granted */ } }
23
26
  async function cacheDrop(id: string): Promise<void> { try { await dbRemove(ENTITY, id); } catch { /* capability not granted */ } }
24
27
  async function cacheGet(id: string): Promise<DbEntity | null> { try { return await dbGet(ENTITY, id); } catch { return null; } }
25
28
  async function queueWrite(actionId: string, params: Record<string, string>): Promise<void> { try { await dbEnqueue(actionId, params); } catch { /* capability not granted */ } }
@@ -33,6 +36,13 @@ interface EventState {
33
36
  title: string;
34
37
  subtitle: string;
35
38
  summary: string;
39
+ // Body ([rico] #3499) — RichContent binds bodyJson; the plain text shows when there is no document.
40
+ /** The v3 Ricos `description` (read with `fields=TEXTS`) as ONE document string; "" when there is none. */
41
+ bodyJson: string;
42
+ /** bodyJson !== "" — gates the plain text, because RichContent draws nothing for "". */
43
+ hasBodyJson: boolean;
44
+ /** The description's plain text (the document's own text, else the legacy description). */
45
+ bodyText: string;
36
46
  initial: string;
37
47
  image: string;
38
48
  dateLine: string;
@@ -55,6 +65,17 @@ interface EventState {
55
65
  }
56
66
 
57
67
  /* eslint-disable @typescript-eslint/no-explicit-any */
68
+ /** [rico] #3499 — keep the description's plain text with the event's cached row, so the offline detail shows it.
69
+ * Merged into the row the events list (or the form) wrote rather than replacing it; with no row yet, the
70
+ * loaded detail's own fields become one. */
71
+ async function cacheBodyText(s: EventState): Promise<void> {
72
+ if (s.status !== "ready" || !s.id) return;
73
+ const prev = await cacheGet(s.id);
74
+ await cachePut(prev ? { ...prev, bodyText: s.bodyText } : {
75
+ id: s.id, title: s.title, subtitle: s.subtitle, image: s.image, dateLine: s.dateLine, location: s.location,
76
+ statusLabel: s.statusLabel, registration: s.registration, bodyText: s.bodyText,
77
+ });
78
+ }
58
79
  function idFrom(arg: unknown, fallback = ""): string {
59
80
  return typeof arg === "string" ? arg : String((arg as any)?.id ?? fallback);
60
81
  }
@@ -97,6 +118,9 @@ function shape(s: Partial<EventState>): EventState {
97
118
  title: s.title ?? "",
98
119
  subtitle: s.subtitle ?? "",
99
120
  summary: s.summary ?? "",
121
+ bodyJson: s.bodyJson ?? "",
122
+ hasBodyJson: !!s.bodyJson,
123
+ bodyText: s.bodyText ?? "",
100
124
  initial: s.initial ?? "?",
101
125
  image,
102
126
  dateLine: s.dateLine ?? "—",
@@ -128,7 +152,9 @@ function demoDetail(s: EventState, id: string): EventState | null {
128
152
  status: "ready",
129
153
  title: d.title,
130
154
  subtitle: [d.dateLine, d.location].filter(Boolean).join(" · "),
131
- summary: d.description,
155
+ summary: "",
156
+ bodyJson: "",
157
+ bodyText: d.description,
132
158
  initial: (d.title[0] || "?").toUpperCase(),
133
159
  image: "",
134
160
  dateLine: d.dateLine,
@@ -151,7 +177,9 @@ function cachedDetail(s: EventState, row: DbEntity): EventState {
151
177
  status: "ready",
152
178
  title,
153
179
  subtitle: String(row.subtitle ?? [dateLine, location].filter((x) => x && x !== "—").join(" · ")),
154
- summary: String(row.summary ?? row.description ?? ""),
180
+ summary: "",
181
+ bodyJson: "",
182
+ bodyText: String(row.bodyText ?? row.summary ?? row.description ?? ""),
155
183
  initial: String(row.initial ?? (title[0] || "?")).slice(0, 1).toUpperCase(),
156
184
  image: String(row.image ?? ""),
157
185
  dateLine: dateLine || "—",
@@ -164,21 +192,27 @@ function cachedDetail(s: EventState, row: DbEntity): EventState {
164
192
  }
165
193
 
166
194
  async function refresh(prev: EventState, id: string): Promise<EventState> {
167
- const res = await getEvent(id);
195
+ // A bare read returns the base fieldset: DETAILS adds `shortDescription` (the teaser and the legacy body),
196
+ // TEXTS the Ricos `description`.
197
+ const res = await getEvent(id, { fields: ["DETAILS", "TEXTS"] });
168
198
  const ev = (res.event ?? res) as WixEvent;
169
199
  if (!ev || !(ev as any).id) {
170
200
  return shape({ ...prev, id, status: "error", title: "", error: copyParam("ownerEvent.errorNotFound", "event not found") });
171
201
  }
172
202
  const row = normalizeEvent(ev); // reuses date/location/status normalization
173
203
  const image = /^https?:\/\//.test(row.image) ? row.image : "";
174
- const desc = (eventDescription(ev) || "").trim();
204
+ const { bodyJson, bodyText } = richBody((ev as unknown as { description?: unknown }).description, (eventDescription(ev) || "").trim());
205
+ // The teaser stays above the body only when it says something the body does not.
206
+ const teaser = String((ev as any).shortDescription ?? "").trim();
175
207
  return shape({
176
208
  ...prev,
177
209
  id,
178
210
  status: "ready",
179
211
  title: row.title,
180
212
  subtitle: [row.dateLine, row.location].filter((x) => x && x !== "—").join(" · "),
181
- summary: desc,
213
+ summary: teaser && teaser !== bodyText.trim() ? teaser : "",
214
+ bodyJson,
215
+ bodyText,
182
216
  initial: (row.title[0] || "?").toUpperCase(),
183
217
  image,
184
218
  dateLine: row.dateLine || "—",
@@ -194,6 +228,7 @@ const svc = defineService<EventState, {
194
228
  load: (s: EventState, arg?: unknown) => Promise<EventState>;
195
229
  retry: (s: EventState) => Promise<EventState>;
196
230
  delete: (s: EventState) => Promise<EventState>;
231
+ openLink: (s: EventState, arg?: unknown) => Promise<EventState>;
197
232
  }>("himiOwnerLiteEvent", {
198
233
  state: shape({ status: "loading" }),
199
234
  actions: {
@@ -208,7 +243,9 @@ const svc = defineService<EventState, {
208
243
  return shape({ ...s, status: "error", error: "Sample item not found." });
209
244
  }
210
245
  try {
211
- return await refresh(shape({ ...s, id, status: "loading" }), id);
246
+ const loaded = await refresh(shape({ ...s, id, status: "loading" }), id);
247
+ await cacheBodyText(loaded);
248
+ return loaded;
212
249
  } catch (e) {
213
250
  // Offline: fall back to the last-synced local row before the demo/error paths.
214
251
  const cached = await cacheGet(id);
@@ -229,7 +266,9 @@ const svc = defineService<EventState, {
229
266
  return shape({ ...s, status: "error", error: "Sample item not found." });
230
267
  }
231
268
  try {
232
- return await refresh(shape({ ...s, status: "loading" }), s.id);
269
+ const loaded = await refresh(shape({ ...s, status: "loading" }), s.id);
270
+ await cacheBodyText(loaded);
271
+ return loaded;
233
272
  } catch (e) {
234
273
  const cached = await cacheGet(s.id);
235
274
  if (cached) return cachedDetail(s, cached);
@@ -256,6 +295,11 @@ const svc = defineService<EventState, {
256
295
  return shape({ ...s, actionMessage: ownerLoadError(e) });
257
296
  }
258
297
  },
298
+ // [rico] #3499 — a link tapped in the RichContent description.
299
+ openLink: async (s, arg) => shape({
300
+ ...s,
301
+ actionMessage: (await openRichLink(arg)) ? "" : copyParam("ownerEvent.linkOpenFailed", "This link could not be opened."),
302
+ }),
259
303
  },
260
304
  });
261
305
 
@@ -73230,6 +73230,48 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
73230
73230
  }
73231
73231
  }
73232
73232
  },
73233
+ {
73234
+ "id": "event_body",
73235
+ "body": {
73236
+ "package": "himalaya",
73237
+ "type": "RichContent",
73238
+ "props": {
73239
+ "documentJsonPath": "$service:himiOwnerLiteEvent.bodyJson",
73240
+ "onLink": {
73241
+ "actionId": "tier5.himiOwnerLiteEvent.openLink"
73242
+ },
73243
+ "fallback": {
73244
+ "id": "",
73245
+ "body": {
73246
+ "package": "himalaya",
73247
+ "type": "Text",
73248
+ "props": {
73249
+ "text": "$service:himiOwnerLiteEvent.bodyText",
73250
+ "style": "body",
73251
+ "color": "textPrimary",
73252
+ "collapseWhenEmpty": true
73253
+ }
73254
+ }
73255
+ }
73256
+ }
73257
+ }
73258
+ },
73259
+ {
73260
+ "id": "event_body_text",
73261
+ "body": {
73262
+ "package": "himalaya",
73263
+ "type": "Text",
73264
+ "props": {
73265
+ "text": "$service:himiOwnerLiteEvent.bodyText",
73266
+ "style": "body",
73267
+ "color": "textPrimary",
73268
+ "collapseWhenEmpty": true
73269
+ }
73270
+ },
73271
+ "modifiers": {
73272
+ "hiddenPath": "$service:himiOwnerLiteEvent.hasBodyJson"
73273
+ }
73274
+ },
73233
73275
  {
73234
73276
  "id": "event_info_card",
73235
73277
  "body": {
@@ -73568,7 +73610,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
73568
73610
  "contentType": "application/javascript+ts-bundle",
73569
73611
  "declaredCapabilities": [
73570
73612
  "wix_api",
73571
- "local_database"
73613
+ "local_database",
73614
+ "open_url"
73572
73615
  ],
73573
73616
  "minBinaryVersion": "0.1.0",
73574
73617
  "bundleVersion": "0.0.1"
@@ -11,7 +11,9 @@ import { normalizeEvent, eventDescription } from "../_shared/himi-events.js";
11
11
  import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
12
12
  import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
13
13
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
14
- import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
14
+ import { openRichLink } from "../_shared/rich-content-link.js";
15
+ import { richBody } from "../_shared/rich-body.js";
16
+ import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
15
17
 
16
18
  // Offline-first (data-offline): the event DETAIL falls back to the last-synced
17
19
  // local `owner_events` row (put by the event form's write-behind) on a failed
@@ -20,6 +22,7 @@ import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity }
20
22
  // ENQUEUED for replay on reconnect. Every local-db call is wrapped so a build
21
23
  // without the `local_database` grant behaves exactly as before (online-only).
22
24
  const ENTITY = "owner_events";
25
+ async function cachePut(row: DbEntity): Promise<void> { try { await dbUpsert(ENTITY, row); } catch { /* capability not granted */ } }
23
26
  async function cacheDrop(id: string): Promise<void> { try { await dbRemove(ENTITY, id); } catch { /* capability not granted */ } }
24
27
  async function cacheGet(id: string): Promise<DbEntity | null> { try { return await dbGet(ENTITY, id); } catch { return null; } }
25
28
  async function queueWrite(actionId: string, params: Record<string, string>): Promise<void> { try { await dbEnqueue(actionId, params); } catch { /* capability not granted */ } }
@@ -33,6 +36,13 @@ interface EventState {
33
36
  title: string;
34
37
  subtitle: string;
35
38
  summary: string;
39
+ // Body ([rico] #3499) — RichContent binds bodyJson; the plain text shows when there is no document.
40
+ /** The v3 Ricos `description` (read with `fields=TEXTS`) as ONE document string; "" when there is none. */
41
+ bodyJson: string;
42
+ /** bodyJson !== "" — gates the plain text, because RichContent draws nothing for "". */
43
+ hasBodyJson: boolean;
44
+ /** The description's plain text (the document's own text, else the legacy description). */
45
+ bodyText: string;
36
46
  initial: string;
37
47
  image: string;
38
48
  dateLine: string;
@@ -55,6 +65,17 @@ interface EventState {
55
65
  }
56
66
 
57
67
  /* eslint-disable @typescript-eslint/no-explicit-any */
68
+ /** [rico] #3499 — keep the description's plain text with the event's cached row, so the offline detail shows it.
69
+ * Merged into the row the events list (or the form) wrote rather than replacing it; with no row yet, the
70
+ * loaded detail's own fields become one. */
71
+ async function cacheBodyText(s: EventState): Promise<void> {
72
+ if (s.status !== "ready" || !s.id) return;
73
+ const prev = await cacheGet(s.id);
74
+ await cachePut(prev ? { ...prev, bodyText: s.bodyText } : {
75
+ id: s.id, title: s.title, subtitle: s.subtitle, image: s.image, dateLine: s.dateLine, location: s.location,
76
+ statusLabel: s.statusLabel, registration: s.registration, bodyText: s.bodyText,
77
+ });
78
+ }
58
79
  function idFrom(arg: unknown, fallback = ""): string {
59
80
  return typeof arg === "string" ? arg : String((arg as any)?.id ?? fallback);
60
81
  }
@@ -97,6 +118,9 @@ function shape(s: Partial<EventState>): EventState {
97
118
  title: s.title ?? "",
98
119
  subtitle: s.subtitle ?? "",
99
120
  summary: s.summary ?? "",
121
+ bodyJson: s.bodyJson ?? "",
122
+ hasBodyJson: !!s.bodyJson,
123
+ bodyText: s.bodyText ?? "",
100
124
  initial: s.initial ?? "?",
101
125
  image,
102
126
  dateLine: s.dateLine ?? "—",
@@ -128,7 +152,9 @@ function demoDetail(s: EventState, id: string): EventState | null {
128
152
  status: "ready",
129
153
  title: d.title,
130
154
  subtitle: [d.dateLine, d.location].filter(Boolean).join(" · "),
131
- summary: d.description,
155
+ summary: "",
156
+ bodyJson: "",
157
+ bodyText: d.description,
132
158
  initial: (d.title[0] || "?").toUpperCase(),
133
159
  image: "",
134
160
  dateLine: d.dateLine,
@@ -151,7 +177,9 @@ function cachedDetail(s: EventState, row: DbEntity): EventState {
151
177
  status: "ready",
152
178
  title,
153
179
  subtitle: String(row.subtitle ?? [dateLine, location].filter((x) => x && x !== "—").join(" · ")),
154
- summary: String(row.summary ?? row.description ?? ""),
180
+ summary: "",
181
+ bodyJson: "",
182
+ bodyText: String(row.bodyText ?? row.summary ?? row.description ?? ""),
155
183
  initial: String(row.initial ?? (title[0] || "?")).slice(0, 1).toUpperCase(),
156
184
  image: String(row.image ?? ""),
157
185
  dateLine: dateLine || "—",
@@ -164,21 +192,27 @@ function cachedDetail(s: EventState, row: DbEntity): EventState {
164
192
  }
165
193
 
166
194
  async function refresh(prev: EventState, id: string): Promise<EventState> {
167
- const res = await getEvent(id);
195
+ // A bare read returns the base fieldset: DETAILS adds `shortDescription` (the teaser and the legacy body),
196
+ // TEXTS the Ricos `description`.
197
+ const res = await getEvent(id, { fields: ["DETAILS", "TEXTS"] });
168
198
  const ev = (res.event ?? res) as WixEvent;
169
199
  if (!ev || !(ev as any).id) {
170
200
  return shape({ ...prev, id, status: "error", title: "", error: copyParam("ownerEvent.errorNotFound", "event not found") });
171
201
  }
172
202
  const row = normalizeEvent(ev); // reuses date/location/status normalization
173
203
  const image = /^https?:\/\//.test(row.image) ? row.image : "";
174
- const desc = (eventDescription(ev) || "").trim();
204
+ const { bodyJson, bodyText } = richBody((ev as unknown as { description?: unknown }).description, (eventDescription(ev) || "").trim());
205
+ // The teaser stays above the body only when it says something the body does not.
206
+ const teaser = String((ev as any).shortDescription ?? "").trim();
175
207
  return shape({
176
208
  ...prev,
177
209
  id,
178
210
  status: "ready",
179
211
  title: row.title,
180
212
  subtitle: [row.dateLine, row.location].filter((x) => x && x !== "—").join(" · "),
181
- summary: desc,
213
+ summary: teaser && teaser !== bodyText.trim() ? teaser : "",
214
+ bodyJson,
215
+ bodyText,
182
216
  initial: (row.title[0] || "?").toUpperCase(),
183
217
  image,
184
218
  dateLine: row.dateLine || "—",
@@ -194,6 +228,7 @@ const svc = defineService<EventState, {
194
228
  load: (s: EventState, arg?: unknown) => Promise<EventState>;
195
229
  retry: (s: EventState) => Promise<EventState>;
196
230
  delete: (s: EventState) => Promise<EventState>;
231
+ openLink: (s: EventState, arg?: unknown) => Promise<EventState>;
197
232
  }>("himiOwnerLiteEvent", {
198
233
  state: shape({ status: "loading" }),
199
234
  actions: {
@@ -208,7 +243,9 @@ const svc = defineService<EventState, {
208
243
  return shape({ ...s, status: "error", error: "Sample item not found." });
209
244
  }
210
245
  try {
211
- return await refresh(shape({ ...s, id, status: "loading" }), id);
246
+ const loaded = await refresh(shape({ ...s, id, status: "loading" }), id);
247
+ await cacheBodyText(loaded);
248
+ return loaded;
212
249
  } catch (e) {
213
250
  // Offline: fall back to the last-synced local row before the demo/error paths.
214
251
  const cached = await cacheGet(id);
@@ -229,7 +266,9 @@ const svc = defineService<EventState, {
229
266
  return shape({ ...s, status: "error", error: "Sample item not found." });
230
267
  }
231
268
  try {
232
- return await refresh(shape({ ...s, status: "loading" }), s.id);
269
+ const loaded = await refresh(shape({ ...s, status: "loading" }), s.id);
270
+ await cacheBodyText(loaded);
271
+ return loaded;
233
272
  } catch (e) {
234
273
  const cached = await cacheGet(s.id);
235
274
  if (cached) return cachedDetail(s, cached);
@@ -256,6 +295,11 @@ const svc = defineService<EventState, {
256
295
  return shape({ ...s, actionMessage: ownerLoadError(e) });
257
296
  }
258
297
  },
298
+ // [rico] #3499 — a link tapped in the RichContent description.
299
+ openLink: async (s, arg) => shape({
300
+ ...s,
301
+ actionMessage: (await openRichLink(arg)) ? "" : copyParam("ownerEvent.linkOpenFailed", "This link could not be opened."),
302
+ }),
259
303
  },
260
304
  });
261
305
 
@@ -82836,6 +82836,48 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
82836
82836
  }
82837
82837
  }
82838
82838
  },
82839
+ {
82840
+ "id": "event_body",
82841
+ "body": {
82842
+ "package": "himalaya",
82843
+ "type": "RichContent",
82844
+ "props": {
82845
+ "documentJsonPath": "$service:himiOwnerLiteEvent.bodyJson",
82846
+ "onLink": {
82847
+ "actionId": "tier5.himiOwnerLiteEvent.openLink"
82848
+ },
82849
+ "fallback": {
82850
+ "id": "",
82851
+ "body": {
82852
+ "package": "himalaya",
82853
+ "type": "Text",
82854
+ "props": {
82855
+ "text": "$service:himiOwnerLiteEvent.bodyText",
82856
+ "style": "body",
82857
+ "color": "textPrimary",
82858
+ "collapseWhenEmpty": true
82859
+ }
82860
+ }
82861
+ }
82862
+ }
82863
+ }
82864
+ },
82865
+ {
82866
+ "id": "event_body_text",
82867
+ "body": {
82868
+ "package": "himalaya",
82869
+ "type": "Text",
82870
+ "props": {
82871
+ "text": "$service:himiOwnerLiteEvent.bodyText",
82872
+ "style": "body",
82873
+ "color": "textPrimary",
82874
+ "collapseWhenEmpty": true
82875
+ }
82876
+ },
82877
+ "modifiers": {
82878
+ "hiddenPath": "$service:himiOwnerLiteEvent.hasBodyJson"
82879
+ }
82880
+ },
82839
82881
  {
82840
82882
  "id": "event_info_card",
82841
82883
  "body": {
@@ -83173,7 +83215,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
83173
83215
  "contentType": "application/javascript+ts-bundle",
83174
83216
  "declaredCapabilities": [
83175
83217
  "wix_api",
83176
- "local_database"
83218
+ "local_database",
83219
+ "open_url"
83177
83220
  ],
83178
83221
  "minBinaryVersion": "0.1.0",
83179
83222
  "bundleVersion": "0.0.1"
@@ -11,7 +11,9 @@ import { normalizeEvent, eventDescription } from "../_shared/himi-events.js";
11
11
  import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
12
12
  import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
13
13
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
14
- import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
14
+ import { openRichLink } from "../_shared/rich-content-link.js";
15
+ import { richBody } from "../_shared/rich-body.js";
16
+ import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
15
17
 
16
18
  // Offline-first (data-offline): the event DETAIL falls back to the last-synced
17
19
  // local `owner_events` row (put by the event form's write-behind) on a failed
@@ -20,6 +22,7 @@ import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity }
20
22
  // ENQUEUED for replay on reconnect. Every local-db call is wrapped so a build
21
23
  // without the `local_database` grant behaves exactly as before (online-only).
22
24
  const ENTITY = "owner_events";
25
+ async function cachePut(row: DbEntity): Promise<void> { try { await dbUpsert(ENTITY, row); } catch { /* capability not granted */ } }
23
26
  async function cacheDrop(id: string): Promise<void> { try { await dbRemove(ENTITY, id); } catch { /* capability not granted */ } }
24
27
  async function cacheGet(id: string): Promise<DbEntity | null> { try { return await dbGet(ENTITY, id); } catch { return null; } }
25
28
  async function queueWrite(actionId: string, params: Record<string, string>): Promise<void> { try { await dbEnqueue(actionId, params); } catch { /* capability not granted */ } }
@@ -33,6 +36,13 @@ interface EventState {
33
36
  title: string;
34
37
  subtitle: string;
35
38
  summary: string;
39
+ // Body ([rico] #3499) — RichContent binds bodyJson; the plain text shows when there is no document.
40
+ /** The v3 Ricos `description` (read with `fields=TEXTS`) as ONE document string; "" when there is none. */
41
+ bodyJson: string;
42
+ /** bodyJson !== "" — gates the plain text, because RichContent draws nothing for "". */
43
+ hasBodyJson: boolean;
44
+ /** The description's plain text (the document's own text, else the legacy description). */
45
+ bodyText: string;
36
46
  initial: string;
37
47
  image: string;
38
48
  dateLine: string;
@@ -55,6 +65,17 @@ interface EventState {
55
65
  }
56
66
 
57
67
  /* eslint-disable @typescript-eslint/no-explicit-any */
68
+ /** [rico] #3499 — keep the description's plain text with the event's cached row, so the offline detail shows it.
69
+ * Merged into the row the events list (or the form) wrote rather than replacing it; with no row yet, the
70
+ * loaded detail's own fields become one. */
71
+ async function cacheBodyText(s: EventState): Promise<void> {
72
+ if (s.status !== "ready" || !s.id) return;
73
+ const prev = await cacheGet(s.id);
74
+ await cachePut(prev ? { ...prev, bodyText: s.bodyText } : {
75
+ id: s.id, title: s.title, subtitle: s.subtitle, image: s.image, dateLine: s.dateLine, location: s.location,
76
+ statusLabel: s.statusLabel, registration: s.registration, bodyText: s.bodyText,
77
+ });
78
+ }
58
79
  function idFrom(arg: unknown, fallback = ""): string {
59
80
  return typeof arg === "string" ? arg : String((arg as any)?.id ?? fallback);
60
81
  }
@@ -97,6 +118,9 @@ function shape(s: Partial<EventState>): EventState {
97
118
  title: s.title ?? "",
98
119
  subtitle: s.subtitle ?? "",
99
120
  summary: s.summary ?? "",
121
+ bodyJson: s.bodyJson ?? "",
122
+ hasBodyJson: !!s.bodyJson,
123
+ bodyText: s.bodyText ?? "",
100
124
  initial: s.initial ?? "?",
101
125
  image,
102
126
  dateLine: s.dateLine ?? "—",
@@ -128,7 +152,9 @@ function demoDetail(s: EventState, id: string): EventState | null {
128
152
  status: "ready",
129
153
  title: d.title,
130
154
  subtitle: [d.dateLine, d.location].filter(Boolean).join(" · "),
131
- summary: d.description,
155
+ summary: "",
156
+ bodyJson: "",
157
+ bodyText: d.description,
132
158
  initial: (d.title[0] || "?").toUpperCase(),
133
159
  image: "",
134
160
  dateLine: d.dateLine,
@@ -151,7 +177,9 @@ function cachedDetail(s: EventState, row: DbEntity): EventState {
151
177
  status: "ready",
152
178
  title,
153
179
  subtitle: String(row.subtitle ?? [dateLine, location].filter((x) => x && x !== "—").join(" · ")),
154
- summary: String(row.summary ?? row.description ?? ""),
180
+ summary: "",
181
+ bodyJson: "",
182
+ bodyText: String(row.bodyText ?? row.summary ?? row.description ?? ""),
155
183
  initial: String(row.initial ?? (title[0] || "?")).slice(0, 1).toUpperCase(),
156
184
  image: String(row.image ?? ""),
157
185
  dateLine: dateLine || "—",
@@ -164,21 +192,27 @@ function cachedDetail(s: EventState, row: DbEntity): EventState {
164
192
  }
165
193
 
166
194
  async function refresh(prev: EventState, id: string): Promise<EventState> {
167
- const res = await getEvent(id);
195
+ // A bare read returns the base fieldset: DETAILS adds `shortDescription` (the teaser and the legacy body),
196
+ // TEXTS the Ricos `description`.
197
+ const res = await getEvent(id, { fields: ["DETAILS", "TEXTS"] });
168
198
  const ev = (res.event ?? res) as WixEvent;
169
199
  if (!ev || !(ev as any).id) {
170
200
  return shape({ ...prev, id, status: "error", title: "", error: copyParam("ownerEvent.errorNotFound", "event not found") });
171
201
  }
172
202
  const row = normalizeEvent(ev); // reuses date/location/status normalization
173
203
  const image = /^https?:\/\//.test(row.image) ? row.image : "";
174
- const desc = (eventDescription(ev) || "").trim();
204
+ const { bodyJson, bodyText } = richBody((ev as unknown as { description?: unknown }).description, (eventDescription(ev) || "").trim());
205
+ // The teaser stays above the body only when it says something the body does not.
206
+ const teaser = String((ev as any).shortDescription ?? "").trim();
175
207
  return shape({
176
208
  ...prev,
177
209
  id,
178
210
  status: "ready",
179
211
  title: row.title,
180
212
  subtitle: [row.dateLine, row.location].filter((x) => x && x !== "—").join(" · "),
181
- summary: desc,
213
+ summary: teaser && teaser !== bodyText.trim() ? teaser : "",
214
+ bodyJson,
215
+ bodyText,
182
216
  initial: (row.title[0] || "?").toUpperCase(),
183
217
  image,
184
218
  dateLine: row.dateLine || "—",
@@ -194,6 +228,7 @@ const svc = defineService<EventState, {
194
228
  load: (s: EventState, arg?: unknown) => Promise<EventState>;
195
229
  retry: (s: EventState) => Promise<EventState>;
196
230
  delete: (s: EventState) => Promise<EventState>;
231
+ openLink: (s: EventState, arg?: unknown) => Promise<EventState>;
197
232
  }>("himiOwnerLiteEvent", {
198
233
  state: shape({ status: "loading" }),
199
234
  actions: {
@@ -208,7 +243,9 @@ const svc = defineService<EventState, {
208
243
  return shape({ ...s, status: "error", error: "Sample item not found." });
209
244
  }
210
245
  try {
211
- return await refresh(shape({ ...s, id, status: "loading" }), id);
246
+ const loaded = await refresh(shape({ ...s, id, status: "loading" }), id);
247
+ await cacheBodyText(loaded);
248
+ return loaded;
212
249
  } catch (e) {
213
250
  // Offline: fall back to the last-synced local row before the demo/error paths.
214
251
  const cached = await cacheGet(id);
@@ -229,7 +266,9 @@ const svc = defineService<EventState, {
229
266
  return shape({ ...s, status: "error", error: "Sample item not found." });
230
267
  }
231
268
  try {
232
- return await refresh(shape({ ...s, status: "loading" }), s.id);
269
+ const loaded = await refresh(shape({ ...s, status: "loading" }), s.id);
270
+ await cacheBodyText(loaded);
271
+ return loaded;
233
272
  } catch (e) {
234
273
  const cached = await cacheGet(s.id);
235
274
  if (cached) return cachedDetail(s, cached);
@@ -256,6 +295,11 @@ const svc = defineService<EventState, {
256
295
  return shape({ ...s, actionMessage: ownerLoadError(e) });
257
296
  }
258
297
  },
298
+ // [rico] #3499 — a link tapped in the RichContent description.
299
+ openLink: async (s, arg) => shape({
300
+ ...s,
301
+ actionMessage: (await openRichLink(arg)) ? "" : copyParam("ownerEvent.linkOpenFailed", "This link could not be opened."),
302
+ }),
259
303
  },
260
304
  });
261
305
 
@@ -76663,6 +76663,48 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
76663
76663
  }
76664
76664
  }
76665
76665
  },
76666
+ {
76667
+ "id": "event_body",
76668
+ "body": {
76669
+ "package": "himalaya",
76670
+ "type": "RichContent",
76671
+ "props": {
76672
+ "documentJsonPath": "$service:himiOwnerLiteEvent.bodyJson",
76673
+ "onLink": {
76674
+ "actionId": "tier5.himiOwnerLiteEvent.openLink"
76675
+ },
76676
+ "fallback": {
76677
+ "id": "",
76678
+ "body": {
76679
+ "package": "himalaya",
76680
+ "type": "Text",
76681
+ "props": {
76682
+ "text": "$service:himiOwnerLiteEvent.bodyText",
76683
+ "style": "body",
76684
+ "color": "textPrimary",
76685
+ "collapseWhenEmpty": true
76686
+ }
76687
+ }
76688
+ }
76689
+ }
76690
+ }
76691
+ },
76692
+ {
76693
+ "id": "event_body_text",
76694
+ "body": {
76695
+ "package": "himalaya",
76696
+ "type": "Text",
76697
+ "props": {
76698
+ "text": "$service:himiOwnerLiteEvent.bodyText",
76699
+ "style": "body",
76700
+ "color": "textPrimary",
76701
+ "collapseWhenEmpty": true
76702
+ }
76703
+ },
76704
+ "modifiers": {
76705
+ "hiddenPath": "$service:himiOwnerLiteEvent.hasBodyJson"
76706
+ }
76707
+ },
76666
76708
  {
76667
76709
  "id": "event_info_card",
76668
76710
  "body": {
@@ -77001,7 +77043,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
77001
77043
  "contentType": "application/javascript+ts-bundle",
77002
77044
  "declaredCapabilities": [
77003
77045
  "wix_api",
77004
- "local_database"
77046
+ "local_database",
77047
+ "open_url"
77005
77048
  ],
77006
77049
  "minBinaryVersion": "0.1.0",
77007
77050
  "bundleVersion": "0.0.1"