@wix/himalaya-cli 0.1573.0 → 0.1575.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/app-config.d.ts +16 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +6 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/message-format.d.ts +59 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/index.d.ts +15 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/oneapp.d.ts +90 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +3 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/l10n.mjs +8 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +3 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bi.mjs +165 -5
- package/dist/cli.mjs +5261 -2937
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +7 -1
- package/guides/hhp.md +2 -2
- package/guides/himi-json-output.md +53 -2
- package/guides/himi-mcp.md +1 -1
- package/guides/himi-test.md +3 -2
- package/guides/iap-products.md +1 -1
- package/guides/importing-oneapp-translations.md +115 -0
- package/guides/index.json +6 -0
- package/guides/one-app-section-mappings.md +16 -4
- package/guides/one-app-to-himi-app.md +167 -10
- package/guides/push-cli-onboarding.md +10 -0
- package/guides/qa-builds.md +21 -1
- package/guides/templates.md +3 -2
- package/guides/testing-himi-apps.md +1 -1
- package/package.json +3 -3
- package/templates/beauty-salon/dev/index.ts +96 -133
- package/templates/beauty-salon/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/beauty-salon-owner/dev/index.ts +27 -6
- package/templates/beauty-salon-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/branded-lite/dev/index.ts +136 -142
- package/templates/branded-lite/l10n/en.json +570 -0
- package/templates/branded-lite/l10n/he.json +200 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-copy.ts +40 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-message-model.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/business-chat-model.ts +3 -2
- package/templates/branded-lite/tier5-src/_shared/business-chat-presenter.ts +26 -21
- package/templates/branded-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/branded-lite/tier5-src/business-chat/index.ts +184 -63
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/community-club/dev/index.ts +22 -6
- package/templates/community-club-owner/dev/index.ts +27 -6
- package/templates/community-club-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/events-meetups/dev/index.ts +22 -6
- package/templates/events-meetups-owner/dev/index.ts +25 -6
- package/templates/events-meetups-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/gym-fitness/dev/index.ts +96 -133
- package/templates/gym-fitness/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/gym-fitness-owner/dev/index.ts +28 -6
- package/templates/gym-fitness-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/himi-chat/dev/index.ts +22 -6
- package/templates/nonprofit-cause/dev/index.ts +22 -6
- package/templates/nonprofit-cause-owner/dev/index.ts +26 -6
- package/templates/nonprofit-cause-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/owner-lite/dev/index.ts +28 -6
- package/templates/owner-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/podcast-creator/dev/index.ts +22 -6
- package/templates/podcast-creator-owner/dev/index.ts +27 -6
- package/templates/podcast-creator-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/portfolio-creative/dev/index.ts +96 -133
- package/templates/portfolio-creative/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/portfolio-creative-owner/dev/index.ts +27 -6
- package/templates/portfolio-creative-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/restaurant/dev/index.ts +22 -6
- package/templates/restaurant-owner/dev/index.ts +28 -6
- package/templates/restaurant-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/retail-store/dev/index.ts +96 -133
- package/templates/retail-store/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/retail-store-owner/dev/index.ts +26 -6
- package/templates/retail-store-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/surface-showcase/dev/index.ts +22 -6
- package/templates/yoga-studio/dev/index.ts +22 -6
- package/templates/yoga-studio-owner/dev/index.ts +26 -6
- package/templates/yoga-studio-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$source": "he",
|
|
3
|
+
"messages": {
|
|
4
|
+
"h_c04921f8d946": {
|
|
5
|
+
"source": "No messages yet",
|
|
6
|
+
"key": "businessChat.emptyTitle",
|
|
7
|
+
"translation": "עדיין אין הודעות"
|
|
8
|
+
},
|
|
9
|
+
"h_cf765512cc6d": {
|
|
10
|
+
"source": "Sending…",
|
|
11
|
+
"key": "businessChat.sending",
|
|
12
|
+
"translation": "שולח…"
|
|
13
|
+
},
|
|
14
|
+
"h_b9e09ae263fb": {
|
|
15
|
+
"source": "Not confirmed",
|
|
16
|
+
"key": "businessChat.unconfirmed",
|
|
17
|
+
"translation": "לא אושר"
|
|
18
|
+
},
|
|
19
|
+
"h_9443bda693df": {
|
|
20
|
+
"source": "Your member session changed. Sign in again to continue.",
|
|
21
|
+
"key": "businessChat.sessionChanged",
|
|
22
|
+
"translation": "ההתחברות שלך השתנתה. יש להתחבר שוב כדי להמשיך."
|
|
23
|
+
},
|
|
24
|
+
"h_aa36b83daad9": {
|
|
25
|
+
"source": "This app host cannot reach the inbox yet.",
|
|
26
|
+
"key": "businessChat.hostUnavailable",
|
|
27
|
+
"translation": "לא ניתן לגשת לתיבת ההודעות באפליקציה זו כרגע."
|
|
28
|
+
},
|
|
29
|
+
"h_e9b3cc0ff1a1": {
|
|
30
|
+
"source": "Message delivery could not be confirmed. Check the conversation before trying again.",
|
|
31
|
+
"key": "businessChat.sendFailed",
|
|
32
|
+
"translation": "לא ניתן לאשר שההודעה נמסרה. יש לבדוק את השיחה לפני ניסיון נוסף."
|
|
33
|
+
},
|
|
34
|
+
"h_9a5009643173": {
|
|
35
|
+
"source": "Couldn’t open the conversation. Try again.",
|
|
36
|
+
"key": "businessChat.openFailed",
|
|
37
|
+
"translation": "לא ניתן לפתוח את השיחה. נסו שוב."
|
|
38
|
+
},
|
|
39
|
+
"h_a8e08e79182b": {
|
|
40
|
+
"source": "Couldn’t load the conversation. Try again.",
|
|
41
|
+
"key": "businessChat.loadFailed",
|
|
42
|
+
"translation": "לא ניתן לטעון את השיחה. נסו שוב."
|
|
43
|
+
},
|
|
44
|
+
"h_c21b9303d41d": {
|
|
45
|
+
"source": "Sign in to send a message.",
|
|
46
|
+
"key": "businessChat.signInPrompt",
|
|
47
|
+
"translation": "יש להתחבר כדי לשלוח הודעה."
|
|
48
|
+
},
|
|
49
|
+
"h_c559b26ddd1a": {
|
|
50
|
+
"source": "The message was rejected. Your draft is retained.",
|
|
51
|
+
"key": "businessChat.rejected",
|
|
52
|
+
"translation": "ההודעה נדחתה. הטיוטה נשמרה."
|
|
53
|
+
},
|
|
54
|
+
"h_263adf949cca": {
|
|
55
|
+
"source": "No automatic retry was made. Reopen the conversation to check whether it arrived.",
|
|
56
|
+
"key": "businessChat.checkDelivery",
|
|
57
|
+
"translation": "לא בוצע ניסיון שליחה נוסף. פתחו שוב את השיחה כדי לבדוק אם ההודעה התקבלה."
|
|
58
|
+
},
|
|
59
|
+
"h_6df910132a36": {
|
|
60
|
+
"source": "Suggestion sent.",
|
|
61
|
+
"key": "businessChat.suggestionSent",
|
|
62
|
+
"translation": "ההצעה נשלחה."
|
|
63
|
+
},
|
|
64
|
+
"h_429255ed7d01": {
|
|
65
|
+
"source": "The suggestion was rejected.",
|
|
66
|
+
"key": "businessChat.suggestionRejected",
|
|
67
|
+
"translation": "ההצעה נדחתה."
|
|
68
|
+
},
|
|
69
|
+
"h_093341bec739": {
|
|
70
|
+
"source": "Suggestion delivery is unconfirmed; it will not be sent again automatically.",
|
|
71
|
+
"key": "businessChat.suggestionUnconfirmed",
|
|
72
|
+
"translation": "לא ניתן לאשר שההצעה נמסרה; היא לא תישלח שוב באופן אוטומטי."
|
|
73
|
+
},
|
|
74
|
+
"h_74c459c38758": {
|
|
75
|
+
"source": "Product image",
|
|
76
|
+
"key": "businessChat.productImage",
|
|
77
|
+
"translation": "תמונת מוצר"
|
|
78
|
+
},
|
|
79
|
+
"h_325fcc2da726": {
|
|
80
|
+
"source": "Connecting to live updates",
|
|
81
|
+
"key": "businessChat.connecting",
|
|
82
|
+
"translation": "מתחבר לעדכונים בזמן אמת"
|
|
83
|
+
},
|
|
84
|
+
"h_c23ff9c74e73": {
|
|
85
|
+
"source": "Reconnecting live updates",
|
|
86
|
+
"key": "businessChat.reconnecting",
|
|
87
|
+
"translation": "מתחבר מחדש לעדכונים בזמן אמת"
|
|
88
|
+
},
|
|
89
|
+
"h_f4531c1fb40f": {
|
|
90
|
+
"source": "Live updates unavailable for this session",
|
|
91
|
+
"key": "businessChat.liveSessionUnavailable",
|
|
92
|
+
"translation": "עדכונים בזמן אמת אינם זמינים בחיבור זה"
|
|
93
|
+
},
|
|
94
|
+
"h_42856378cbb8": {
|
|
95
|
+
"source": "Live updates unavailable in this host",
|
|
96
|
+
"key": "businessChat.liveHostUnavailable",
|
|
97
|
+
"translation": "עדכונים בזמן אמת אינם זמינים באפליקציה זו"
|
|
98
|
+
},
|
|
99
|
+
"h_2ef475c9eed8": {
|
|
100
|
+
"source": "Live connection setup failed",
|
|
101
|
+
"key": "businessChat.liveSetupFailed",
|
|
102
|
+
"translation": "הגדרת החיבור בזמן אמת נכשלה"
|
|
103
|
+
},
|
|
104
|
+
"h_40a99786c549": {
|
|
105
|
+
"source": "Live connection authentication failed",
|
|
106
|
+
"key": "businessChat.liveAuthFailed",
|
|
107
|
+
"translation": "אימות החיבור בזמן אמת נכשל"
|
|
108
|
+
},
|
|
109
|
+
"h_e601a70aa6d5": {
|
|
110
|
+
"source": "Live connection interrupted",
|
|
111
|
+
"key": "businessChat.liveInterrupted",
|
|
112
|
+
"translation": "החיבור בזמן אמת נותק"
|
|
113
|
+
},
|
|
114
|
+
"h_4e2c6bddd5ef": {
|
|
115
|
+
"source": "Live update could not be read",
|
|
116
|
+
"key": "businessChat.liveUnreadable",
|
|
117
|
+
"translation": "לא ניתן לקרוא את העדכון בזמן אמת"
|
|
118
|
+
},
|
|
119
|
+
"h_a7ada329317d": {
|
|
120
|
+
"source": "Live updates paused",
|
|
121
|
+
"key": "businessChat.livePaused",
|
|
122
|
+
"translation": "העדכונים בזמן אמת מושהים"
|
|
123
|
+
},
|
|
124
|
+
"h_45d7c3e03a77": {
|
|
125
|
+
"source": "Live updates unavailable",
|
|
126
|
+
"key": "businessChat.liveUnavailable",
|
|
127
|
+
"translation": "עדכונים בזמן אמת אינם זמינים"
|
|
128
|
+
},
|
|
129
|
+
"h_d2de1a2831e7": {
|
|
130
|
+
"source": "Open link",
|
|
131
|
+
"key": "businessChat.openLink",
|
|
132
|
+
"translation": "פתיחת קישור"
|
|
133
|
+
},
|
|
134
|
+
"h_24345a14377f": {
|
|
135
|
+
"source": "Today",
|
|
136
|
+
"key": "businessChat.today",
|
|
137
|
+
"translation": "היום"
|
|
138
|
+
},
|
|
139
|
+
"h_da24830f1f70": {
|
|
140
|
+
"source": "Yesterday",
|
|
141
|
+
"key": "businessChat.yesterday",
|
|
142
|
+
"translation": "אתמול"
|
|
143
|
+
},
|
|
144
|
+
"h_fc7ba35433ce": {
|
|
145
|
+
"source": "AI assistant",
|
|
146
|
+
"key": "businessChat.aiAssistant",
|
|
147
|
+
"translation": "עוזר בינה מלאכותית"
|
|
148
|
+
},
|
|
149
|
+
"h_798ec37b0270": {
|
|
150
|
+
"source": "Staff Response",
|
|
151
|
+
"key": "businessChat.staffResponse",
|
|
152
|
+
"translation": "תשובת הצוות"
|
|
153
|
+
},
|
|
154
|
+
"h_4f3117c17b1a": {
|
|
155
|
+
"source": "Image attachment",
|
|
156
|
+
"key": "businessChat.imageAttachment",
|
|
157
|
+
"translation": "תמונה מצורפת"
|
|
158
|
+
},
|
|
159
|
+
"h_6733d4f14f4d": {
|
|
160
|
+
"source": "Type your message…",
|
|
161
|
+
"key": "chatThread.composerPlaceholder",
|
|
162
|
+
"translation": "כתיבת הודעה…"
|
|
163
|
+
},
|
|
164
|
+
"h_08d738ca2b26": {
|
|
165
|
+
"source": "Type your message",
|
|
166
|
+
"key": "chatThread.composerLabel",
|
|
167
|
+
"translation": "לכתוב הודעה"
|
|
168
|
+
},
|
|
169
|
+
"h_9bc2575c3930": {
|
|
170
|
+
"source": "Send",
|
|
171
|
+
"key": "chatThread.sendCta",
|
|
172
|
+
"translation": "שליחה"
|
|
173
|
+
},
|
|
174
|
+
"h_8b2c12c5d4d5": {
|
|
175
|
+
"source": "Send a message to start the conversation.",
|
|
176
|
+
"key": "chatThread.emptySubtitle",
|
|
177
|
+
"translation": "שליחת הודעה תתחיל את השיחה."
|
|
178
|
+
},
|
|
179
|
+
"h_b227f5879bde": {
|
|
180
|
+
"source": "Coupon code",
|
|
181
|
+
"key": "chatThread.couponLabel",
|
|
182
|
+
"translation": "קוד קופון"
|
|
183
|
+
},
|
|
184
|
+
"h_33ce417454bf": {
|
|
185
|
+
"source": "Loading…",
|
|
186
|
+
"key": "common.detailLoadingText",
|
|
187
|
+
"translation": "בטעינה…"
|
|
188
|
+
},
|
|
189
|
+
"h_042c862e4467": {
|
|
190
|
+
"source": "Try again",
|
|
191
|
+
"key": "common.retryCta",
|
|
192
|
+
"translation": "נסו שוב"
|
|
193
|
+
},
|
|
194
|
+
"h_ada2e9e96fa9": {
|
|
195
|
+
"source": "Sign in",
|
|
196
|
+
"key": "account.signInCta",
|
|
197
|
+
"translation": "התחברות"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Shared defaults keep descriptor copy and worker fallbacks aligned.
|
|
2
|
+
import { copyParam } from "./himi-copy.js";
|
|
3
|
+
|
|
4
|
+
export const BUSINESS_CHAT_COPY = {
|
|
5
|
+
"emptyTitle": "No messages yet",
|
|
6
|
+
"sending": "Sending…",
|
|
7
|
+
"unconfirmed": "Not confirmed",
|
|
8
|
+
"sessionChanged": "Your member session changed. Sign in again to continue.",
|
|
9
|
+
"hostUnavailable": "This app host cannot reach the inbox yet.",
|
|
10
|
+
"sendFailed": "Message delivery could not be confirmed. Check the conversation before trying again.",
|
|
11
|
+
"openFailed": "Couldn’t open the conversation. Try again.",
|
|
12
|
+
"loadFailed": "Couldn’t load the conversation. Try again.",
|
|
13
|
+
"signInPrompt": "Sign in to send a message.",
|
|
14
|
+
"rejected": "The message was rejected. Your draft is retained.",
|
|
15
|
+
"checkDelivery": "No automatic retry was made. Reopen the conversation to check whether it arrived.",
|
|
16
|
+
"suggestionSent": "Suggestion sent.",
|
|
17
|
+
"suggestionRejected": "The suggestion was rejected.",
|
|
18
|
+
"suggestionUnconfirmed": "Suggestion delivery is unconfirmed; it will not be sent again automatically.",
|
|
19
|
+
"productImage": "Product image",
|
|
20
|
+
"connecting": "Connecting to live updates",
|
|
21
|
+
"reconnecting": "Reconnecting live updates",
|
|
22
|
+
"liveSessionUnavailable": "Live updates unavailable for this session",
|
|
23
|
+
"liveHostUnavailable": "Live updates unavailable in this host",
|
|
24
|
+
"liveSetupFailed": "Live connection setup failed",
|
|
25
|
+
"liveAuthFailed": "Live connection authentication failed",
|
|
26
|
+
"liveInterrupted": "Live connection interrupted",
|
|
27
|
+
"liveUnreadable": "Live update could not be read",
|
|
28
|
+
"livePaused": "Live updates paused",
|
|
29
|
+
"liveUnavailable": "Live updates unavailable",
|
|
30
|
+
"openLink": "Open link",
|
|
31
|
+
"today": "Today",
|
|
32
|
+
"yesterday": "Yesterday",
|
|
33
|
+
"aiAssistant": "AI assistant",
|
|
34
|
+
"staffResponse": "Staff Response",
|
|
35
|
+
"imageAttachment": "Image attachment"
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export function businessChatCopy(key: keyof typeof BUSINESS_CHAT_COPY): string {
|
|
39
|
+
return copyParam(`businessChat.${key}`, BUSINESS_CHAT_COPY[key]);
|
|
40
|
+
}
|
|
@@ -57,7 +57,7 @@ function media(id: string, kind: 'image' | 'file' | 'audio' | 'video', input: un
|
|
|
57
57
|
const url = safeUrl(candidate); const mime = first(m.mimeType, m.contentType);
|
|
58
58
|
const resolvedKind = kind === 'file' && /^audio\//i.test(mime) ? 'audio' : kind === 'file' && /^video\//i.test(mime) ? 'video' : kind;
|
|
59
59
|
const missing = url ? '' : candidate ? 'Attachment URL is unavailable: only safe HTTPS links are supported.' : 'Attachment requires a download URL.';
|
|
60
|
-
return { ...base(id, resolvedKind, first(m.text, m.title)), url, fileName: first(m.filename, m.fileName, m.name), caption: first(m.caption, m.alt, missing
|
|
60
|
+
return { ...base(id, resolvedKind, first(m.text, m.title)), url, fileName: first(m.filename, m.fileName, m.name), caption: first(m.caption, m.alt, missing), mimeType: mime, mediaId: first(m.mediaFileId, m.id, m._id, m.fileId), mediaProvider: str(m.mediaProvider), thumbnailUrl: safeUrl(m.thumbnailUrl), urlExpiration: str(m.urlExpiration), ...(missing ? { unavailableReason: missing } : {}) };
|
|
61
61
|
}
|
|
62
62
|
function buttons(value: unknown, prefix: string): MessageButton[] {
|
|
63
63
|
if (!Array.isArray(value)) return [];
|
|
@@ -5,8 +5,9 @@ import { mergeMessages, normalizeMessages, type MessageRow } from './business-ch
|
|
|
5
5
|
* time) or unconfirmed (the request failed without a definite answer). The server names the stored message, so the
|
|
6
6
|
* new one is recognised by its text, among the messages after `afterId` (the newest stored message on screen when it
|
|
7
7
|
* was sent), or among all of them when the room was loaded and empty (`fromStart`); `priorCount` is how many earlier
|
|
8
|
-
* sends with the same text were still waiting for their stored copy.
|
|
9
|
-
|
|
8
|
+
* sends with the same text were still waiting for their stored copy. `smartChatFromStart` records a successful empty
|
|
9
|
+
* Smart Chat read before sending from Members history; it selects that anchor only for accepted delivery viewed in Smart Chat history. */
|
|
10
|
+
export interface BusinessChatIntent { id:string; text:string; roomId:string; principal:string; createdDate:string; delivery:'sending'|'sent'|'unconfirmed'; senderName?:string; storedId?:string; afterId?:string; fromStart?:boolean; priorCount?:number; smartChatFromStart?:boolean }
|
|
10
11
|
|
|
11
12
|
export function newBusinessChatIntent(text:string,roomId:string,principal:string,random=Math.random):BusinessChatIntent{
|
|
12
13
|
return {id:'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,c=>{const r=Math.floor(random()*16);return(c==='x'?r:(r&3)|8).toString(16);}),text,roomId,principal,createdDate:new Date().toISOString(),delivery:'sending'};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { businessChatCopy } from "./business-chat-copy.js";
|
|
1
2
|
import { formatMonthDay } from "./engine-intl.js";
|
|
2
3
|
type RecordValue = Record<string, unknown>;
|
|
3
4
|
|
|
@@ -59,16 +60,16 @@ export type BusinessRoomSummary = {
|
|
|
59
60
|
/** Keep host capability failures visible without exposing runtime error details. */
|
|
60
61
|
export function businessRealtimeStatusLabel(state: string, reason = ""): string {
|
|
61
62
|
if (state === "connected") return "";
|
|
62
|
-
if (state === "connecting") return
|
|
63
|
-
if (state === "reconnecting") return
|
|
64
|
-
if (reason === "chat_setup_scope_unavailable") return
|
|
65
|
-
if (reason === "runtime_unavailable") return
|
|
66
|
-
if (reason === "invalid_setup_response") return
|
|
67
|
-
if (reason === "firebase_auth_failed") return
|
|
68
|
-
if (reason === "firebase_connection_failed") return
|
|
69
|
-
if (reason === "invalid_event") return
|
|
70
|
-
if (reason === "event_overflow") return
|
|
71
|
-
return
|
|
63
|
+
if (state === "connecting") return businessChatCopy('connecting');
|
|
64
|
+
if (state === "reconnecting") return businessChatCopy('reconnecting');
|
|
65
|
+
if (reason === "chat_setup_scope_unavailable") return businessChatCopy('liveSessionUnavailable');
|
|
66
|
+
if (reason === "runtime_unavailable") return businessChatCopy('liveHostUnavailable');
|
|
67
|
+
if (reason === "invalid_setup_response") return businessChatCopy('liveSetupFailed');
|
|
68
|
+
if (reason === "firebase_auth_failed") return businessChatCopy('liveAuthFailed');
|
|
69
|
+
if (reason === "firebase_connection_failed") return businessChatCopy('liveInterrupted');
|
|
70
|
+
if (reason === "invalid_event") return businessChatCopy('liveUnreadable');
|
|
71
|
+
if (reason === "event_overflow") return businessChatCopy('livePaused');
|
|
72
|
+
return businessChatCopy('liveUnavailable');
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
const object = (value: unknown): RecordValue =>
|
|
@@ -76,7 +77,7 @@ const object = (value: unknown): RecordValue =>
|
|
|
76
77
|
|
|
77
78
|
/** Return a sole existing two-party business room, or a unique business-name match among several;
|
|
78
79
|
* never create a room or guess among remaining matches. */
|
|
79
|
-
export function selectExistingBusinessRoom(rooms: unknown[], businessName?: string): { id: string; participantId: string } | undefined {
|
|
80
|
+
export function selectExistingBusinessRoom(rooms: unknown[], businessName?: string, requireUnambiguous = false): { id: string; participantId: string } | undefined {
|
|
80
81
|
let candidates = rooms.filter((value) => {
|
|
81
82
|
const room = object(value);
|
|
82
83
|
return typeof room.id === "string" && !!room.id &&
|
|
@@ -84,7 +85,8 @@ export function selectExistingBusinessRoom(rooms: unknown[], businessName?: stri
|
|
|
84
85
|
room.participantCount === 2 && room.clientReferenceType === "contactId" &&
|
|
85
86
|
Object.keys(object(room.businessContext)).length > 0;
|
|
86
87
|
});
|
|
87
|
-
|
|
88
|
+
const candidateCount = candidates.length;
|
|
89
|
+
if (candidateCount > 1 && businessName?.trim()) {
|
|
88
90
|
// Keep case significant so differently named businesses are not conflated.
|
|
89
91
|
const name = businessName.trim();
|
|
90
92
|
candidates = candidates.filter((value) => {
|
|
@@ -92,7 +94,10 @@ export function selectExistingBusinessRoom(rooms: unknown[], businessName?: stri
|
|
|
92
94
|
return typeof roomName === "string" && roomName.trim() === name;
|
|
93
95
|
});
|
|
94
96
|
}
|
|
95
|
-
if (candidates.length !== 1)
|
|
97
|
+
if (candidates.length !== 1) {
|
|
98
|
+
if (requireUnambiguous && candidateCount > 1) throw new Error("business_room_selection_ambiguous");
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
96
101
|
const room = object(candidates[0]);
|
|
97
102
|
return { id: room.id as string, participantId: room.participantId as string };
|
|
98
103
|
}
|
|
@@ -163,7 +168,7 @@ export function businessVisitorOptionSuggestions(options: BusinessVisitorOptions
|
|
|
163
168
|
/** Wix Chat realtime currently sends `{type, timestamp, summary, message?}` events. */
|
|
164
169
|
export function isBusinessMessageEvent(envelope: unknown, roomId: string, smartChat = false): boolean {
|
|
165
170
|
const event = object(object(envelope).event);
|
|
166
|
-
if (!roomId || typeof event.type !== "string" || !/^(message|messagesent)$/i.test(event.type)) return false;
|
|
171
|
+
if ((!roomId && !smartChat) || typeof event.type !== "string" || !/^(message|messagesent)$/i.test(event.type)) return false;
|
|
167
172
|
const message = object(event.message);
|
|
168
173
|
const data = object(event.data);
|
|
169
174
|
const nested = object(data.message);
|
|
@@ -180,7 +185,7 @@ export function shouldRefreshBusinessHistory(
|
|
|
180
185
|
session: { member: boolean; principal: string; currentPrincipal: string; scope: string; currentScope: string },
|
|
181
186
|
smartChat = false,
|
|
182
187
|
): boolean {
|
|
183
|
-
return !!session.member && !!roomId && !!session.principal && session.principal === session.currentPrincipal &&
|
|
188
|
+
return !!session.member && (!!roomId || smartChat) && !!session.principal && session.principal === session.currentPrincipal &&
|
|
184
189
|
session.scope === session.currentScope && isBusinessMessageEvent(envelope, roomId, smartChat);
|
|
185
190
|
}
|
|
186
191
|
|
|
@@ -364,7 +369,7 @@ export function extractBusinessMessageExtras(markdown: string, options: { liftCo
|
|
|
364
369
|
// True only when a button was really added: a repeated link, or one past the sixth, stays in the text.
|
|
365
370
|
const addLink = (label: string, url: string): boolean => {
|
|
366
371
|
if (links.length >= 6 || links.some((link) => link.url === url)) return false;
|
|
367
|
-
links.push({ label: label.replace(/[*_`]/g, "").trim().slice(0, 40) ||
|
|
372
|
+
links.push({ label: label.replace(/[*_`]/g, "").trim().slice(0, 40) || businessChatCopy('openLink'), url });
|
|
368
373
|
return true;
|
|
369
374
|
};
|
|
370
375
|
for (const line of markdown.split("\n")) {
|
|
@@ -454,7 +459,7 @@ export function presentBusinessMessages<T extends Pick<BusinessMessage, "id" | "
|
|
|
454
459
|
const key = Number.isFinite(timestamp.getTime()) ? dateKey(timestamp) : "";
|
|
455
460
|
const startsDay = !!key && key !== previousDay;
|
|
456
461
|
const dayLabel = startsDay
|
|
457
|
-
? key === today ?
|
|
462
|
+
? key === today ? businessChatCopy('today') : key === yesterdayKey ? businessChatCopy('yesterday') : formatMonthDay(timestamp)
|
|
458
463
|
: "";
|
|
459
464
|
if (key) previousDay = key;
|
|
460
465
|
|
|
@@ -523,10 +528,10 @@ export function presentBusinessMessages<T extends Pick<BusinessMessage, "id" | "
|
|
|
523
528
|
senderAvatar,
|
|
524
529
|
senderName,
|
|
525
530
|
senderInitials: typeof display.initials === "string" && display.initials.trim() ? display.initials.trim() : initials(senderName),
|
|
526
|
-
senderBadge: participantId && !mine ? isAiGenerated ?
|
|
531
|
+
senderBadge: participantId && !mine ? isAiGenerated ? businessChatCopy('aiAssistant') : businessChatCopy('staffResponse') : "",
|
|
527
532
|
imageUrl,
|
|
528
533
|
// A card's own picture is named by the card (a product's name), not as an anonymous attachment.
|
|
529
|
-
imageDescription: imagePart?.caption || (imageUrl && !imagePart?.url && first ? first.replace(/\*/g, "") :
|
|
534
|
+
imageDescription: imagePart?.caption || (imageUrl && !imagePart?.url && first ? first.replace(/\*/g, "") : businessChatCopy('imageAttachment')),
|
|
530
535
|
noImage: !imageUrl,
|
|
531
536
|
bodyText,
|
|
532
537
|
bodyRuns,
|
|
@@ -568,9 +573,9 @@ export async function dispatchBusinessSuggestion<T extends { suggestions?: Busin
|
|
|
568
573
|
session: { member: boolean; roomId: string; principal: string; currentPrincipal: string; scope: string; currentScope: string },
|
|
569
574
|
submit: (suggestion: BusinessSuggestion) => Promise<unknown>,
|
|
570
575
|
): Promise<boolean> {
|
|
571
|
-
if (!session.member || !session.
|
|
576
|
+
if (!session.member || !session.principal || session.principal !== session.currentPrincipal || session.scope !== session.currentScope) return false;
|
|
572
577
|
const suggestion = findBusinessSuggestion(rows, suggestionId);
|
|
573
|
-
if (!suggestion) return false;
|
|
578
|
+
if (!suggestion || (suggestion.kind === "postback" && !session.roomId)) return false;
|
|
574
579
|
// A submit that reports `false` (a stale quick reply nobody posted) is not a send.
|
|
575
580
|
return (await submit(suggestion)) !== false;
|
|
576
581
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
/** Reject repeated taps until the accepted write's result is committed. A promise
|
|
4
|
+
* queue alone releases before the host applies the reducer result, so the next
|
|
5
|
+
* write can still read an old revision/id. The private synchronous reducer below
|
|
6
|
+
* publishes busy and commits completion through the ordinary service handle;
|
|
7
|
+
* awaiting that handle guarantees the host has stored the result before unlock.
|
|
8
|
+
* Skipped actions return undefined, so they cannot roll back the accepted write.
|
|
9
|
+
*/
|
|
10
|
+
export function withExclusiveWrites<S extends object = Record<string, unknown>>(
|
|
11
|
+
registerService: typeof register, names: readonly string[],
|
|
12
|
+
options: { prepare?: (action: string, args: readonly unknown[]) => void; coalesce?: readonly string[]; settle?: (before: S, next: S, live: S) => S } = {},
|
|
13
|
+
): typeof register {
|
|
14
|
+
return ((name: string, definition: ServiceDefinition<Record<string, unknown>>) => {
|
|
15
|
+
let busy = false;
|
|
16
|
+
let activeAction = "";
|
|
17
|
+
let completion = Promise.resolve();
|
|
18
|
+
let complete = () => {};
|
|
19
|
+
let handle: { getState(): Record<string, unknown>; exclusiveWriteState(next: Record<string, unknown>): Promise<unknown> };
|
|
20
|
+
const wrapped: ServiceDefinition<Record<string, unknown>> = {
|
|
21
|
+
...definition,
|
|
22
|
+
state: { ...definition.state, writeBusy: false },
|
|
23
|
+
notHydrated: [...(definition.notHydrated ?? []), 'writeBusy'],
|
|
24
|
+
actions: {
|
|
25
|
+
...Object.fromEntries(Object.entries(definition.actions ?? {}).map(([action, reduce]) => [action,
|
|
26
|
+
names.includes(action) ? async (state: Record<string, unknown>, ...args: never[]) => {
|
|
27
|
+
// Intake request metadata even when its state replacement is skipped.
|
|
28
|
+
options.prepare?.(action, args);
|
|
29
|
+
if (busy) {
|
|
30
|
+
// Repeated initial loads must await the accepted load, so callers
|
|
31
|
+
// observe its committed rows rather than the initial skeleton.
|
|
32
|
+
if (activeAction === action && options.coalesce?.includes(action)) await completion;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
busy = true;
|
|
36
|
+
activeAction = action;
|
|
37
|
+
completion = new Promise<void>(resolve => { complete = resolve; });
|
|
38
|
+
try {
|
|
39
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: true });
|
|
40
|
+
const next = await reduce(state, ...args);
|
|
41
|
+
const live = handle.getState();
|
|
42
|
+
// Non-write actions (for example editing a draft field) can still run
|
|
43
|
+
// during I/O. Let the owning service preserve those newer edits.
|
|
44
|
+
const settled = options.settle ? options.settle(state as S, (next ?? live) as S, live as S) : next ?? live;
|
|
45
|
+
await handle.exclusiveWriteState({ ...settled, writeBusy: false });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: false });
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
busy = false;
|
|
51
|
+
complete();
|
|
52
|
+
}
|
|
53
|
+
} : reduce,
|
|
54
|
+
])),
|
|
55
|
+
exclusiveWriteState: (_state, next: Record<string, unknown>) => next,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const registered = registerService(name, wrapped);
|
|
59
|
+
// Registration preserves this private action, but the dynamic action map
|
|
60
|
+
// cannot express its payload to the generic service handle.
|
|
61
|
+
handle = registered as unknown as typeof handle;
|
|
62
|
+
// Older hosts ignore notHydrated. A fresh worker has no pending write.
|
|
63
|
+
handle.getState().writeBusy = false;
|
|
64
|
+
return registered;
|
|
65
|
+
}) as typeof register;
|
|
66
|
+
}
|