@mrrocket/sdk 0.17.0 → 0.21.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/CHANGELOG.md +30 -1
- package/README.md +24 -1
- package/dist/api-client.d.ts +28 -4
- package/dist/api-client.js +30 -13
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +15 -3
- package/dist/browser/mrrocket-messenger.js +38 -5
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +28 -3
- package/dist/client.js +37 -5
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +7 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/feedback.js +161 -119
- package/dist/feedback.js.map +1 -1
- package/dist/finish-web.d.ts +18 -0
- package/dist/finish-web.js +139 -0
- package/dist/finish-web.js.map +1 -0
- package/dist/flows-ui.d.ts +1 -10
- package/dist/flows-ui.js +175 -48
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.d.ts +8 -1
- package/dist/flows-web.js +10 -1
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +193 -9
- package/dist/flows.js +284 -38
- package/dist/flows.js.map +1 -1
- package/dist/i18n.d.ts +22 -0
- package/dist/i18n.js +74 -0
- package/dist/i18n.js.map +1 -0
- package/dist/icons.d.ts +24 -0
- package/dist/icons.js +61 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +9 -3
- package/dist/messenger-ui.js +388 -191
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.d.ts +3 -1
- package/dist/messenger.js +3 -2
- package/dist/messenger.js.map +1 -1
- package/dist/onboarding-state.d.ts +23 -0
- package/dist/onboarding-state.js +43 -0
- package/dist/onboarding-state.js.map +1 -0
- package/dist/paywall.js +2 -1
- package/dist/paywall.js.map +1 -1
- package/dist/react-native/index.d.cts +239 -19
- package/dist/react-native/index.d.ts +239 -19
- package/dist/react-native/index.js +1305 -518
- package/dist/react-native/index.js.map +1 -1
- package/dist/server.d.ts +4 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +35 -2
- package/dist/styles-css.js.map +1 -1
- package/dist/survey.js +18 -14
- package/dist/survey.js.map +1 -1
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +15 -12
- package/dist/ui.js.map +1 -1
- package/package.json +6 -3
package/dist/flows.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/flows.ts
|
|
2
|
+
import { currentLocale, t, tf } from "./i18n.js";
|
|
2
3
|
var CANCEL_EVENT = "$cancel_flow";
|
|
3
4
|
var ONBOARDING_EVENT = "$onboarding";
|
|
4
5
|
var INAPP_EVENT = "$inapp";
|
|
@@ -8,23 +9,54 @@ var PLAIN_CANCEL = {
|
|
|
8
9
|
question: "",
|
|
9
10
|
reasons: [],
|
|
10
11
|
offers: {},
|
|
11
|
-
|
|
12
|
+
// Read when it is shown, so it follows the visitor's language.
|
|
13
|
+
get confirm() {
|
|
14
|
+
return { title: t("Cancel your subscription?"), body: t("You keep access until the end of the billing period."), button: t("Cancel subscription") };
|
|
15
|
+
},
|
|
12
16
|
offersFor: "business"
|
|
13
17
|
};
|
|
14
|
-
var FAILED = "Something went wrong. Please try again.";
|
|
15
18
|
var PROGRESS_KEY = "mrrocket:onboarding";
|
|
16
19
|
var eventSlug = (s) => s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
17
20
|
var SIGNUP = /* @__PURE__ */ new Set(["signed_up", "signup", "sign_up", "registered", "account_created", "user_signed_up"]);
|
|
18
21
|
var matchesTrigger = (trigger, event) => {
|
|
19
|
-
const
|
|
22
|
+
const t2 = eventSlug(trigger);
|
|
20
23
|
const e = eventSlug(event);
|
|
21
|
-
if (!
|
|
22
|
-
return
|
|
24
|
+
if (!t2 || !e) return false;
|
|
25
|
+
return t2 === e || SIGNUP.has(t2) && SIGNUP.has(e);
|
|
23
26
|
};
|
|
24
27
|
var startsOnLoad = (trigger) => /^(first (launch|visit|open|session|login|sign ?in|app open)|app (opened|launched)|on load|every visit|always)?$/i.test(trigger.trim());
|
|
25
28
|
var FROM_CHECKLIST = "From a checklist";
|
|
26
29
|
var isPageTrigger = (trigger) => trigger.trim().startsWith("/");
|
|
27
|
-
var
|
|
30
|
+
var FROM_CODE = "From your code";
|
|
31
|
+
var NEEDS_NEWER = "Needs tracking code 0.18.0";
|
|
32
|
+
var momentOf = (f) => f.when ?? f.trigger;
|
|
33
|
+
var manual = (trigger) => eventSlug(trigger) === "from_a_checklist" || eventSlug(trigger) === "from_your_code" || eventSlug(trigger) === eventSlug(NEEDS_NEWER);
|
|
34
|
+
var AFTER_PREFIX = "after:";
|
|
35
|
+
var afterOf = (trigger) => {
|
|
36
|
+
const t2 = (trigger ?? "").trim();
|
|
37
|
+
return t2.toLowerCase().startsWith(AFTER_PREFIX) ? t2.slice(AFTER_PREFIX.length).trim() || null : null;
|
|
38
|
+
};
|
|
39
|
+
var opensOf = (step) => step.opens ?? (step.startsTour ? { kind: "tour", id: step.startsTour } : void 0);
|
|
40
|
+
var OTHER_PREFIX = "other:";
|
|
41
|
+
var scaleOf = (q) => {
|
|
42
|
+
const min = q?.min ?? 0;
|
|
43
|
+
const max = q?.max ?? 10;
|
|
44
|
+
return Array.from({ length: Math.max(0, Math.min(max - min, 20) + 1) }, (_, i) => min + i);
|
|
45
|
+
};
|
|
46
|
+
var answerOk = (q, value) => {
|
|
47
|
+
if (!q || value == null) return false;
|
|
48
|
+
if (q.type === "choice") {
|
|
49
|
+
if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim().length > 0;
|
|
50
|
+
return (q.options ?? []).some((o) => o.id === value);
|
|
51
|
+
}
|
|
52
|
+
if (q.type === "scale") return scaleOf(q).some((n) => String(n) === value);
|
|
53
|
+
return value.trim().length > 0 && value.length <= 500;
|
|
54
|
+
};
|
|
55
|
+
var answerLabel = (q, value) => {
|
|
56
|
+
if (q?.type !== "choice") return value.trim();
|
|
57
|
+
if (q.other && value.startsWith(OTHER_PREFIX)) return value.slice(OTHER_PREFIX.length).trim();
|
|
58
|
+
return q.options?.find((o) => o.id === value)?.label ?? value;
|
|
59
|
+
};
|
|
28
60
|
var videoEmbed = (url) => {
|
|
29
61
|
const u = (url ?? "").trim();
|
|
30
62
|
if (!/^https?:\/\//i.test(u)) return null;
|
|
@@ -46,12 +78,14 @@ var runId = () => `cf_${Math.random().toString(36).slice(2, 10)}${Date.now().toS
|
|
|
46
78
|
var discounted = (amount, percent) => Math.round(amount * (1 - percent / 100) * 100) / 100;
|
|
47
79
|
var money = (amount, currency) => {
|
|
48
80
|
try {
|
|
49
|
-
return new Intl.NumberFormat(
|
|
81
|
+
return new Intl.NumberFormat(currentLocale(), { style: "currency", currency }).format(amount);
|
|
50
82
|
} catch {
|
|
51
83
|
return `${amount.toFixed(2)} ${currency}`;
|
|
52
84
|
}
|
|
53
85
|
};
|
|
54
|
-
var offerButton = (o) => o.kind === "contact" ? "Send to the team" : o.kind === "pause" ? "Pause instead" : o.kind === "downgrade" ? "Switch plan" : "Accept offer";
|
|
86
|
+
var offerButton = (o) => o.kind === "contact" ? t("Send to the team") : o.kind === "pause" ? t("Pause instead") : o.kind === "downgrade" ? t("Switch plan") : t("Accept offer");
|
|
87
|
+
var monthsWord = (n) => n === 1 ? t("1 month") : tf("{n} months", { n });
|
|
88
|
+
var forMonths = (n) => tf("for {months}", { months: monthsWord(n) });
|
|
55
89
|
var safeUrl = (url) => {
|
|
56
90
|
const u = (url ?? "").trim();
|
|
57
91
|
return /^https?:\/\//i.test(u) || /^\/(?!\/)/.test(u) ? u : void 0;
|
|
@@ -90,7 +124,12 @@ var createFlows = (env) => {
|
|
|
90
124
|
const now = () => env.now?.() ?? Date.now();
|
|
91
125
|
let timer = null;
|
|
92
126
|
let returnTo = null;
|
|
127
|
+
const answers = /* @__PURE__ */ new Map();
|
|
128
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
129
|
+
const celebrated = /* @__PURE__ */ new Set();
|
|
130
|
+
let finaleNow = null;
|
|
93
131
|
const flows = () => config?.onboarding ?? [];
|
|
132
|
+
const drawn = () => flows().filter((f) => f.shownBy !== "code");
|
|
94
133
|
const flowById = (id) => id ? flows().find((f) => f.id === id) : void 0;
|
|
95
134
|
const prog = (id) => {
|
|
96
135
|
let p = progress.get(id);
|
|
@@ -164,6 +203,14 @@ var createFlows = (env) => {
|
|
|
164
203
|
};
|
|
165
204
|
const keyOf = (pid) => `${PROGRESS_KEY}:${pid ?? "visitor"}`;
|
|
166
205
|
const eventsKeyOf = (pid) => `${PROGRESS_KEY}:events:${pid ?? "visitor"}`;
|
|
206
|
+
const answersKeyOf = (pid) => `${PROGRESS_KEY}:answers:${pid ?? "visitor"}`;
|
|
207
|
+
const celebrate = (f) => {
|
|
208
|
+
if (!f.finish || f.finish === "none" || f.shownBy === "code" || celebrated.has(f.id)) return;
|
|
209
|
+
const p = progress.get(f.id);
|
|
210
|
+
if (!p || p.dismissed || walkLine(f, nodesOf(f), p, false).at !== "end") return;
|
|
211
|
+
celebrated.add(f.id);
|
|
212
|
+
finaleNow = { kind: f.finish, key: `${f.id}:${now()}` };
|
|
213
|
+
};
|
|
167
214
|
const save = () => {
|
|
168
215
|
dirty = false;
|
|
169
216
|
const store = env.storage?.();
|
|
@@ -179,6 +226,7 @@ var createFlows = (env) => {
|
|
|
179
226
|
};
|
|
180
227
|
try {
|
|
181
228
|
store.set(keyOf(env.personId()), JSON.stringify(out));
|
|
229
|
+
if (answers.size) store.set(answersKeyOf(env.personId()), JSON.stringify(Object.fromEntries(answers)));
|
|
182
230
|
if (seenEvents.size) store.set(eventsKeyOf(env.personId()), JSON.stringify([...seenEvents].slice(-200)));
|
|
183
231
|
} catch {
|
|
184
232
|
}
|
|
@@ -231,8 +279,17 @@ var createFlows = (env) => {
|
|
|
231
279
|
type: "checklist",
|
|
232
280
|
flow,
|
|
233
281
|
items: items.map((s) => {
|
|
234
|
-
const
|
|
235
|
-
|
|
282
|
+
const o = opensOf(s);
|
|
283
|
+
const t2 = o && (o.kind === "tour" || o.kind === "modal") && o.id !== flow.id ? flowById(o.id) : void 0;
|
|
284
|
+
const target = t2 && t2.shownBy !== "code" && nodesOf(t2).length ? t2 : void 0;
|
|
285
|
+
const opens = o && (o.kind === "page" ? !!o.target.trim() : o.kind === "code" || !!target) ? o : void 0;
|
|
286
|
+
return {
|
|
287
|
+
step: s,
|
|
288
|
+
done: p.done.has(s.id),
|
|
289
|
+
...target && o?.kind === "tour" ? { tour: target } : {},
|
|
290
|
+
...opens ? { opens } : {},
|
|
291
|
+
...target ? { target } : {}
|
|
292
|
+
};
|
|
236
293
|
})
|
|
237
294
|
};
|
|
238
295
|
}
|
|
@@ -243,7 +300,16 @@ var createFlows = (env) => {
|
|
|
243
300
|
const rest = line.slice(index + 1);
|
|
244
301
|
const fork = rest.findIndex((n) => n.kind === "condition");
|
|
245
302
|
const ahead = (fork < 0 ? rest : rest.slice(0, fork)).filter(isStep).length;
|
|
246
|
-
return {
|
|
303
|
+
return {
|
|
304
|
+
type: "step",
|
|
305
|
+
flow,
|
|
306
|
+
step,
|
|
307
|
+
index: w.before,
|
|
308
|
+
total: w.before + 1 + ahead,
|
|
309
|
+
last: !ahead && fork < 0,
|
|
310
|
+
// 0.18.0: a question's answer so far, and whether there is one before it to go back to.
|
|
311
|
+
...step.kind === "question" ? { answer: drafts.get(step.id) ?? (step.question ? answers.get(step.question.key) : void 0), back: w.before > 0 } : {}
|
|
312
|
+
};
|
|
247
313
|
};
|
|
248
314
|
const view = () => {
|
|
249
315
|
if (cancel) {
|
|
@@ -270,23 +336,25 @@ var createFlows = (env) => {
|
|
|
270
336
|
if (ob) return ob;
|
|
271
337
|
if (active && !holding) {
|
|
272
338
|
const back = returnTo;
|
|
273
|
-
const
|
|
339
|
+
const ended2 = active;
|
|
274
340
|
returnTo = null;
|
|
275
341
|
active = null;
|
|
276
342
|
const list = back ? flowById(back.flow) : void 0;
|
|
277
343
|
if (back && list) {
|
|
278
344
|
const item = stepsIn(nodesOf(list)).find((s) => s.id === back.step);
|
|
279
345
|
const p = prog(list.id);
|
|
280
|
-
if (item && !item.event && !progress.get(
|
|
346
|
+
if (item && !item.event && !progress.get(ended2)?.dismissed && !p.done.has(item.id)) {
|
|
281
347
|
markDone(p, item.id);
|
|
282
348
|
record({ flow: list.id, step: item.id, kind: "checklist", action: "completed" });
|
|
349
|
+
celebrate(list);
|
|
283
350
|
}
|
|
284
351
|
active = list.id;
|
|
285
352
|
const again = onboardingView();
|
|
286
353
|
if (again) return again;
|
|
287
354
|
if (!holding) active = null;
|
|
288
355
|
} else if (known) {
|
|
289
|
-
const
|
|
356
|
+
const was = flowById(ended2);
|
|
357
|
+
const after = was ? momentOf(was) : void 0;
|
|
290
358
|
Promise.resolve().then(() => autoStart(after));
|
|
291
359
|
}
|
|
292
360
|
}
|
|
@@ -314,22 +382,64 @@ var createFlows = (env) => {
|
|
|
314
382
|
emit();
|
|
315
383
|
return true;
|
|
316
384
|
};
|
|
317
|
-
const allowed = (f) => f.audience !== "signed_in" || !!env.personId();
|
|
385
|
+
const allowed = (f) => f.audience === "answered" ? !!f.answered && answers.get(f.answered.question) === f.answered.option : f.audience !== "signed_in" || !!env.personId();
|
|
386
|
+
const predecessorEnded = (id) => {
|
|
387
|
+
const g = flowById(id);
|
|
388
|
+
return !g || finished(g);
|
|
389
|
+
};
|
|
390
|
+
const due = (f, trigger = momentOf(f)) => {
|
|
391
|
+
const path = env.path?.();
|
|
392
|
+
const pred = afterOf(trigger);
|
|
393
|
+
if (pred) return predecessorEnded(pred);
|
|
394
|
+
if (manual(trigger)) return false;
|
|
395
|
+
if (isPageTrigger(trigger)) return path !== void 0 && pageMatches(trigger, path);
|
|
396
|
+
if (startsOnLoad(trigger)) return true;
|
|
397
|
+
for (const e of seenEvents) if (matchesTrigger(trigger, e)) return true;
|
|
398
|
+
return false;
|
|
399
|
+
};
|
|
318
400
|
const autoStart = (after) => {
|
|
319
401
|
if (!auto || active || !known) return;
|
|
320
402
|
if (!env.personId() && !env.storage?.()) return;
|
|
321
403
|
const path = env.path?.();
|
|
322
404
|
const same = after && !manual(after) ? eventSlug(after) : "";
|
|
323
|
-
const next =
|
|
324
|
-
(f) => (isPageTrigger(f
|
|
405
|
+
const next = drawn().find(
|
|
406
|
+
(f) => (afterOf(momentOf(f)) ? predecessorEnded(afterOf(momentOf(f))) : isPageTrigger(momentOf(f)) ? path !== void 0 && pageMatches(momentOf(f), path) : startsOnLoad(momentOf(f)) || !!same && eventSlug(momentOf(f)) === same) && allowed(f) && nodesOf(f).length && !finished(f)
|
|
325
407
|
);
|
|
326
408
|
if (next) start(next);
|
|
327
409
|
};
|
|
410
|
+
const openItem = (list, stepId, target) => {
|
|
411
|
+
const p = prog(target.id);
|
|
412
|
+
p.done.clear();
|
|
413
|
+
p.at.clear();
|
|
414
|
+
p.dismissed = false;
|
|
415
|
+
returnTo = { flow: list.id, step: stepId };
|
|
416
|
+
active = target.id;
|
|
417
|
+
env.track(ONBOARDING_EVENT, { flow: target.id, action: "started", from: list.id });
|
|
418
|
+
save();
|
|
419
|
+
emit();
|
|
420
|
+
};
|
|
421
|
+
const answerStep = (flow, step, raw) => {
|
|
422
|
+
const q = step.question;
|
|
423
|
+
const value = q.type === "text" ? raw.trim().slice(0, 500) : raw.startsWith(OTHER_PREFIX) ? OTHER_PREFIX + raw.slice(OTHER_PREFIX.length).trim().slice(0, 490) : raw;
|
|
424
|
+
answers.set(q.key, value);
|
|
425
|
+
drafts.delete(step.id);
|
|
426
|
+
markDone(prog(flow.id), step.id);
|
|
427
|
+
env.track(ONBOARDING_EVENT, { flow: flow.id, step: step.id, kind: "question", action: "completed", question: q.key, answer: value, label: answerLabel(q, value) });
|
|
428
|
+
celebrate(flow);
|
|
429
|
+
save();
|
|
430
|
+
};
|
|
431
|
+
const ended = (f) => {
|
|
432
|
+
save();
|
|
433
|
+
if (finished(f)) Promise.resolve().then(() => autoStart());
|
|
434
|
+
emit();
|
|
435
|
+
};
|
|
328
436
|
const refresh = () => {
|
|
329
437
|
const pid = env.personId();
|
|
330
438
|
if (progressFor !== null && progressFor !== pid) {
|
|
331
439
|
progress.clear();
|
|
332
440
|
seenEvents.clear();
|
|
441
|
+
answers.clear();
|
|
442
|
+
drafts.clear();
|
|
333
443
|
handled.clear();
|
|
334
444
|
active = null;
|
|
335
445
|
returnTo = null;
|
|
@@ -346,6 +456,10 @@ var createFlows = (env) => {
|
|
|
346
456
|
if (seen) {
|
|
347
457
|
for (const e of JSON.parse(seen)) if (typeof e === "string") seenEvents.add(e);
|
|
348
458
|
}
|
|
459
|
+
const said = await store.get(answersKeyOf(pid));
|
|
460
|
+
if (said) {
|
|
461
|
+
for (const [k, v] of Object.entries(JSON.parse(said))) if (typeof v === "string") answers.set(k, v);
|
|
462
|
+
}
|
|
349
463
|
} catch {
|
|
350
464
|
}
|
|
351
465
|
}
|
|
@@ -356,6 +470,7 @@ var createFlows = (env) => {
|
|
|
356
470
|
if (state && env.personId() === pid) {
|
|
357
471
|
merge(state.onboarding);
|
|
358
472
|
for (const e of state.events ?? []) if (typeof e === "string") seenEvents.add(eventSlug(e));
|
|
473
|
+
for (const [k, v] of Object.entries(state.answers ?? {})) if (typeof v === "string" && !answers.has(k)) answers.set(k, v);
|
|
359
474
|
messages = (state.messages ?? []).filter((m) => m && m.id && m.title);
|
|
360
475
|
}
|
|
361
476
|
}
|
|
@@ -416,7 +531,7 @@ var createFlows = (env) => {
|
|
|
416
531
|
() => finish(c, r),
|
|
417
532
|
() => {
|
|
418
533
|
c.busy = false;
|
|
419
|
-
c.error =
|
|
534
|
+
c.error = t("Something went wrong. Please try again.");
|
|
420
535
|
emit();
|
|
421
536
|
}
|
|
422
537
|
);
|
|
@@ -449,27 +564,33 @@ var createFlows = (env) => {
|
|
|
449
564
|
if (fresh) seenEvents.add(slug);
|
|
450
565
|
const flow = flowById(active);
|
|
451
566
|
let ticked = false;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
567
|
+
const lists = [...flow ? [flow] : [], ...flows().filter((f) => f.shownBy === "code" && f.id !== flow?.id)];
|
|
568
|
+
for (const l of lists) {
|
|
569
|
+
const p = prog(l.id);
|
|
570
|
+
let here = false;
|
|
571
|
+
for (const s of stepsIn(nodesOf(l)))
|
|
455
572
|
if (s.kind === "checklist" && s.event && !p.done.has(s.id) && matchesTrigger(s.event, name)) {
|
|
456
573
|
markDone(p, s.id);
|
|
457
|
-
|
|
458
|
-
env.track(ONBOARDING_EVENT, { flow:
|
|
574
|
+
here = true;
|
|
575
|
+
env.track(ONBOARDING_EVENT, { flow: l.id, step: s.id, kind: s.kind, action: "completed" });
|
|
459
576
|
}
|
|
577
|
+
if (here) {
|
|
578
|
+
ticked = true;
|
|
579
|
+
celebrate(l);
|
|
580
|
+
}
|
|
460
581
|
}
|
|
461
|
-
if (ticked || fresh && flow) {
|
|
582
|
+
if (ticked || fresh && (flow || lists.length)) {
|
|
462
583
|
save();
|
|
463
584
|
emit();
|
|
464
585
|
} else if (fresh) save();
|
|
465
586
|
if (!auto || !env.personId() && !env.storage?.()) return;
|
|
466
|
-
const next =
|
|
587
|
+
const next = drawn().find((f) => nodesOf(f).length && !manual(momentOf(f)) && matchesTrigger(momentOf(f), name) && allowed(f) && !finished(f));
|
|
467
588
|
if (next) start(next);
|
|
468
589
|
},
|
|
469
590
|
openCancelFlow,
|
|
470
591
|
startOnboarding: (id) => {
|
|
471
|
-
const f = id ? flowById(id) :
|
|
472
|
-
if (!f || !nodesOf(f).length) return false;
|
|
592
|
+
const f = id ? flowById(id) : drawn().find((x) => nodesOf(x).length && !manual(momentOf(x)) && !afterOf(momentOf(x)) && !finished(x));
|
|
593
|
+
if (!f || !nodesOf(f).length || f.shownBy === "code") return false;
|
|
473
594
|
const p = prog(f.id);
|
|
474
595
|
p.dismissed = false;
|
|
475
596
|
return start(f);
|
|
@@ -490,8 +611,8 @@ var createFlows = (env) => {
|
|
|
490
611
|
answer: (text) => {
|
|
491
612
|
const c = cancel;
|
|
492
613
|
if (!c || c.busy || c.screen !== "followup") return;
|
|
493
|
-
const
|
|
494
|
-
c.answer =
|
|
614
|
+
const t2 = text.slice(0, 500);
|
|
615
|
+
c.answer = t2.trim() ? t2 : void 0;
|
|
495
616
|
emit();
|
|
496
617
|
},
|
|
497
618
|
next: () => {
|
|
@@ -560,8 +681,17 @@ var createFlows = (env) => {
|
|
|
560
681
|
next: () => {
|
|
561
682
|
const v = onboardingView();
|
|
562
683
|
if (v?.type !== "step") return;
|
|
684
|
+
if (v.step.kind === "question") {
|
|
685
|
+
const q = v.step.question;
|
|
686
|
+
const a = drafts.get(v.step.id) ?? (q ? answers.get(q.key) : void 0);
|
|
687
|
+
if (!q || !answerOk(q, a)) return;
|
|
688
|
+
answerStep(v.flow, v.step, a);
|
|
689
|
+
emit();
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
563
692
|
markDone(prog(v.flow.id), v.step.id);
|
|
564
693
|
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: v.step.kind, action: "completed" });
|
|
694
|
+
celebrate(v.flow);
|
|
565
695
|
save();
|
|
566
696
|
emit();
|
|
567
697
|
const url = v.step.kind === "modal" ? safeUrl(v.step.url) : void 0;
|
|
@@ -574,6 +704,7 @@ var createFlows = (env) => {
|
|
|
574
704
|
if (!item || item.done) return;
|
|
575
705
|
markDone(prog(v.flow.id), stepId);
|
|
576
706
|
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
|
|
707
|
+
celebrate(v.flow);
|
|
577
708
|
save();
|
|
578
709
|
emit();
|
|
579
710
|
},
|
|
@@ -590,20 +721,123 @@ var createFlows = (env) => {
|
|
|
590
721
|
startTour: (stepId) => {
|
|
591
722
|
const v = onboardingView();
|
|
592
723
|
if (v?.type !== "checklist") return false;
|
|
593
|
-
const
|
|
594
|
-
if (!
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
724
|
+
const target = v.items.find((i) => i.step.id === stepId)?.target;
|
|
725
|
+
if (!target) return false;
|
|
726
|
+
openItem(v.flow, stepId, target);
|
|
727
|
+
return true;
|
|
728
|
+
},
|
|
729
|
+
open: (stepId) => {
|
|
730
|
+
const v = onboardingView();
|
|
731
|
+
if (v?.type !== "checklist") return false;
|
|
732
|
+
const it = v.items.find((i) => i.step.id === stepId);
|
|
733
|
+
const o = it?.opens;
|
|
734
|
+
if (!it || !o) return false;
|
|
735
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "opened", opens: o.kind });
|
|
736
|
+
if ((o.kind === "tour" || o.kind === "modal") && it.target) {
|
|
737
|
+
openItem(v.flow, stepId, it.target);
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
if (!it.done && !it.step.event) {
|
|
741
|
+
markDone(prog(v.flow.id), stepId);
|
|
742
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: "checklist", action: "completed" });
|
|
743
|
+
celebrate(v.flow);
|
|
744
|
+
}
|
|
602
745
|
save();
|
|
603
746
|
emit();
|
|
747
|
+
if (o.kind === "page") env.navigate?.(o.target.trim());
|
|
748
|
+
else env.task?.(stepId, v.flow);
|
|
604
749
|
return true;
|
|
750
|
+
},
|
|
751
|
+
answer: (value) => {
|
|
752
|
+
const v = onboardingView();
|
|
753
|
+
if (v?.type !== "step" || v.step.kind !== "question") return;
|
|
754
|
+
drafts.set(v.step.id, String(value).slice(0, 500));
|
|
755
|
+
emit();
|
|
756
|
+
},
|
|
757
|
+
skip: () => {
|
|
758
|
+
const v = onboardingView();
|
|
759
|
+
const q = v?.type === "step" && v.step.kind === "question" ? v.step.question : void 0;
|
|
760
|
+
if (v?.type !== "step" || !q?.optional) return;
|
|
761
|
+
drafts.delete(v.step.id);
|
|
762
|
+
markDone(prog(v.flow.id), v.step.id);
|
|
763
|
+
env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: "question", action: "completed", question: q.key, skipped: true });
|
|
764
|
+
celebrate(v.flow);
|
|
765
|
+
save();
|
|
766
|
+
emit();
|
|
767
|
+
},
|
|
768
|
+
back: () => {
|
|
769
|
+
const v = onboardingView();
|
|
770
|
+
if (v?.type !== "step" || v.step.kind !== "question" || !v.back) return;
|
|
771
|
+
const line = stepsIn(nodesOf(v.flow));
|
|
772
|
+
const at = line.findIndex((s) => s.id === v.step.id);
|
|
773
|
+
const p = prog(v.flow.id);
|
|
774
|
+
for (let i = at - 1; i >= 0; i--)
|
|
775
|
+
if (p.done.has(line[i].id)) {
|
|
776
|
+
p.done.delete(line[i].id);
|
|
777
|
+
p.at.delete(line[i].id);
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
save();
|
|
781
|
+
emit();
|
|
782
|
+
},
|
|
783
|
+
item: (id) => {
|
|
784
|
+
const f = flowById(id);
|
|
785
|
+
if (!f) return null;
|
|
786
|
+
const p = progress.get(f.id);
|
|
787
|
+
const steps = stepsIn(nodesOf(f)).map((s) => {
|
|
788
|
+
const a = s.question ? answers.get(s.question.key) : void 0;
|
|
789
|
+
return { ...s, done: !!p?.done.has(s.id), ...a !== void 0 ? { answer: a } : {} };
|
|
790
|
+
});
|
|
791
|
+
const done = steps.length > 0 && steps.every((s) => s.done);
|
|
792
|
+
const dismissed = !!p?.dismissed;
|
|
793
|
+
return { item: f, steps, done, dismissed, eligible: !done && !dismissed && allowed(f) && due(f) };
|
|
794
|
+
},
|
|
795
|
+
seen: (id) => {
|
|
796
|
+
const f = flowById(id);
|
|
797
|
+
if (!f || f.shownBy !== "code" || !known) return;
|
|
798
|
+
if (!shown.has(`f:${f.id}`)) {
|
|
799
|
+
shown.add(`f:${f.id}`);
|
|
800
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, action: "started" });
|
|
801
|
+
}
|
|
802
|
+
const p = progress.get(f.id);
|
|
803
|
+
for (const s of stepsIn(nodesOf(f)))
|
|
804
|
+
if (!p?.done.has(s.id) && !shown.has(`s:${s.id}`)) {
|
|
805
|
+
shown.add(`s:${s.id}`);
|
|
806
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, step: s.id, kind: s.kind, action: "shown" });
|
|
807
|
+
if (s.kind !== "checklist") break;
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
complete: (id, stepId) => {
|
|
811
|
+
const f = flowById(id);
|
|
812
|
+
const s = f && stepsIn(nodesOf(f)).find((x) => x.id === stepId);
|
|
813
|
+
if (!f || !s || s.kind === "question") return;
|
|
814
|
+
const p = prog(f.id);
|
|
815
|
+
if (p.done.has(s.id)) return;
|
|
816
|
+
markDone(p, s.id);
|
|
817
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, step: s.id, kind: s.kind, action: "completed" });
|
|
818
|
+
ended(f);
|
|
819
|
+
},
|
|
820
|
+
dismissItem: (id) => {
|
|
821
|
+
const f = flowById(id);
|
|
822
|
+
if (!f) return;
|
|
823
|
+
const p = prog(f.id);
|
|
824
|
+
if (p.dismissed) return;
|
|
825
|
+
p.dismissed = true;
|
|
826
|
+
env.track(ONBOARDING_EVENT, { flow: f.id, action: "dismissed" });
|
|
827
|
+
if (active === f.id) active = null;
|
|
828
|
+
ended(f);
|
|
829
|
+
},
|
|
830
|
+
answerItem: (id, question, value) => {
|
|
831
|
+
const f = flowById(id);
|
|
832
|
+
const s = f && stepsIn(nodesOf(f)).find((x) => x.kind === "question" && (x.id === question || x.question?.key === question));
|
|
833
|
+
if (!f || !s?.question || !answerOk(s.question, value)) return;
|
|
834
|
+
answerStep(f, s, value);
|
|
835
|
+
ended(f);
|
|
605
836
|
}
|
|
606
837
|
},
|
|
838
|
+
finale: () => finaleNow,
|
|
839
|
+
accent: () => config?.onboardingAccent,
|
|
840
|
+
answers: () => Object.fromEntries(answers),
|
|
607
841
|
message: {
|
|
608
842
|
click: () => {
|
|
609
843
|
const v = view();
|
|
@@ -626,26 +860,38 @@ var createFlows = (env) => {
|
|
|
626
860
|
return controller;
|
|
627
861
|
};
|
|
628
862
|
export {
|
|
863
|
+
AFTER_PREFIX,
|
|
629
864
|
CANCEL_EVENT,
|
|
630
865
|
FROM_CHECKLIST,
|
|
866
|
+
FROM_CODE,
|
|
631
867
|
INAPP_EVENT,
|
|
868
|
+
NEEDS_NEWER,
|
|
632
869
|
ONBOARDING_EVENT,
|
|
870
|
+
OTHER_PREFIX,
|
|
633
871
|
PLAIN_CANCEL,
|
|
634
872
|
REVIEW_EVENT,
|
|
873
|
+
afterOf,
|
|
874
|
+
answerLabel,
|
|
875
|
+
answerOk,
|
|
635
876
|
createFlows,
|
|
636
877
|
discounted,
|
|
637
878
|
eventSlug,
|
|
879
|
+
forMonths,
|
|
638
880
|
isPageTrigger,
|
|
639
881
|
matchesTrigger,
|
|
640
882
|
mediaOf,
|
|
883
|
+
momentOf,
|
|
641
884
|
money,
|
|
642
885
|
monthsSince,
|
|
886
|
+
monthsWord,
|
|
643
887
|
nodesOf,
|
|
644
888
|
offerButton,
|
|
889
|
+
opensOf,
|
|
645
890
|
pageMatches,
|
|
646
891
|
ruleMatches,
|
|
647
892
|
runId,
|
|
648
893
|
safeUrl,
|
|
894
|
+
scaleOf,
|
|
649
895
|
startsOnLoad,
|
|
650
896
|
videoEmbed,
|
|
651
897
|
waitMs
|