@wix/himalaya-cli 0.1078.0 → 0.1080.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/dev-server/src/builder.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +9 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +41 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/site-shell.d.ts +0 -14
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/validator.d.ts +36 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/rich-content/src/display.d.ts +26 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/rich-content/src/index.d.ts +79 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +26 -49
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +11 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-rich-content.mjs +344 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/package.json +4 -0
- package/dist/cli.mjs +104 -7
- package/guides/importable-sdks.md +1 -0
- package/guides/sdk-reference.json +74 -1
- package/guides/splash-screens.md +13 -1
- package/package.json +2 -2
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/community-club-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/community-club-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/events-meetups-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/events-meetups-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/owner-lite/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/owner-lite/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/restaurant-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/restaurant-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
- package/templates/retail-store-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/retail-store-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-post-content.ts +7 -26
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-rich-guard.ts +12 -41
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +2 -2
package/guides/splash-screens.md
CHANGED
|
@@ -148,6 +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
152
|
}
|
|
152
153
|
```
|
|
153
154
|
|
|
@@ -163,13 +164,24 @@ A `splashScreenId` pointing at an OTA descriptor fails at exactly the moment it'
|
|
|
163
164
|
render the generic `ProgressView` + literal `"Loading…"` on precisely the launch we're fixing.
|
|
164
165
|
It would also invert boot ordering (`applyProdServeFromCache` and `LocaleStore.initShared` must
|
|
165
166
|
precede any fetch) and put descriptor-shaped payload inside `NavigationConfig`, where a single
|
|
166
|
-
strict-decode throw disables **all** OTA. Proto field
|
|
167
|
+
strict-decode throw disables **all** OTA. Proto field 14+ is reserved should that ever change
|
|
168
|
+
(13 is `ready_signal`).
|
|
167
169
|
|
|
168
170
|
### Readiness, not a timer
|
|
169
171
|
|
|
170
172
|
`BootReadiness` (iOS + Android, one tested implementation each) decides when the surface may
|
|
171
173
|
go: **seeded AND past the anti-flash floor, OR the hard ceiling elapsed.**
|
|
172
174
|
|
|
175
|
+
`readySignal: "first_content"` adds one
|
|
176
|
+
condition to "seeded": the first WebView page has finished loading. It exists for a site shell
|
|
177
|
+
(`himi-brandy`), where every tab is the customer's website and a splash that leaves on `seeded`
|
|
178
|
+
fades into a blank WebView. Each native WebView host calls `BootReadiness.markFirstContent()` on
|
|
179
|
+
a real page load (not on an error page or a cancelled navigation), and the shell re-evaluates
|
|
180
|
+
on it: `BootReadiness.firstContentNotification` on iOS, the `firstContent` `StateFlow` on
|
|
181
|
+
Android. The shell must also MOUNT its content under the splash, not after it, or there is no
|
|
182
|
+
page to wait for. The ceiling still applies, and a launch it releases while waiting for content
|
|
183
|
+
reports `splash_timeout`. Unknown values read as `""` on device; `defineApp` rejects them in source.
|
|
184
|
+
|
|
173
185
|
The ceiling is the load-bearing part. The app must always become interactive, so `maxHoldMs`
|
|
174
186
|
dismisses the splash even if readiness is never reported — a failed seed prefill, a slow disk,
|
|
175
187
|
or a bug in boot cannot wedge the UI behind a splash. `effectiveReady` is **monotonic**: once
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1080.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": "1e0c05f97a07293123b9d983e46d508913e929db4fde75461f56a065"
|
|
71
71
|
}
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import type { WixRichContent } from "@himalaya/wix-blog";
|
|
2
|
+
// The rich-content helpers. Inside the platform source this import is a relative path, so node test runs
|
|
3
|
+
// resolve it; an app's copy imports "@himalaya/wix-rich-content".
|
|
4
|
+
import { ricosToPlainText, isPlainRicos, plainTextToRicos } from "@himalaya/wix-rich-content";
|
|
2
5
|
import { wixMediaUrl, WIX_DETAIL_IMAGE } from "./himi-store.js";
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
// Thin wrappers keep the WixRichContent signatures callers compile against.
|
|
5
8
|
/** Join inline runs without adding paragraph breaks between bold/plain spans. */
|
|
6
|
-
export
|
|
7
|
-
const inline = (node: Node): string => typeof node.textData?.text === "string"
|
|
8
|
-
? node.textData.text : (node.nodes ?? []).map(inline).join("");
|
|
9
|
-
const blocks = (nodes: Node[]): string[] => nodes.flatMap(node =>
|
|
10
|
-
node.type === "PARAGRAPH" || node.type === "HEADING" || node.type === "TEXT"
|
|
11
|
-
? [inline(node)] : blocks(node.nodes ?? []));
|
|
12
|
-
return blocks((rich?.nodes ?? []) as Node[]).join("\n\n");
|
|
13
|
-
}
|
|
9
|
+
export const postBodyText = (rich: WixRichContent | null | undefined): string => ricosToPlainText(rich);
|
|
14
10
|
/** The plain editor must not overwrite formatting or any opaque media node.
|
|
15
11
|
* Rich drafts remain editable for title/excerpt/cover; body stays untouched. */
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
if (!raw || typeof raw !== "object") return true;
|
|
19
|
-
const node = raw as Node;
|
|
20
|
-
if (node.type !== "PARAGRAPH") return true;
|
|
21
|
-
if (Object.keys(node).some(key => !["type", "id", "nodes", "paragraphData"].includes(key))) return true;
|
|
22
|
-
if (node.paragraphData && Object.keys(node.paragraphData as object).length) return true;
|
|
23
|
-
return (node.nodes ?? []).some(child => child.type !== "TEXT"
|
|
24
|
-
|| !!child.textData?.decorations?.length
|
|
25
|
-
|| Object.keys(child).some(key => !["type", "id", "textData", "nodes"].includes(key)));
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function plainPostBody(body: string): WixRichContent {
|
|
29
|
-
return { nodes: body.split("\n\n").map(text => ({
|
|
30
|
-
type: "PARAGRAPH", nodes: [{ type: "TEXT", textData: { text, decorations: [] } }],
|
|
31
|
-
})) };
|
|
32
|
-
}
|
|
12
|
+
export const hasRichBody = (rich: WixRichContent | null | undefined): boolean => !isPlainRicos(rich);
|
|
13
|
+
export const plainPostBody = (body: string): WixRichContent => plainTextToRicos(body);
|
|
33
14
|
/** Current Blog media shape plus older coverImage projections. */
|
|
34
15
|
export function postCoverUrl(raw: Record<string, any> | null | undefined): string {
|
|
35
16
|
if (!raw) return "";
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// May a plain owner form round-trip this body? The Ricos and HTML checks are @himalaya/wix-rich-content's;
|
|
2
|
+
// this file adds the read-only previews the forms show for a locked body, and the FAQ's legacy DraftJS answer.
|
|
3
|
+
// Inside the platform source this import is a relative path, so node test runs resolve it; an app's copy
|
|
4
|
+
// imports "@himalaya/wix-rich-content".
|
|
5
|
+
import { htmlIsTrivial, isPlainRicos } from "@himalaya/wix-rich-content";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const TAG = /<[a-z!/?]/i;
|
|
7
|
-
|
|
8
|
-
/** True when a plain text field can hold this HTML without losing anything: empty, bare text
|
|
9
|
-
* (no tag — a lone `<` in prose is text), or exactly one attribute-less `<p>…</p>` with no
|
|
10
|
-
* inner tags. */
|
|
11
|
-
export function htmlIsTrivial(html: string | null | undefined): boolean {
|
|
12
|
-
const s = (html ?? "").trim();
|
|
13
|
-
return !s || !TAG.test(s) || /^<p>[^<>]*<\/p>$/.test(s);
|
|
14
|
-
}
|
|
7
|
+
export { htmlIsTrivial };
|
|
15
8
|
|
|
16
9
|
const ENTITIES: Record<string, string> = { " ": " ", "&": "&", "<": "<", ">": ">", """: "\"", "'": "'" };
|
|
17
10
|
|
|
@@ -23,35 +16,13 @@ export function htmlPreviewText(html: string | null | undefined): string {
|
|
|
23
16
|
.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
|
|
24
17
|
}
|
|
25
18
|
|
|
26
|
-
type RicosNode = { type?: unknown; nodes?: unknown; textData?: { decorations?: unknown }; paragraphData?: unknown; [key: string]: unknown };
|
|
27
|
-
const onlyKeys = (o: object, allowed: string[]) => Object.keys(o).every((k) => allowed.includes(k));
|
|
28
|
-
const emptyObject = (v: unknown) => v === undefined || (!!v && typeof v === "object" && Object.keys(v as object).length === 0);
|
|
29
|
-
|
|
30
|
-
/** The paragraph styling a PLAIN paragraph still carries on the Ricos wire (Events v3 sends
|
|
31
|
-
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). */
|
|
32
|
-
function defaultParagraphData(pd: unknown): boolean {
|
|
33
|
-
if (pd === undefined) return true;
|
|
34
|
-
if (!pd || typeof pd !== "object" || !onlyKeys(pd, ["textStyle", "indentation"])) return false;
|
|
35
|
-
const { textStyle, indentation } = pd as { textStyle?: unknown; indentation?: unknown };
|
|
36
|
-
if (indentation !== undefined && indentation !== 0) return false;
|
|
37
|
-
if (textStyle === undefined) return true;
|
|
38
|
-
if (!textStyle || typeof textStyle !== "object" || !onlyKeys(textStyle, ["textAlignment"])) return false;
|
|
39
|
-
const align = (textStyle as { textAlignment?: unknown }).textAlignment;
|
|
40
|
-
return align === undefined || align === "AUTO";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
19
|
/** True when a plain text field can round-trip this Ricos document: no document styling, and
|
|
44
|
-
* only paragraphs of undecorated text whose paragraph data is the wire default
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
&& onlyKeys(n, ["type", "id", "nodes", "paragraphData"]) && defaultParagraphData(n.paragraphData)
|
|
51
|
-
&& (Array.isArray(n.nodes) ? (n.nodes as RicosNode[]) : []).every((t) => !!t && typeof t === "object" && t.type === "TEXT"
|
|
52
|
-
&& onlyKeys(t, ["type", "id", "textData", "nodes"]) && (!Array.isArray(t.nodes) || t.nodes.length === 0)
|
|
53
|
-
&& !(Array.isArray(t.textData?.decorations) && t.textData.decorations.length)));
|
|
54
|
-
}
|
|
20
|
+
* only paragraphs of undecorated text whose paragraph data is the wire default (Events v3 sends
|
|
21
|
+
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). Anything else —
|
|
22
|
+
* a heading, list, link, bold run, media node, alignment or indent — is rich, and so is a missing
|
|
23
|
+
* document (callers decide what an absent body means). */
|
|
24
|
+
export const ricosIsPlain = (doc: { nodes?: unknown; documentStyle?: unknown } | null | undefined): boolean =>
|
|
25
|
+
!!doc && isPlainRicos(doc);
|
|
55
26
|
|
|
56
27
|
/** Preview text for a legacy DraftJS document: its `blocks[].text` joined by a blank line, or ""
|
|
57
28
|
* when the payload is not DraftJS JSON (never a throw). */
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import type { WixRichContent } from "@himalaya/wix-blog";
|
|
2
|
+
// The rich-content helpers. Inside the platform source this import is a relative path, so node test runs
|
|
3
|
+
// resolve it; an app's copy imports "@himalaya/wix-rich-content".
|
|
4
|
+
import { ricosToPlainText, isPlainRicos, plainTextToRicos } from "@himalaya/wix-rich-content";
|
|
2
5
|
import { wixMediaUrl, WIX_DETAIL_IMAGE } from "./himi-store.js";
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
// Thin wrappers keep the WixRichContent signatures callers compile against.
|
|
5
8
|
/** Join inline runs without adding paragraph breaks between bold/plain spans. */
|
|
6
|
-
export
|
|
7
|
-
const inline = (node: Node): string => typeof node.textData?.text === "string"
|
|
8
|
-
? node.textData.text : (node.nodes ?? []).map(inline).join("");
|
|
9
|
-
const blocks = (nodes: Node[]): string[] => nodes.flatMap(node =>
|
|
10
|
-
node.type === "PARAGRAPH" || node.type === "HEADING" || node.type === "TEXT"
|
|
11
|
-
? [inline(node)] : blocks(node.nodes ?? []));
|
|
12
|
-
return blocks((rich?.nodes ?? []) as Node[]).join("\n\n");
|
|
13
|
-
}
|
|
9
|
+
export const postBodyText = (rich: WixRichContent | null | undefined): string => ricosToPlainText(rich);
|
|
14
10
|
/** The plain editor must not overwrite formatting or any opaque media node.
|
|
15
11
|
* Rich drafts remain editable for title/excerpt/cover; body stays untouched. */
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
if (!raw || typeof raw !== "object") return true;
|
|
19
|
-
const node = raw as Node;
|
|
20
|
-
if (node.type !== "PARAGRAPH") return true;
|
|
21
|
-
if (Object.keys(node).some(key => !["type", "id", "nodes", "paragraphData"].includes(key))) return true;
|
|
22
|
-
if (node.paragraphData && Object.keys(node.paragraphData as object).length) return true;
|
|
23
|
-
return (node.nodes ?? []).some(child => child.type !== "TEXT"
|
|
24
|
-
|| !!child.textData?.decorations?.length
|
|
25
|
-
|| Object.keys(child).some(key => !["type", "id", "textData", "nodes"].includes(key)));
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function plainPostBody(body: string): WixRichContent {
|
|
29
|
-
return { nodes: body.split("\n\n").map(text => ({
|
|
30
|
-
type: "PARAGRAPH", nodes: [{ type: "TEXT", textData: { text, decorations: [] } }],
|
|
31
|
-
})) };
|
|
32
|
-
}
|
|
12
|
+
export const hasRichBody = (rich: WixRichContent | null | undefined): boolean => !isPlainRicos(rich);
|
|
13
|
+
export const plainPostBody = (body: string): WixRichContent => plainTextToRicos(body);
|
|
33
14
|
/** Current Blog media shape plus older coverImage projections. */
|
|
34
15
|
export function postCoverUrl(raw: Record<string, any> | null | undefined): string {
|
|
35
16
|
if (!raw) return "";
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// May a plain owner form round-trip this body? The Ricos and HTML checks are @himalaya/wix-rich-content's;
|
|
2
|
+
// this file adds the read-only previews the forms show for a locked body, and the FAQ's legacy DraftJS answer.
|
|
3
|
+
// Inside the platform source this import is a relative path, so node test runs resolve it; an app's copy
|
|
4
|
+
// imports "@himalaya/wix-rich-content".
|
|
5
|
+
import { htmlIsTrivial, isPlainRicos } from "@himalaya/wix-rich-content";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const TAG = /<[a-z!/?]/i;
|
|
7
|
-
|
|
8
|
-
/** True when a plain text field can hold this HTML without losing anything: empty, bare text
|
|
9
|
-
* (no tag — a lone `<` in prose is text), or exactly one attribute-less `<p>…</p>` with no
|
|
10
|
-
* inner tags. */
|
|
11
|
-
export function htmlIsTrivial(html: string | null | undefined): boolean {
|
|
12
|
-
const s = (html ?? "").trim();
|
|
13
|
-
return !s || !TAG.test(s) || /^<p>[^<>]*<\/p>$/.test(s);
|
|
14
|
-
}
|
|
7
|
+
export { htmlIsTrivial };
|
|
15
8
|
|
|
16
9
|
const ENTITIES: Record<string, string> = { " ": " ", "&": "&", "<": "<", ">": ">", """: "\"", "'": "'" };
|
|
17
10
|
|
|
@@ -23,35 +16,13 @@ export function htmlPreviewText(html: string | null | undefined): string {
|
|
|
23
16
|
.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
|
|
24
17
|
}
|
|
25
18
|
|
|
26
|
-
type RicosNode = { type?: unknown; nodes?: unknown; textData?: { decorations?: unknown }; paragraphData?: unknown; [key: string]: unknown };
|
|
27
|
-
const onlyKeys = (o: object, allowed: string[]) => Object.keys(o).every((k) => allowed.includes(k));
|
|
28
|
-
const emptyObject = (v: unknown) => v === undefined || (!!v && typeof v === "object" && Object.keys(v as object).length === 0);
|
|
29
|
-
|
|
30
|
-
/** The paragraph styling a PLAIN paragraph still carries on the Ricos wire (Events v3 sends
|
|
31
|
-
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). */
|
|
32
|
-
function defaultParagraphData(pd: unknown): boolean {
|
|
33
|
-
if (pd === undefined) return true;
|
|
34
|
-
if (!pd || typeof pd !== "object" || !onlyKeys(pd, ["textStyle", "indentation"])) return false;
|
|
35
|
-
const { textStyle, indentation } = pd as { textStyle?: unknown; indentation?: unknown };
|
|
36
|
-
if (indentation !== undefined && indentation !== 0) return false;
|
|
37
|
-
if (textStyle === undefined) return true;
|
|
38
|
-
if (!textStyle || typeof textStyle !== "object" || !onlyKeys(textStyle, ["textAlignment"])) return false;
|
|
39
|
-
const align = (textStyle as { textAlignment?: unknown }).textAlignment;
|
|
40
|
-
return align === undefined || align === "AUTO";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
19
|
/** True when a plain text field can round-trip this Ricos document: no document styling, and
|
|
44
|
-
* only paragraphs of undecorated text whose paragraph data is the wire default
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
&& onlyKeys(n, ["type", "id", "nodes", "paragraphData"]) && defaultParagraphData(n.paragraphData)
|
|
51
|
-
&& (Array.isArray(n.nodes) ? (n.nodes as RicosNode[]) : []).every((t) => !!t && typeof t === "object" && t.type === "TEXT"
|
|
52
|
-
&& onlyKeys(t, ["type", "id", "textData", "nodes"]) && (!Array.isArray(t.nodes) || t.nodes.length === 0)
|
|
53
|
-
&& !(Array.isArray(t.textData?.decorations) && t.textData.decorations.length)));
|
|
54
|
-
}
|
|
20
|
+
* only paragraphs of undecorated text whose paragraph data is the wire default (Events v3 sends
|
|
21
|
+
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). Anything else —
|
|
22
|
+
* a heading, list, link, bold run, media node, alignment or indent — is rich, and so is a missing
|
|
23
|
+
* document (callers decide what an absent body means). */
|
|
24
|
+
export const ricosIsPlain = (doc: { nodes?: unknown; documentStyle?: unknown } | null | undefined): boolean =>
|
|
25
|
+
!!doc && isPlainRicos(doc);
|
|
55
26
|
|
|
56
27
|
/** Preview text for a legacy DraftJS document: its `blocks[].text` joined by a blank line, or ""
|
|
57
28
|
* when the payload is not DraftJS JSON (never a throw). */
|
|
@@ -10,8 +10,8 @@ import { defineService } from "@himalaya/state";
|
|
|
10
10
|
import { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
|
|
11
11
|
import { normalizeEvent } from "../_shared/himi-events.js";
|
|
12
12
|
import { postBodyText, plainPostBody } from "../_shared/owner-post-content.js";
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// Events v3 sends default paragraphData on EVERY paragraph and an empty documentStyle; ricosIsPlain
|
|
14
|
+
// reads both as plain.
|
|
15
15
|
import { ricosIsPlain } from "../_shared/owner-rich-guard.js";
|
|
16
16
|
import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
|
|
17
17
|
import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import type { WixRichContent } from "@himalaya/wix-blog";
|
|
2
|
+
// The rich-content helpers. Inside the platform source this import is a relative path, so node test runs
|
|
3
|
+
// resolve it; an app's copy imports "@himalaya/wix-rich-content".
|
|
4
|
+
import { ricosToPlainText, isPlainRicos, plainTextToRicos } from "@himalaya/wix-rich-content";
|
|
2
5
|
import { wixMediaUrl, WIX_DETAIL_IMAGE } from "./himi-store.js";
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
// Thin wrappers keep the WixRichContent signatures callers compile against.
|
|
5
8
|
/** Join inline runs without adding paragraph breaks between bold/plain spans. */
|
|
6
|
-
export
|
|
7
|
-
const inline = (node: Node): string => typeof node.textData?.text === "string"
|
|
8
|
-
? node.textData.text : (node.nodes ?? []).map(inline).join("");
|
|
9
|
-
const blocks = (nodes: Node[]): string[] => nodes.flatMap(node =>
|
|
10
|
-
node.type === "PARAGRAPH" || node.type === "HEADING" || node.type === "TEXT"
|
|
11
|
-
? [inline(node)] : blocks(node.nodes ?? []));
|
|
12
|
-
return blocks((rich?.nodes ?? []) as Node[]).join("\n\n");
|
|
13
|
-
}
|
|
9
|
+
export const postBodyText = (rich: WixRichContent | null | undefined): string => ricosToPlainText(rich);
|
|
14
10
|
/** The plain editor must not overwrite formatting or any opaque media node.
|
|
15
11
|
* Rich drafts remain editable for title/excerpt/cover; body stays untouched. */
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
if (!raw || typeof raw !== "object") return true;
|
|
19
|
-
const node = raw as Node;
|
|
20
|
-
if (node.type !== "PARAGRAPH") return true;
|
|
21
|
-
if (Object.keys(node).some(key => !["type", "id", "nodes", "paragraphData"].includes(key))) return true;
|
|
22
|
-
if (node.paragraphData && Object.keys(node.paragraphData as object).length) return true;
|
|
23
|
-
return (node.nodes ?? []).some(child => child.type !== "TEXT"
|
|
24
|
-
|| !!child.textData?.decorations?.length
|
|
25
|
-
|| Object.keys(child).some(key => !["type", "id", "textData", "nodes"].includes(key)));
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function plainPostBody(body: string): WixRichContent {
|
|
29
|
-
return { nodes: body.split("\n\n").map(text => ({
|
|
30
|
-
type: "PARAGRAPH", nodes: [{ type: "TEXT", textData: { text, decorations: [] } }],
|
|
31
|
-
})) };
|
|
32
|
-
}
|
|
12
|
+
export const hasRichBody = (rich: WixRichContent | null | undefined): boolean => !isPlainRicos(rich);
|
|
13
|
+
export const plainPostBody = (body: string): WixRichContent => plainTextToRicos(body);
|
|
33
14
|
/** Current Blog media shape plus older coverImage projections. */
|
|
34
15
|
export function postCoverUrl(raw: Record<string, any> | null | undefined): string {
|
|
35
16
|
if (!raw) return "";
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// May a plain owner form round-trip this body? The Ricos and HTML checks are @himalaya/wix-rich-content's;
|
|
2
|
+
// this file adds the read-only previews the forms show for a locked body, and the FAQ's legacy DraftJS answer.
|
|
3
|
+
// Inside the platform source this import is a relative path, so node test runs resolve it; an app's copy
|
|
4
|
+
// imports "@himalaya/wix-rich-content".
|
|
5
|
+
import { htmlIsTrivial, isPlainRicos } from "@himalaya/wix-rich-content";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const TAG = /<[a-z!/?]/i;
|
|
7
|
-
|
|
8
|
-
/** True when a plain text field can hold this HTML without losing anything: empty, bare text
|
|
9
|
-
* (no tag — a lone `<` in prose is text), or exactly one attribute-less `<p>…</p>` with no
|
|
10
|
-
* inner tags. */
|
|
11
|
-
export function htmlIsTrivial(html: string | null | undefined): boolean {
|
|
12
|
-
const s = (html ?? "").trim();
|
|
13
|
-
return !s || !TAG.test(s) || /^<p>[^<>]*<\/p>$/.test(s);
|
|
14
|
-
}
|
|
7
|
+
export { htmlIsTrivial };
|
|
15
8
|
|
|
16
9
|
const ENTITIES: Record<string, string> = { " ": " ", "&": "&", "<": "<", ">": ">", """: "\"", "'": "'" };
|
|
17
10
|
|
|
@@ -23,35 +16,13 @@ export function htmlPreviewText(html: string | null | undefined): string {
|
|
|
23
16
|
.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
|
|
24
17
|
}
|
|
25
18
|
|
|
26
|
-
type RicosNode = { type?: unknown; nodes?: unknown; textData?: { decorations?: unknown }; paragraphData?: unknown; [key: string]: unknown };
|
|
27
|
-
const onlyKeys = (o: object, allowed: string[]) => Object.keys(o).every((k) => allowed.includes(k));
|
|
28
|
-
const emptyObject = (v: unknown) => v === undefined || (!!v && typeof v === "object" && Object.keys(v as object).length === 0);
|
|
29
|
-
|
|
30
|
-
/** The paragraph styling a PLAIN paragraph still carries on the Ricos wire (Events v3 sends
|
|
31
|
-
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). */
|
|
32
|
-
function defaultParagraphData(pd: unknown): boolean {
|
|
33
|
-
if (pd === undefined) return true;
|
|
34
|
-
if (!pd || typeof pd !== "object" || !onlyKeys(pd, ["textStyle", "indentation"])) return false;
|
|
35
|
-
const { textStyle, indentation } = pd as { textStyle?: unknown; indentation?: unknown };
|
|
36
|
-
if (indentation !== undefined && indentation !== 0) return false;
|
|
37
|
-
if (textStyle === undefined) return true;
|
|
38
|
-
if (!textStyle || typeof textStyle !== "object" || !onlyKeys(textStyle, ["textAlignment"])) return false;
|
|
39
|
-
const align = (textStyle as { textAlignment?: unknown }).textAlignment;
|
|
40
|
-
return align === undefined || align === "AUTO";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
19
|
/** True when a plain text field can round-trip this Ricos document: no document styling, and
|
|
44
|
-
* only paragraphs of undecorated text whose paragraph data is the wire default
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
&& onlyKeys(n, ["type", "id", "nodes", "paragraphData"]) && defaultParagraphData(n.paragraphData)
|
|
51
|
-
&& (Array.isArray(n.nodes) ? (n.nodes as RicosNode[]) : []).every((t) => !!t && typeof t === "object" && t.type === "TEXT"
|
|
52
|
-
&& onlyKeys(t, ["type", "id", "textData", "nodes"]) && (!Array.isArray(t.nodes) || t.nodes.length === 0)
|
|
53
|
-
&& !(Array.isArray(t.textData?.decorations) && t.textData.decorations.length)));
|
|
54
|
-
}
|
|
20
|
+
* only paragraphs of undecorated text whose paragraph data is the wire default (Events v3 sends
|
|
21
|
+
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). Anything else —
|
|
22
|
+
* a heading, list, link, bold run, media node, alignment or indent — is rich, and so is a missing
|
|
23
|
+
* document (callers decide what an absent body means). */
|
|
24
|
+
export const ricosIsPlain = (doc: { nodes?: unknown; documentStyle?: unknown } | null | undefined): boolean =>
|
|
25
|
+
!!doc && isPlainRicos(doc);
|
|
55
26
|
|
|
56
27
|
/** Preview text for a legacy DraftJS document: its `blocks[].text` joined by a blank line, or ""
|
|
57
28
|
* when the payload is not DraftJS JSON (never a throw). */
|
|
@@ -10,8 +10,8 @@ import { defineService } from "@himalaya/state";
|
|
|
10
10
|
import { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
|
|
11
11
|
import { normalizeEvent } from "../_shared/himi-events.js";
|
|
12
12
|
import { postBodyText, plainPostBody } from "../_shared/owner-post-content.js";
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// Events v3 sends default paragraphData on EVERY paragraph and an empty documentStyle; ricosIsPlain
|
|
14
|
+
// reads both as plain.
|
|
15
15
|
import { ricosIsPlain } from "../_shared/owner-rich-guard.js";
|
|
16
16
|
import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
|
|
17
17
|
import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import type { WixRichContent } from "@himalaya/wix-blog";
|
|
2
|
+
// The rich-content helpers. Inside the platform source this import is a relative path, so node test runs
|
|
3
|
+
// resolve it; an app's copy imports "@himalaya/wix-rich-content".
|
|
4
|
+
import { ricosToPlainText, isPlainRicos, plainTextToRicos } from "@himalaya/wix-rich-content";
|
|
2
5
|
import { wixMediaUrl, WIX_DETAIL_IMAGE } from "./himi-store.js";
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
// Thin wrappers keep the WixRichContent signatures callers compile against.
|
|
5
8
|
/** Join inline runs without adding paragraph breaks between bold/plain spans. */
|
|
6
|
-
export
|
|
7
|
-
const inline = (node: Node): string => typeof node.textData?.text === "string"
|
|
8
|
-
? node.textData.text : (node.nodes ?? []).map(inline).join("");
|
|
9
|
-
const blocks = (nodes: Node[]): string[] => nodes.flatMap(node =>
|
|
10
|
-
node.type === "PARAGRAPH" || node.type === "HEADING" || node.type === "TEXT"
|
|
11
|
-
? [inline(node)] : blocks(node.nodes ?? []));
|
|
12
|
-
return blocks((rich?.nodes ?? []) as Node[]).join("\n\n");
|
|
13
|
-
}
|
|
9
|
+
export const postBodyText = (rich: WixRichContent | null | undefined): string => ricosToPlainText(rich);
|
|
14
10
|
/** The plain editor must not overwrite formatting or any opaque media node.
|
|
15
11
|
* Rich drafts remain editable for title/excerpt/cover; body stays untouched. */
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
if (!raw || typeof raw !== "object") return true;
|
|
19
|
-
const node = raw as Node;
|
|
20
|
-
if (node.type !== "PARAGRAPH") return true;
|
|
21
|
-
if (Object.keys(node).some(key => !["type", "id", "nodes", "paragraphData"].includes(key))) return true;
|
|
22
|
-
if (node.paragraphData && Object.keys(node.paragraphData as object).length) return true;
|
|
23
|
-
return (node.nodes ?? []).some(child => child.type !== "TEXT"
|
|
24
|
-
|| !!child.textData?.decorations?.length
|
|
25
|
-
|| Object.keys(child).some(key => !["type", "id", "textData", "nodes"].includes(key)));
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function plainPostBody(body: string): WixRichContent {
|
|
29
|
-
return { nodes: body.split("\n\n").map(text => ({
|
|
30
|
-
type: "PARAGRAPH", nodes: [{ type: "TEXT", textData: { text, decorations: [] } }],
|
|
31
|
-
})) };
|
|
32
|
-
}
|
|
12
|
+
export const hasRichBody = (rich: WixRichContent | null | undefined): boolean => !isPlainRicos(rich);
|
|
13
|
+
export const plainPostBody = (body: string): WixRichContent => plainTextToRicos(body);
|
|
33
14
|
/** Current Blog media shape plus older coverImage projections. */
|
|
34
15
|
export function postCoverUrl(raw: Record<string, any> | null | undefined): string {
|
|
35
16
|
if (!raw) return "";
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// May a plain owner form round-trip this body? The Ricos and HTML checks are @himalaya/wix-rich-content's;
|
|
2
|
+
// this file adds the read-only previews the forms show for a locked body, and the FAQ's legacy DraftJS answer.
|
|
3
|
+
// Inside the platform source this import is a relative path, so node test runs resolve it; an app's copy
|
|
4
|
+
// imports "@himalaya/wix-rich-content".
|
|
5
|
+
import { htmlIsTrivial, isPlainRicos } from "@himalaya/wix-rich-content";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const TAG = /<[a-z!/?]/i;
|
|
7
|
-
|
|
8
|
-
/** True when a plain text field can hold this HTML without losing anything: empty, bare text
|
|
9
|
-
* (no tag — a lone `<` in prose is text), or exactly one attribute-less `<p>…</p>` with no
|
|
10
|
-
* inner tags. */
|
|
11
|
-
export function htmlIsTrivial(html: string | null | undefined): boolean {
|
|
12
|
-
const s = (html ?? "").trim();
|
|
13
|
-
return !s || !TAG.test(s) || /^<p>[^<>]*<\/p>$/.test(s);
|
|
14
|
-
}
|
|
7
|
+
export { htmlIsTrivial };
|
|
15
8
|
|
|
16
9
|
const ENTITIES: Record<string, string> = { " ": " ", "&": "&", "<": "<", ">": ">", """: "\"", "'": "'" };
|
|
17
10
|
|
|
@@ -23,35 +16,13 @@ export function htmlPreviewText(html: string | null | undefined): string {
|
|
|
23
16
|
.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
|
|
24
17
|
}
|
|
25
18
|
|
|
26
|
-
type RicosNode = { type?: unknown; nodes?: unknown; textData?: { decorations?: unknown }; paragraphData?: unknown; [key: string]: unknown };
|
|
27
|
-
const onlyKeys = (o: object, allowed: string[]) => Object.keys(o).every((k) => allowed.includes(k));
|
|
28
|
-
const emptyObject = (v: unknown) => v === undefined || (!!v && typeof v === "object" && Object.keys(v as object).length === 0);
|
|
29
|
-
|
|
30
|
-
/** The paragraph styling a PLAIN paragraph still carries on the Ricos wire (Events v3 sends
|
|
31
|
-
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). */
|
|
32
|
-
function defaultParagraphData(pd: unknown): boolean {
|
|
33
|
-
if (pd === undefined) return true;
|
|
34
|
-
if (!pd || typeof pd !== "object" || !onlyKeys(pd, ["textStyle", "indentation"])) return false;
|
|
35
|
-
const { textStyle, indentation } = pd as { textStyle?: unknown; indentation?: unknown };
|
|
36
|
-
if (indentation !== undefined && indentation !== 0) return false;
|
|
37
|
-
if (textStyle === undefined) return true;
|
|
38
|
-
if (!textStyle || typeof textStyle !== "object" || !onlyKeys(textStyle, ["textAlignment"])) return false;
|
|
39
|
-
const align = (textStyle as { textAlignment?: unknown }).textAlignment;
|
|
40
|
-
return align === undefined || align === "AUTO";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
19
|
/** True when a plain text field can round-trip this Ricos document: no document styling, and
|
|
44
|
-
* only paragraphs of undecorated text whose paragraph data is the wire default
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
&& onlyKeys(n, ["type", "id", "nodes", "paragraphData"]) && defaultParagraphData(n.paragraphData)
|
|
51
|
-
&& (Array.isArray(n.nodes) ? (n.nodes as RicosNode[]) : []).every((t) => !!t && typeof t === "object" && t.type === "TEXT"
|
|
52
|
-
&& onlyKeys(t, ["type", "id", "textData", "nodes"]) && (!Array.isArray(t.nodes) || t.nodes.length === 0)
|
|
53
|
-
&& !(Array.isArray(t.textData?.decorations) && t.textData.decorations.length)));
|
|
54
|
-
}
|
|
20
|
+
* only paragraphs of undecorated text whose paragraph data is the wire default (Events v3 sends
|
|
21
|
+
* `{ textStyle: { textAlignment: "AUTO" }, indentation: 0 }` on every paragraph). Anything else —
|
|
22
|
+
* a heading, list, link, bold run, media node, alignment or indent — is rich, and so is a missing
|
|
23
|
+
* document (callers decide what an absent body means). */
|
|
24
|
+
export const ricosIsPlain = (doc: { nodes?: unknown; documentStyle?: unknown } | null | undefined): boolean =>
|
|
25
|
+
!!doc && isPlainRicos(doc);
|
|
55
26
|
|
|
56
27
|
/** Preview text for a legacy DraftJS document: its `blocks[].text` joined by a blank line, or ""
|
|
57
28
|
* when the payload is not DraftJS JSON (never a throw). */
|
|
@@ -10,8 +10,8 @@ import { defineService } from "@himalaya/state";
|
|
|
10
10
|
import { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
|
|
11
11
|
import { normalizeEvent } from "../_shared/himi-events.js";
|
|
12
12
|
import { postBodyText, plainPostBody } from "../_shared/owner-post-content.js";
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// Events v3 sends default paragraphData on EVERY paragraph and an empty documentStyle; ricosIsPlain
|
|
14
|
+
// reads both as plain.
|
|
15
15
|
import { ricosIsPlain } from "../_shared/owner-rich-guard.js";
|
|
16
16
|
import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
|
|
17
17
|
import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import type { WixRichContent } from "@himalaya/wix-blog";
|
|
2
|
+
// The rich-content helpers. Inside the platform source this import is a relative path, so node test runs
|
|
3
|
+
// resolve it; an app's copy imports "@himalaya/wix-rich-content".
|
|
4
|
+
import { ricosToPlainText, isPlainRicos, plainTextToRicos } from "@himalaya/wix-rich-content";
|
|
2
5
|
import { wixMediaUrl, WIX_DETAIL_IMAGE } from "./himi-store.js";
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
// Thin wrappers keep the WixRichContent signatures callers compile against.
|
|
5
8
|
/** Join inline runs without adding paragraph breaks between bold/plain spans. */
|
|
6
|
-
export
|
|
7
|
-
const inline = (node: Node): string => typeof node.textData?.text === "string"
|
|
8
|
-
? node.textData.text : (node.nodes ?? []).map(inline).join("");
|
|
9
|
-
const blocks = (nodes: Node[]): string[] => nodes.flatMap(node =>
|
|
10
|
-
node.type === "PARAGRAPH" || node.type === "HEADING" || node.type === "TEXT"
|
|
11
|
-
? [inline(node)] : blocks(node.nodes ?? []));
|
|
12
|
-
return blocks((rich?.nodes ?? []) as Node[]).join("\n\n");
|
|
13
|
-
}
|
|
9
|
+
export const postBodyText = (rich: WixRichContent | null | undefined): string => ricosToPlainText(rich);
|
|
14
10
|
/** The plain editor must not overwrite formatting or any opaque media node.
|
|
15
11
|
* Rich drafts remain editable for title/excerpt/cover; body stays untouched. */
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
if (!raw || typeof raw !== "object") return true;
|
|
19
|
-
const node = raw as Node;
|
|
20
|
-
if (node.type !== "PARAGRAPH") return true;
|
|
21
|
-
if (Object.keys(node).some(key => !["type", "id", "nodes", "paragraphData"].includes(key))) return true;
|
|
22
|
-
if (node.paragraphData && Object.keys(node.paragraphData as object).length) return true;
|
|
23
|
-
return (node.nodes ?? []).some(child => child.type !== "TEXT"
|
|
24
|
-
|| !!child.textData?.decorations?.length
|
|
25
|
-
|| Object.keys(child).some(key => !["type", "id", "textData", "nodes"].includes(key)));
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function plainPostBody(body: string): WixRichContent {
|
|
29
|
-
return { nodes: body.split("\n\n").map(text => ({
|
|
30
|
-
type: "PARAGRAPH", nodes: [{ type: "TEXT", textData: { text, decorations: [] } }],
|
|
31
|
-
})) };
|
|
32
|
-
}
|
|
12
|
+
export const hasRichBody = (rich: WixRichContent | null | undefined): boolean => !isPlainRicos(rich);
|
|
13
|
+
export const plainPostBody = (body: string): WixRichContent => plainTextToRicos(body);
|
|
33
14
|
/** Current Blog media shape plus older coverImage projections. */
|
|
34
15
|
export function postCoverUrl(raw: Record<string, any> | null | undefined): string {
|
|
35
16
|
if (!raw) return "";
|