@wix/himalaya-cli 0.1145.0 → 0.1146.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/stdlib/wix-sdk/comments-v1/src/extensions.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/forum/src/extensions.d.ts +10 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-comments.mjs +5 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-forum.mjs +13 -0
- package/dist/cli.mjs +0 -2
- package/guides/sdk-reference.json +13 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +62 -3
- package/templates/beauty-salon/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +35 -4
- package/templates/beauty-salon-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/branded-lite/dev/index.ts +273 -12
- package/templates/branded-lite/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-cms-collection/index.ts +43 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-faq/index.ts +69 -13
- package/templates/branded-lite/tier5-src/himi-branded-lite-feed/index.ts +23 -3
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +35 -4
- package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +29 -5
- package/templates/community-club/dev/index.ts +135 -5
- package/templates/community-club/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/community-club/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/community-club/tier5-src/himi-branded-lite-cms-collection/index.ts +43 -3
- package/templates/community-club/tier5-src/himi-branded-lite-feed/index.ts +23 -3
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/community-club-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/events-meetups/dev/index.ts +91 -4
- package/templates/events-meetups/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/events-meetups/tier5-src/himi-branded-lite-feed/index.ts +23 -3
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/events-meetups-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/gym-fitness/dev/index.ts +92 -4
- package/templates/gym-fitness/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +35 -4
- package/templates/gym-fitness-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/nonprofit-cause/dev/index.ts +91 -4
- package/templates/nonprofit-cause/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-feed/index.ts +23 -3
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/nonprofit-cause-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/owner-lite/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/podcast-creator/dev/index.ts +91 -4
- package/templates/podcast-creator/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/podcast-creator/tier5-src/himi-branded-lite-feed/index.ts +23 -3
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/podcast-creator-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/portfolio-creative/dev/index.ts +92 -4
- package/templates/portfolio-creative/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +35 -4
- package/templates/portfolio-creative-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/restaurant/dev/index.ts +60 -2
- package/templates/restaurant/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/restaurant/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/restaurant-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/retail-store/dev/index.ts +62 -3
- package/templates/retail-store/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/retail-store/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +35 -4
- package/templates/retail-store-owner/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/yoga-studio/dev/index.ts +60 -2
- package/templates/yoga-studio/tier5-src/_shared/rich-body.ts +10 -4
- package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +12 -2
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +40 -3
- package/templates/yoga-studio-owner/tier5-src/_shared/rich-body.ts +10 -4
|
@@ -2,8 +2,12 @@ export declare const BLOG_COMMENTS_APP_ID = "14bcded7-0066-7c35-14d7-466cb3f0910
|
|
|
2
2
|
export type WixBlogCommentRow = {
|
|
3
3
|
id: string;
|
|
4
4
|
authorName: string;
|
|
5
|
+
/** The comment's plain text (every text run, space-joined). */
|
|
5
6
|
text: string;
|
|
6
7
|
createdDate: string;
|
|
8
|
+
/** [rico] #3372 — the comment's Ricos document (`content.richContent`) as the API returned it, for a
|
|
9
|
+
* RichContent row; undefined when the comment has none. */
|
|
10
|
+
richContent?: unknown;
|
|
7
11
|
};
|
|
8
12
|
type BlogCommentLike = {
|
|
9
13
|
id?: string;
|
|
@@ -3,6 +3,16 @@ import type { WixPost } from "./index.js";
|
|
|
3
3
|
export interface WixPostResponse {
|
|
4
4
|
post?: WixPost;
|
|
5
5
|
}
|
|
6
|
+
/** A forum post's Ricos body (`content`). Opaque here: render it with RichContent (`@himalaya/wix-rich-content`
|
|
7
|
+
* prepares it); `contentText` stays the plain-text copy. */
|
|
8
|
+
export interface WixForumRichContent {
|
|
9
|
+
nodes?: unknown[];
|
|
10
|
+
metadata?: unknown;
|
|
11
|
+
documentStyle?: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** [rico] #3372 — the post's Ricos `content` document (an object, or its JSON string), or null when it has none
|
|
14
|
+
* or it is not a document. */
|
|
15
|
+
export declare function forumPostContent(post: WixPost | null | undefined): WixForumRichContent | null;
|
|
6
16
|
/** Delete a post — DELETE /forum/v1/posts/{postId}. Irreversible. */
|
|
7
17
|
export declare function deletePost(id: string): Promise<WixPostResponse>;
|
|
8
18
|
/** Pin a post to the top of its category — PUT /forum/v1/posts/{postId}/pin → { post }. */
|
|
@@ -33,6 +33,7 @@ var commentText = (comment) => {
|
|
|
33
33
|
const text = readRichText(richContent);
|
|
34
34
|
return text || "Comment";
|
|
35
35
|
};
|
|
36
|
+
var commentRichContent = (comment) => comment.content?.richContent;
|
|
36
37
|
var commentAuthor = (comment) => {
|
|
37
38
|
const author = comment.author;
|
|
38
39
|
return author?.name ?? author?.nickname ?? "Member";
|
|
@@ -43,7 +44,8 @@ function blogCommentRow(comment) {
|
|
|
43
44
|
id: c.id ?? "comment",
|
|
44
45
|
authorName: commentAuthor(c),
|
|
45
46
|
text: commentText(c),
|
|
46
|
-
createdDate: c.createdDate ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
47
|
+
createdDate: c.createdDate ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
48
|
+
richContent: commentRichContent(c)
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
async function createBlogComment(postId, text) {
|
|
@@ -82,7 +84,8 @@ async function listBlogComments(postId) {
|
|
|
82
84
|
id: comment.id ?? "comment",
|
|
83
85
|
authorName: commentAuthor(comment),
|
|
84
86
|
text: commentText(comment),
|
|
85
|
-
createdDate: comment.createdDate ?? ""
|
|
87
|
+
createdDate: comment.createdDate ?? "",
|
|
88
|
+
richContent: commentRichContent(comment)
|
|
86
89
|
}));
|
|
87
90
|
const instantOf = (row) => {
|
|
88
91
|
if (!row.createdDate) return Number.POSITIVE_INFINITY;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
// ../../stdlib/wix-sdk/forum/src/extensions.ts
|
|
2
|
+
function forumPostContent(post) {
|
|
3
|
+
let c = post?.content;
|
|
4
|
+
if (typeof c === "string") {
|
|
5
|
+
try {
|
|
6
|
+
c = JSON.parse(c);
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const doc = c;
|
|
12
|
+
return doc && typeof doc === "object" && Array.isArray(doc.nodes) ? doc : null;
|
|
13
|
+
}
|
|
2
14
|
function deletePost(id) {
|
|
3
15
|
if (!id) throw new Error("deletePost: id is required");
|
|
4
16
|
return wixCall("DELETE", `/forum/v1/posts/${encodeURIComponent(id)}`);
|
|
@@ -126,6 +138,7 @@ export {
|
|
|
126
138
|
deletePost,
|
|
127
139
|
disablePostCommenting,
|
|
128
140
|
enablePostCommenting,
|
|
141
|
+
forumPostContent,
|
|
129
142
|
getCategory,
|
|
130
143
|
getPost,
|
|
131
144
|
listForumBffCategories,
|
package/dist/cli.mjs
CHANGED
|
@@ -23567,7 +23567,6 @@ function shouldRevealError(args) {
|
|
|
23567
23567
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
23568
23568
|
var init_validation = __esm({
|
|
23569
23569
|
"../../core/ts/src/validation.ts"() {
|
|
23570
|
-
"use strict";
|
|
23571
23570
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
23572
23571
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
23573
23572
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -24227,7 +24226,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
24227
24226
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
24228
24227
|
var init_worker_subtree = __esm({
|
|
24229
24228
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
24230
|
-
"use strict";
|
|
24231
24229
|
init_worker_subtree_safe_types_generated();
|
|
24232
24230
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
24233
24231
|
maxEncodedBytes: 65536,
|
|
@@ -10436,7 +10436,7 @@
|
|
|
10436
10436
|
"name": "WixBlogCommentRow",
|
|
10437
10437
|
"kind": "type",
|
|
10438
10438
|
"jsdoc": "",
|
|
10439
|
-
"decl": "export type WixBlogCommentRow = {\n id: string;\n authorName: string;\n text: string;\n createdDate: string;\n};"
|
|
10439
|
+
"decl": "export type WixBlogCommentRow = {\n id: string;\n authorName: string;\n /** The comment's plain text (every text run, space-joined). */\n text: string;\n createdDate: string;\n /** [rico] #3372 — the comment's Ricos document (`content.richContent`) as the API returned it, for a\n * RichContent row; undefined when the comment has none. */\n richContent?: unknown;\n};"
|
|
10440
10440
|
},
|
|
10441
10441
|
{
|
|
10442
10442
|
"name": "blogCommentRow",
|
|
@@ -17506,6 +17506,18 @@
|
|
|
17506
17506
|
"jsdoc": "/** Response of pin/unpin/delete — the mutated post (may be absent for delete). */",
|
|
17507
17507
|
"decl": "export interface WixPostResponse {\n post?: WixPost;\n}"
|
|
17508
17508
|
},
|
|
17509
|
+
{
|
|
17510
|
+
"name": "WixForumRichContent",
|
|
17511
|
+
"kind": "interface",
|
|
17512
|
+
"jsdoc": "/** A forum post's Ricos body (`content`). Opaque here: render it with RichContent (`@himalaya/wix-rich-content`\n * prepares it); `contentText` stays the plain-text copy. */",
|
|
17513
|
+
"decl": "export interface WixForumRichContent { nodes?: unknown[]; metadata?: unknown; documentStyle?: unknown }"
|
|
17514
|
+
},
|
|
17515
|
+
{
|
|
17516
|
+
"name": "forumPostContent",
|
|
17517
|
+
"kind": "function",
|
|
17518
|
+
"jsdoc": "/** [rico] #3372 — the post's Ricos `content` document (an object, or its JSON string), or null when it has none\n * or it is not a document. */",
|
|
17519
|
+
"decl": "export function forumPostContent(post: WixPost | null | undefined): WixForumRichContent | null;"
|
|
17520
|
+
},
|
|
17509
17521
|
{
|
|
17510
17522
|
"name": "deletePost",
|
|
17511
17523
|
"kind": "function",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1146.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": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "da1d9d3e46786000c48c49145c2ea2b1212f236db2110b68775d29ee"
|
|
71
71
|
}
|
|
@@ -25845,7 +25845,33 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
25845
25845
|
}
|
|
25846
25846
|
},
|
|
25847
25847
|
{
|
|
25848
|
-
"id": "",
|
|
25848
|
+
"id": "product_body",
|
|
25849
|
+
"body": {
|
|
25850
|
+
"package": "himalaya",
|
|
25851
|
+
"type": "RichContent",
|
|
25852
|
+
"props": {
|
|
25853
|
+
"documentJsonPath": "$service:himiStoreProduct.descriptionJson",
|
|
25854
|
+
"onLink": {
|
|
25855
|
+
"actionId": "tier5.himiStoreProduct.openLink"
|
|
25856
|
+
},
|
|
25857
|
+
"fallback": {
|
|
25858
|
+
"id": "",
|
|
25859
|
+
"body": {
|
|
25860
|
+
"package": "himalaya",
|
|
25861
|
+
"type": "Text",
|
|
25862
|
+
"props": {
|
|
25863
|
+
"text": "$service:himiStoreProduct.description",
|
|
25864
|
+
"style": "body",
|
|
25865
|
+
"color": "textSecondary",
|
|
25866
|
+
"collapseWhenEmpty": true
|
|
25867
|
+
}
|
|
25868
|
+
}
|
|
25869
|
+
}
|
|
25870
|
+
}
|
|
25871
|
+
}
|
|
25872
|
+
},
|
|
25873
|
+
{
|
|
25874
|
+
"id": "product_body_text",
|
|
25849
25875
|
"body": {
|
|
25850
25876
|
"package": "himalaya",
|
|
25851
25877
|
"type": "Text",
|
|
@@ -25855,6 +25881,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
25855
25881
|
"color": "textSecondary",
|
|
25856
25882
|
"collapseWhenEmpty": true
|
|
25857
25883
|
}
|
|
25884
|
+
},
|
|
25885
|
+
"modifiers": {
|
|
25886
|
+
"hiddenPath": "$service:himiStoreProduct.hasDescriptionJson"
|
|
25858
25887
|
}
|
|
25859
25888
|
}
|
|
25860
25889
|
]
|
|
@@ -26349,7 +26378,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
26349
26378
|
"declaredCapabilities": [
|
|
26350
26379
|
"wix_api",
|
|
26351
26380
|
"share",
|
|
26352
|
-
"local_database"
|
|
26381
|
+
"local_database",
|
|
26382
|
+
"open_url"
|
|
26353
26383
|
],
|
|
26354
26384
|
"minBinaryVersion": "0.1.0",
|
|
26355
26385
|
"bundleVersion": "0.0.1"
|
|
@@ -28860,7 +28890,33 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
28860
28890
|
}
|
|
28861
28891
|
},
|
|
28862
28892
|
{
|
|
28863
|
-
"id": "",
|
|
28893
|
+
"id": "comment_body_$item.id",
|
|
28894
|
+
"body": {
|
|
28895
|
+
"package": "himalaya",
|
|
28896
|
+
"type": "RichContent",
|
|
28897
|
+
"props": {
|
|
28898
|
+
"documentJsonPath": "$item.bodyJson",
|
|
28899
|
+
"onLink": {
|
|
28900
|
+
"actionId": "tier5.himiBlogPost.openLink"
|
|
28901
|
+
},
|
|
28902
|
+
"fallback": {
|
|
28903
|
+
"id": "",
|
|
28904
|
+
"body": {
|
|
28905
|
+
"package": "himalaya",
|
|
28906
|
+
"type": "Text",
|
|
28907
|
+
"props": {
|
|
28908
|
+
"text": "$item.text",
|
|
28909
|
+
"style": "body",
|
|
28910
|
+
"color": "textSecondary",
|
|
28911
|
+
"collapseWhenEmpty": true
|
|
28912
|
+
}
|
|
28913
|
+
}
|
|
28914
|
+
}
|
|
28915
|
+
}
|
|
28916
|
+
}
|
|
28917
|
+
},
|
|
28918
|
+
{
|
|
28919
|
+
"id": "comment_body_text_$item.id",
|
|
28864
28920
|
"body": {
|
|
28865
28921
|
"package": "himalaya",
|
|
28866
28922
|
"type": "Text",
|
|
@@ -28870,6 +28926,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
28870
28926
|
"color": "textSecondary",
|
|
28871
28927
|
"collapseWhenEmpty": true
|
|
28872
28928
|
}
|
|
28929
|
+
},
|
|
28930
|
+
"modifiers": {
|
|
28931
|
+
"hiddenPath": "$item.hasBodyJson"
|
|
28873
28932
|
}
|
|
28874
28933
|
}
|
|
28875
28934
|
]
|
|
@@ -14,15 +14,21 @@ export interface RichBody {
|
|
|
14
14
|
bodyText: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** [rico] #3372 — the media width for a document drawn in a LIST row (a feed post, a comment, a CMS item): the
|
|
18
|
+
* list-image size (WIX_LIST_IMAGE), so a scrolling list never downloads every image at detail size. */
|
|
19
|
+
export const ROW_IMAGE_WIDTH = 1000;
|
|
20
|
+
|
|
17
21
|
/** Prepares `richContent` (a Ricos document, as a Wix API returns it, or its JSON string). No document, or one
|
|
18
22
|
* that is not a document, gives `{ bodyJson: "", bodyText: fallback }`. A document past the viewer's limits
|
|
19
23
|
* gives bodyJson "" and its plain text — except one rejected as too deep, which is not walked again (the
|
|
20
|
-
* plain-text walk has no depth limit), so `fallback` stands in for it.
|
|
21
|
-
|
|
24
|
+
* plain-text walk has no depth limit), so `fallback` stands in for it. `imageWidth` sizes its media (default
|
|
25
|
+
* the 1400 px detail budget). */
|
|
26
|
+
export function richBody(richContent: unknown, fallback = "", imageWidth = 1400): RichBody {
|
|
22
27
|
const doc = parseRicosJson(richContent);
|
|
23
28
|
if (!doc) return { bodyJson: "", bodyText: fallback };
|
|
24
|
-
// 1400 px — the detail-image budget, which is also the encoder's maximum
|
|
25
|
-
|
|
29
|
+
// 1400 px by default — the detail-image budget, which is also the encoder's maximum; list rows pass
|
|
30
|
+
// ROW_IMAGE_WIDTH.
|
|
31
|
+
const { json, rejected } = encodeRichContent(doc, { imageWidth });
|
|
26
32
|
const text = rejected === "too_deep" ? "" : ricosToPlainText(doc);
|
|
27
33
|
return { bodyJson: json, bodyText: text || fallback };
|
|
28
34
|
}
|
|
@@ -8,7 +8,7 @@ import { isSavedId, toggleSavedId } from "../_shared/himi-blog-saved";
|
|
|
8
8
|
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
|
-
import { richBody } from "../_shared/rich-body.js";
|
|
11
|
+
import { richBody, ROW_IMAGE_WIDTH } from "../_shared/rich-body.js";
|
|
12
12
|
|
|
13
13
|
// Offline-first (data-offline): the post body is a public read-through — when the
|
|
14
14
|
// live fetch fails (offline) fall back to the last-synced row the feed cached under
|
|
@@ -23,8 +23,14 @@ async function readCachedPost(id: string): Promise<DbEntity | null> {
|
|
|
23
23
|
interface CommentRow {
|
|
24
24
|
id: string;
|
|
25
25
|
authorName: string;
|
|
26
|
+
/** The comment's plain text: the row RichContent's fallback. */
|
|
26
27
|
text: string;
|
|
27
28
|
meta: string;
|
|
29
|
+
/** [rico] #3372 — the comment's Ricos document as ONE string for the row's RichContent; "" when it has none
|
|
30
|
+
* or the encoder rejected it. */
|
|
31
|
+
bodyJson: string;
|
|
32
|
+
/** bodyJson !== "" — gates the row's plain text, because RichContent draws nothing for "". */
|
|
33
|
+
hasBodyJson: boolean;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
interface S {
|
|
@@ -168,11 +174,15 @@ function commentMeta(row: WixBlogCommentRow): string {
|
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
function toCommentRow(row: WixBlogCommentRow): CommentRow {
|
|
177
|
+
// The plain text is the document's own (runs joined as written, not space-separated) when it has one.
|
|
178
|
+
const { bodyJson, bodyText } = richBody(row.richContent, row.text || "Comment", ROW_IMAGE_WIDTH);
|
|
171
179
|
return {
|
|
172
180
|
id: row.id,
|
|
173
181
|
authorName: row.authorName || "Member",
|
|
174
|
-
text:
|
|
182
|
+
text: bodyText,
|
|
175
183
|
meta: commentMeta(row),
|
|
184
|
+
bodyJson,
|
|
185
|
+
hasBodyJson: bodyJson !== "",
|
|
176
186
|
};
|
|
177
187
|
}
|
|
178
188
|
|
|
@@ -10,6 +10,8 @@ import { cleanTitle } from "../_shared/himi-text.js";
|
|
|
10
10
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
11
11
|
import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
|
|
12
12
|
import { isProductSaved, toggleSavedProduct } from "../_shared/himi-store-saved.js";
|
|
13
|
+
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
14
|
+
import { richBody } from "../_shared/rich-body.js";
|
|
13
15
|
|
|
14
16
|
// Offline-first (data-offline): the detail is a public READ-THROUGH — when the live
|
|
15
17
|
// product fetch fails (offline) fall back to the last-synced row the catalog cached
|
|
@@ -120,11 +122,27 @@ function optionsHint(groups: StoreOption[], selection: StoreOptionSelection): st
|
|
|
120
122
|
return copyTemplate("product.optionsChoose", "Choose {options}", { options: missing.join(", ") });
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
/** The description pair. V3's `description` is a Ricos OBJECT (the HTML `plainText` would throw on it): it
|
|
126
|
+
* becomes the RichContent document, and `plainDescription` (or the document's own text) the plain copy. A V1
|
|
127
|
+
* `description` is an HTML string: stripped text, no document. */
|
|
128
|
+
function productBody(p: StoreProduct): { description: string; descriptionJson: string } {
|
|
129
|
+
const raw = (p as { description?: unknown }).description;
|
|
130
|
+
const html = plainText(p.plainDescription || (typeof raw === "string" ? raw : ""));
|
|
131
|
+
const { bodyJson, bodyText } = richBody(typeof raw === "string" ? null : raw, html);
|
|
132
|
+
return { description: html || bodyText, descriptionJson: bodyJson };
|
|
133
|
+
}
|
|
134
|
+
|
|
123
135
|
interface S {
|
|
124
136
|
status: "loading" | "ready" | "error";
|
|
125
137
|
name: string;
|
|
126
138
|
price: string;
|
|
139
|
+
/** The plain-text description: RichContent's fallback, and what shows with no document. */
|
|
127
140
|
description: string;
|
|
141
|
+
/** [rico] #3372 — a V3 product's Ricos `description` (read with `fields=DESCRIPTION`) as ONE document string
|
|
142
|
+
* for RichContent; "" for a V1 HTML description, none, or a document the encoder rejected. */
|
|
143
|
+
descriptionJson: string;
|
|
144
|
+
/** descriptionJson !== "" — gates the plain text, because RichContent draws nothing for "". */
|
|
145
|
+
hasDescriptionJson: boolean;
|
|
128
146
|
image: string;
|
|
129
147
|
/** FULL media gallery (media.items[] with the main image first) — the detail
|
|
130
148
|
* screen renders a Carousel over it when 2+ images exist. */
|
|
@@ -253,6 +271,7 @@ function derive(s: S): S {
|
|
|
253
271
|
const overflow = groups.length > OPTION_SLOTS;
|
|
254
272
|
return {
|
|
255
273
|
...s,
|
|
274
|
+
hasDescriptionJson: s.descriptionJson !== "",
|
|
256
275
|
...slotsOf(groups, s.selection),
|
|
257
276
|
hasOptions: groups.length > 0,
|
|
258
277
|
noOptions: groups.length === 0,
|
|
@@ -296,12 +315,16 @@ const svc = defineService<S, {
|
|
|
296
315
|
setCartCount: (s: S, arg?: unknown) => S;
|
|
297
316
|
share: (s: S) => Promise<S>;
|
|
298
317
|
toggleSave: (s: S) => S;
|
|
318
|
+
/** [rico] #3372 — RichContent's on_link: `{ url }` of the tapped link. */
|
|
319
|
+
openLink: (s: S, arg?: unknown) => Promise<S>;
|
|
299
320
|
}>("himiStoreProduct", {
|
|
300
321
|
state: derive({
|
|
301
322
|
status: "loading",
|
|
302
323
|
name: "",
|
|
303
324
|
price: "",
|
|
304
325
|
description: "",
|
|
326
|
+
descriptionJson: "",
|
|
327
|
+
hasDescriptionJson: false,
|
|
305
328
|
image: "",
|
|
306
329
|
gallery: [],
|
|
307
330
|
hasGallery: false,
|
|
@@ -354,7 +377,7 @@ const svc = defineService<S, {
|
|
|
354
377
|
if (!id) {
|
|
355
378
|
lastProduct = undefined;
|
|
356
379
|
lastLoadedId = undefined;
|
|
357
|
-
return derive({ ...s, status: "ready", quantity: 1, optionGroups: [], selection: {}, optionsUnknown: false, emptyText: copyParam("product.notFoundText", "Product not found.") });
|
|
380
|
+
return derive({ ...s, status: "ready", quantity: 1, optionGroups: [], selection: {}, optionsUnknown: false, descriptionJson: "", emptyText: copyParam("product.notFoundText", "Product not found.") });
|
|
358
381
|
}
|
|
359
382
|
// The cart count only feeds the top-bar badge: ask for it NOW, beside the product, instead
|
|
360
383
|
// of after it — it used to add a whole cart round trip before the page could paint.
|
|
@@ -365,7 +388,8 @@ const svc = defineService<S, {
|
|
|
365
388
|
void cartCount.then((n) => { cartKnown = n; }, () => {});
|
|
366
389
|
// The live product read goes out FIRST too — the local-db read below must never delay it.
|
|
367
390
|
// Settled into a value so a failure is handled where it is awaited, never unhandled.
|
|
368
|
-
|
|
391
|
+
// DESCRIPTION widens the default fieldset with the V3 Ricos description (a V1 catalog ignores it).
|
|
392
|
+
const live = getStoreProduct(id, ["DESCRIPTION"]).then(
|
|
369
393
|
(r) => ({ ok: true as const, r }),
|
|
370
394
|
() => ({ ok: false as const }),
|
|
371
395
|
);
|
|
@@ -424,7 +448,7 @@ const svc = defineService<S, {
|
|
|
424
448
|
optionsUnknown: false,
|
|
425
449
|
name,
|
|
426
450
|
price: productPrice(p),
|
|
427
|
-
|
|
451
|
+
...productBody(p),
|
|
428
452
|
image,
|
|
429
453
|
gallery: productGallery(p),
|
|
430
454
|
initial: (name[0] || "#").toUpperCase(),
|
|
@@ -455,6 +479,7 @@ const svc = defineService<S, {
|
|
|
455
479
|
name,
|
|
456
480
|
price: String(synced.subtitle ?? ""),
|
|
457
481
|
description: "",
|
|
482
|
+
descriptionJson: "",
|
|
458
483
|
image,
|
|
459
484
|
initial: String(synced.initial ?? "") || (name[0] || "#").toUpperCase(),
|
|
460
485
|
cartHint: copyParam("product.cartHintOffline", "Reconnect to choose options and add this to your cart."),
|
|
@@ -464,7 +489,7 @@ const svc = defineService<S, {
|
|
|
464
489
|
}
|
|
465
490
|
lastProduct = undefined;
|
|
466
491
|
lastLoadedId = undefined;
|
|
467
|
-
return derive({ ...s, status: "error", optionGroups: [], selection: {}, optionsUnknown: false, emptyText: copyParam("product.errorText", "Product could not load.") });
|
|
492
|
+
return derive({ ...s, status: "error", optionGroups: [], selection: {}, optionsUnknown: false, descriptionJson: "", emptyText: copyParam("product.errorText", "Product could not load.") });
|
|
468
493
|
},
|
|
469
494
|
// The stepper fires onChange with { value } on every tap; clamp 1–99 and
|
|
470
495
|
// ignore unparsable/garbage args instead of silently zeroing the quantity.
|
|
@@ -519,6 +544,7 @@ const svc = defineService<S, {
|
|
|
519
544
|
name,
|
|
520
545
|
price: String(row.subtitle ?? ""),
|
|
521
546
|
description: "",
|
|
547
|
+
descriptionJson: "",
|
|
522
548
|
image: String(row.image ?? ""),
|
|
523
549
|
gallery: [],
|
|
524
550
|
initial: String(row.initial ?? "") || (name[0] || "#").toUpperCase(),
|
|
@@ -605,6 +631,11 @@ const svc = defineService<S, {
|
|
|
605
631
|
return derive({ ...s, added: false, cartHint: copyParam("product.shareUnavailable", "Sharing is not available on this device.") });
|
|
606
632
|
}
|
|
607
633
|
},
|
|
634
|
+
// Only http(s), mailto and tel links reach open_url; a refused link, or one nothing opened, says so in the
|
|
635
|
+
// hint line (`added: false`, as for share, so it never renders inside the add-to-cart confirmation).
|
|
636
|
+
openLink: async (s, arg) => ((await openRichLink(arg))
|
|
637
|
+
? s
|
|
638
|
+
: derive({ ...s, added: false, cartHint: copyParam("product.linkOpenFailed", "This link could not be opened.") })),
|
|
608
639
|
},
|
|
609
640
|
});
|
|
610
641
|
|
|
@@ -14,15 +14,21 @@ export interface RichBody {
|
|
|
14
14
|
bodyText: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** [rico] #3372 — the media width for a document drawn in a LIST row (a feed post, a comment, a CMS item): the
|
|
18
|
+
* list-image size (WIX_LIST_IMAGE), so a scrolling list never downloads every image at detail size. */
|
|
19
|
+
export const ROW_IMAGE_WIDTH = 1000;
|
|
20
|
+
|
|
17
21
|
/** Prepares `richContent` (a Ricos document, as a Wix API returns it, or its JSON string). No document, or one
|
|
18
22
|
* that is not a document, gives `{ bodyJson: "", bodyText: fallback }`. A document past the viewer's limits
|
|
19
23
|
* gives bodyJson "" and its plain text — except one rejected as too deep, which is not walked again (the
|
|
20
|
-
* plain-text walk has no depth limit), so `fallback` stands in for it.
|
|
21
|
-
|
|
24
|
+
* plain-text walk has no depth limit), so `fallback` stands in for it. `imageWidth` sizes its media (default
|
|
25
|
+
* the 1400 px detail budget). */
|
|
26
|
+
export function richBody(richContent: unknown, fallback = "", imageWidth = 1400): RichBody {
|
|
22
27
|
const doc = parseRicosJson(richContent);
|
|
23
28
|
if (!doc) return { bodyJson: "", bodyText: fallback };
|
|
24
|
-
// 1400 px — the detail-image budget, which is also the encoder's maximum
|
|
25
|
-
|
|
29
|
+
// 1400 px by default — the detail-image budget, which is also the encoder's maximum; list rows pass
|
|
30
|
+
// ROW_IMAGE_WIDTH.
|
|
31
|
+
const { json, rejected } = encodeRichContent(doc, { imageWidth });
|
|
26
32
|
const text = rejected === "too_deep" ? "" : ricosToPlainText(doc);
|
|
27
33
|
return { bodyJson: json, bodyText: text || fallback };
|
|
28
34
|
}
|