@wix/himalaya-cli 0.1502.0 → 0.1504.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/cli.mjs +35 -29
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/splash-screens.md +4 -1
- package/package.json +2 -2
- package/templates/branded-lite/dev/index.ts +52 -2149
- package/templates/branded-lite/tier5-src/business-chat/index.ts +22 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-inbox/index.ts +0 -71
|
@@ -26,11 +26,26 @@ type State = {
|
|
|
26
26
|
liveState:string; liveReason:string; realtimeEventCount:number; lastRealtimeEventType:string; pendingIntent:PendingIntent|null; scrollAnchor:string; scrollSend:number; usedSuggestionIds:string[]; visitorOptions:BusinessVisitorOptions|null; quickReplies:BusinessSuggestion[]; hasQuickReplies:boolean; noQuickReplies:boolean;
|
|
27
27
|
liveLabel:string; hasActiveRoom:boolean; noActiveRoom:boolean; isGuest:boolean; notGuest:boolean; realtimeDiagnostic:string; noError:boolean; noNotice:boolean; noMessages:boolean; hasMessages:boolean; noEmpty:boolean; notLoading:boolean;
|
|
28
28
|
hostedName:string; hasHostedName:boolean; noHostedName:boolean;
|
|
29
|
+
/** Who signs business messages: the hosted site, else the name the screen's `init` passes. */
|
|
30
|
+
businessName:string; businessInitials:string;
|
|
29
31
|
sendDisabled:boolean; sendEnabled:boolean; noLive:boolean; loadingHidden:boolean; errorText:string; loadFailure:string;
|
|
30
32
|
};
|
|
31
33
|
// A hosted build serves many businesses: the title is the site the host opened, not the app's own name.
|
|
32
34
|
const hostedName=hostSiteFromInput((globalThis as {himalaya?:{input?:unknown}}).himalaya?.input)?.name??'';
|
|
33
|
-
|
|
35
|
+
/** One or two capitals for a name, as an avatar shows it ("Hush Studio" → "HS"). */
|
|
36
|
+
// What a reader sees as one character: an emoji joined from several code points (👩🍳, a flag) stays whole. Code
|
|
37
|
+
// points are the fallback where the engine has no Intl.Segmenter.
|
|
38
|
+
function graphemes(text:string):string[]{
|
|
39
|
+
// The wrist's engine has no Intl at all, so it is only read behind this check.
|
|
40
|
+
const Segmenter=typeof Intl!=='undefined'?(Intl as unknown as {Segmenter?:new(locale?:string,options?:{granularity:'grapheme'})=>{segment(input:string):Iterable<{segment:string}>}}).Segmenter:undefined;
|
|
41
|
+
return Segmenter?Array.from(new Segmenter(undefined,{granularity:'grapheme'}).segment(text),(part)=>part.segment):[...text];
|
|
42
|
+
}
|
|
43
|
+
function initialsOf(name:string):string{
|
|
44
|
+
const first=(word:string)=>graphemes(word)[0]??'';
|
|
45
|
+
const words=name.trim().split(/\s+/).filter(Boolean);
|
|
46
|
+
return (words.length>1?`${first(words[0]!)}${first(words[words.length-1]!)}`:first(words[0]??'')).toUpperCase();
|
|
47
|
+
}
|
|
48
|
+
const initial:State={hostedName,businessName:hostedName,businessInitials:initialsOf(hostedName),hasHostedName:!!hostedName,noHostedName:!hostedName,phase:'loading',error:'',notice:'',member:false,roomId:'',principal:'',scope:'',messages:[],draft:'',busy:false,
|
|
34
49
|
liveState:'unavailable',liveReason:'',realtimeEventCount:0,lastRealtimeEventType:'',liveLabel:'',hasActiveRoom:false,noActiveRoom:true,isGuest:false,notGuest:true,realtimeDiagnostic:'',pendingIntent:null,scrollAnchor:'',scrollSend:0,usedSuggestionIds:[],visitorOptions:null,quickReplies:[],hasQuickReplies:false,noQuickReplies:true,noError:true,noNotice:true,noMessages:true,hasMessages:false,noEmpty:true,notLoading:false,sendDisabled:true,sendEnabled:false,noLive:true,loadingHidden:false,errorText:'',loadFailure:''};
|
|
35
50
|
let currentIdentity:ChatIdentity|null=null;
|
|
36
51
|
let subscriptionId='';
|
|
@@ -309,6 +324,11 @@ const svc=defineService<State,Record<string,(s:State,a?:unknown)=>State|undefine
|
|
|
309
324
|
// A retry passes no site: the one the screen gave first stays. A site the screen passes, even an empty one (a
|
|
310
325
|
// business with no site), replaces it, so an earlier business's links are not routed to this one's screens.
|
|
311
326
|
if(hasParam(a,'siteUrl')){ownSiteUrl=param(a,'siteUrl');ownScreens.product=param(a,'productScreen');ownScreens.service=param(a,'serviceScreen');}
|
|
327
|
+
// A retry passes no name: the one the screen gave first stays. Under `displayName`, a copy key, so a template
|
|
328
|
+
// sweep that renames the business renames it here too and the initials follow.
|
|
329
|
+
const signedBy=s.hostedName||param(a,'displayName')||s.businessName;
|
|
330
|
+
const signed=(st:State):State=>({...st,businessName:signedBy,businessInitials:initialsOf(signedBy)});
|
|
331
|
+
s=signed(s);
|
|
312
332
|
const oldPrincipal=s.principal;
|
|
313
333
|
const initRevision=draftRevision;
|
|
314
334
|
// Text typed at any point of the load, subscribe step included, outlives the snapshot this started from. So does the
|
|
@@ -336,7 +356,7 @@ const svc=defineService<State,Record<string,(s:State,a?:unknown)=>State|undefine
|
|
|
336
356
|
const identity=await getCurrentMember();
|
|
337
357
|
if(myEpoch!==epoch)return undefined;
|
|
338
358
|
if(!identity.authenticated)throw new Error('member_sign_in_required');
|
|
339
|
-
if(oldPrincipal&&(oldPrincipal!==(identity.userId||identity.email)||s.scope!==identity.scope)){s=clearPrivate(s,'guest');myEpoch=epoch;reset=true;}
|
|
359
|
+
if(oldPrincipal&&(oldPrincipal!==(identity.userId||identity.email)||s.scope!==identity.scope)){s=signed(clearPrivate(s,'guest'));myEpoch=epoch;reset=true;}
|
|
340
360
|
currentIdentity=identity;
|
|
341
361
|
s=shape({...s,phase:'loading',member:true,principal:identity.userId||identity.email,scope:identity.scope,error:'',notice:''});
|
|
342
362
|
const existingRoom=await resolveExistingBusinessRoom(myEpoch);
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// himi-branded-lite-inbox — reads the visitor's real Wix Inbox conversations (listConversations).
|
|
2
|
-
// a failed query is an error with Retry (or Sign in when Wix refuses the
|
|
3
|
-
// visitor) and an empty one is a real empty state — never sample rows presented as
|
|
4
|
-
// the customer's own. Samples appear only in the host's explicit sample preview
|
|
5
|
-
// (_shared/branded-load-state.ts).
|
|
6
|
-
//
|
|
7
|
-
// Inbox exposes no list-messages endpoint (only listConversations + single getMessage),
|
|
8
|
-
// so this is a conversation LIST — same read-view scope as the Galleries/Forms verticals.
|
|
9
|
-
import { defineService } from "../_shared/collection-service.js";
|
|
10
|
-
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
11
|
-
import { listConversations, type WixConversation } from "@himalaya/wix-inbox";
|
|
12
|
-
|
|
13
|
-
interface Row { id: string; title: string; subtitle: string }
|
|
14
|
-
interface S extends LoadFlags {
|
|
15
|
-
conversations: Row[]; emptyText: string;
|
|
16
|
-
isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
|
|
17
|
-
* shows only when the site really has none, and the unavailable one only when the
|
|
18
|
-
* query failed. A failed query must never render the sentence that claims the
|
|
19
|
-
* customer has no content. */
|
|
20
|
-
notMockEmpty: boolean; notMockUnavailable: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// businessDisplayData / participantDisplayData are open maps; pull a human name from
|
|
24
|
-
// the usual keys without assuming a fixed shape.
|
|
25
|
-
function displayName(m?: Record<string, unknown>): string {
|
|
26
|
-
if (!m) return "";
|
|
27
|
-
for (const k of ["name", "displayName", "businessName", "nickname", "fullName"]) {
|
|
28
|
-
const v = m[k];
|
|
29
|
-
if (typeof v === "string" && v.trim()) return v.trim();
|
|
30
|
-
}
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function norm(c: WixConversation, i: number): Row {
|
|
35
|
-
const title = displayName(c.businessDisplayData) || displayName(c.participantDisplayData) || "Conversation";
|
|
36
|
-
const preview = (c.latestMessage?.previewText || c.summary || "").trim();
|
|
37
|
-
return { id: c.id || `conv-${i}`, title, subtitle: preview };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const MOCK: Row[] = [
|
|
41
|
-
{ id: "sample-orders", title: "Orders", subtitle: "Your order is on its way — track it here." },
|
|
42
|
-
{ id: "sample-support", title: "Support", subtitle: "Hi! Let us know how we can help." },
|
|
43
|
-
{ id: "sample-team", title: "The Team", subtitle: "Welcome 👋 Thanks for joining us!" },
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
/** sample rows reach the screen ONLY in the host's explicit sample preview;
|
|
47
|
-
* a failed or empty query is an honest error / empty state (branded-load-state.ts). */
|
|
48
|
-
const COPY: LoadCopy<Row> = {
|
|
49
|
-
prefix: "inbox",
|
|
50
|
-
empty: "No messages yet.",
|
|
51
|
-
error: "Your messages couldn't be loaded.",
|
|
52
|
-
signIn: "Sign in to see your messages.",
|
|
53
|
-
sample: () => MOCK.map((r) => ({ ...r })),
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedInbox", {
|
|
57
|
-
state: { ...LOADING, conversations: [], emptyText: "" },
|
|
58
|
-
actions: {
|
|
59
|
-
load: async (s) => {
|
|
60
|
-
try {
|
|
61
|
-
const r = await listConversations({ paging: { limit: 50 } });
|
|
62
|
-
const conversations = (r.conversations ?? []).map(norm);
|
|
63
|
-
return { ...s, ...loaded("conversations", conversations, COPY) };
|
|
64
|
-
} catch (err) {
|
|
65
|
-
return { ...s, ...loadFailed("conversations", err, COPY) };
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
svc.load();
|
|
71
|
-
export { svc };
|