@planningcenter/topbar 12.3.0-qa-937.2 → 12.3.0-qa-947.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/topbar.js +452 -370
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +452 -370
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +452 -370
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +452 -370
- package/dist/topbar.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/topbar.js
CHANGED
|
@@ -3266,6 +3266,45 @@ function saveLastProduct(slug) {
|
|
|
3266
3266
|
}
|
|
3267
3267
|
}
|
|
3268
3268
|
|
|
3269
|
+
// Products open modals from many libraries, not all of which mark themselves
|
|
3270
|
+
// with roles, so a modal is recognized by what it does: cover the page.
|
|
3271
|
+
function isModalOpen() {
|
|
3272
|
+
const {
|
|
3273
|
+
clientHeight,
|
|
3274
|
+
clientWidth
|
|
3275
|
+
} = document.documentElement;
|
|
3276
|
+
const element = document.elementFromPoint(clientWidth / 2, clientHeight / 2);
|
|
3277
|
+
return element !== null && (isInNativeDialog(element) || hasBackdrop(element, clientWidth, clientHeight));
|
|
3278
|
+
}
|
|
3279
|
+
// A native modal's backdrop is drawn by the browser rather than being an
|
|
3280
|
+
// element; hit-testing it lands on the dialog itself.
|
|
3281
|
+
function isInNativeDialog(element) {
|
|
3282
|
+
return element.closest("dialog[open]") !== null;
|
|
3283
|
+
}
|
|
3284
|
+
function hasBackdrop(element, width, height) {
|
|
3285
|
+
let current = element;
|
|
3286
|
+
while (current && !isBackdrop(current, width, height)) {
|
|
3287
|
+
current = current.parentElement;
|
|
3288
|
+
}
|
|
3289
|
+
return current !== null;
|
|
3290
|
+
}
|
|
3291
|
+
// Some backdrops start below the topbar rather than at the top of the screen,
|
|
3292
|
+
// so the layer only has to span the width and reach from the center to the
|
|
3293
|
+
// bottom. Its own box has to reach the center: a bar along the bottom can
|
|
3294
|
+
// hold a panel that pops up over the center, like Services' media player.
|
|
3295
|
+
// react-aria leaves a closed modal's backdrop up while it fades out, search's
|
|
3296
|
+
// own included, so one that's on its way out doesn't count.
|
|
3297
|
+
function isBackdrop(element, width, height) {
|
|
3298
|
+
const rect = element.getBoundingClientRect();
|
|
3299
|
+
return !element.matches("[data-exiting]") && getComputedStyle(element).position === "fixed" && rect.width >= width - SCROLLBAR_SLACK && rect.top <= height / 2 && rect.bottom >= height - ROUNDING_SLACK;
|
|
3300
|
+
}
|
|
3301
|
+
// react-aria keeps a classic scrollbar's gutter reserved while its modals lock
|
|
3302
|
+
// scrolling, so their backdrops stop short of the right edge by its width.
|
|
3303
|
+
const SCROLLBAR_SLACK = 24;
|
|
3304
|
+
// Rects are fractional at non-100% zoom, but clientWidth and clientHeight are
|
|
3305
|
+
// rounded to whole pixels.
|
|
3306
|
+
const ROUNDING_SLACK = 1;
|
|
3307
|
+
|
|
3269
3308
|
var styles$c = {"visuallyHidden":"visuallyHidden-C28ju"};
|
|
3270
3309
|
|
|
3271
3310
|
var styles$b = {"tab":"tab-lQLDx"};
|
|
@@ -3397,10 +3436,11 @@ function useRecentlyVisitableMetadata() {
|
|
|
3397
3436
|
return shim.useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3398
3437
|
}
|
|
3399
3438
|
|
|
3400
|
-
const MAX_ENTRIES$1 =
|
|
3401
|
-
const FORMAT_VERSION = 1;
|
|
3439
|
+
const MAX_ENTRIES$1 = 50;
|
|
3402
3440
|
const MAX_TEXT_LENGTH = 200;
|
|
3403
3441
|
const PRODUCTS = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3442
|
+
// Login has no pages worth returning to.
|
|
3443
|
+
const APPS = PRODUCTS.filter(product => product !== "login");
|
|
3404
3444
|
function isId(value) {
|
|
3405
3445
|
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3406
3446
|
}
|
|
@@ -3409,26 +3449,47 @@ function isPcrl(value) {
|
|
|
3409
3449
|
const segments = value.split("/");
|
|
3410
3450
|
return segments.length === 4 && PRODUCTS.some(product => product === segments[0]) && isId(segments[3]);
|
|
3411
3451
|
}
|
|
3452
|
+
function isApp(value) {
|
|
3453
|
+
return APPS.some(app => app === value);
|
|
3454
|
+
}
|
|
3455
|
+
// Links are built by resolving the path against its app's origin, so a stored
|
|
3456
|
+
// path must not be able to resolve anywhere else (`//evil.com`, `/\evil.com`).
|
|
3457
|
+
function isPath(value) {
|
|
3458
|
+
const origin = "https://app.invalid";
|
|
3459
|
+
try {
|
|
3460
|
+
return typeof value === "string" && value.startsWith("/") && new URL(value, origin).origin === origin;
|
|
3461
|
+
} catch {
|
|
3462
|
+
return false;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3412
3465
|
function isNonBlankString(value) {
|
|
3413
3466
|
return typeof value === "string" && value.trim() !== "";
|
|
3414
3467
|
}
|
|
3468
|
+
function isOptionalString(value) {
|
|
3469
|
+
return value === undefined || typeof value === "string";
|
|
3470
|
+
}
|
|
3415
3471
|
function isRecord(value) {
|
|
3416
3472
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3417
3473
|
}
|
|
3474
|
+
function isPageMetadata(value) {
|
|
3475
|
+
return value === undefined || isRecord(value) && isOptionalString(value.details) && isOptionalString(value.type);
|
|
3476
|
+
}
|
|
3477
|
+
// The query string is part of the link but not of the page's identity, so
|
|
3478
|
+
// revisiting with other filters moves the existing entry instead of adding one.
|
|
3418
3479
|
function entryKey(entry) {
|
|
3419
3480
|
if (entry.type === "resource") {
|
|
3420
3481
|
return `resource:${entry.pcrl}`;
|
|
3421
3482
|
} else {
|
|
3422
|
-
return `page:${entry.
|
|
3483
|
+
return `page:${entry.app}:${entry.path.split("?")[0]}`;
|
|
3423
3484
|
}
|
|
3424
3485
|
}
|
|
3425
3486
|
function isEntry(value) {
|
|
3426
|
-
if (!isRecord(value)) {
|
|
3487
|
+
if (!isRecord(value) || typeof value.visitedAt !== "string") {
|
|
3427
3488
|
return false;
|
|
3428
3489
|
} else if (value.type === "resource") {
|
|
3429
3490
|
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3430
3491
|
} else if (value.type === "page") {
|
|
3431
|
-
return
|
|
3492
|
+
return isApp(value.app) && isPath(value.path) && isNonBlankString(value.title) && isPageMetadata(value.metadata);
|
|
3432
3493
|
} else {
|
|
3433
3494
|
return false;
|
|
3434
3495
|
}
|
|
@@ -3437,58 +3498,175 @@ function isEntry(value) {
|
|
|
3437
3498
|
function clampText(value) {
|
|
3438
3499
|
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3439
3500
|
}
|
|
3440
|
-
function
|
|
3501
|
+
function clampMetadata({
|
|
3502
|
+
details,
|
|
3503
|
+
type
|
|
3504
|
+
}) {
|
|
3505
|
+
return {
|
|
3506
|
+
...(details && {
|
|
3507
|
+
details: clampText(details)
|
|
3508
|
+
}),
|
|
3509
|
+
...(type && {
|
|
3510
|
+
type: clampText(type)
|
|
3511
|
+
})
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
function clampVisit(visit) {
|
|
3515
|
+
if (visit.type === "resource") {
|
|
3516
|
+
return visit;
|
|
3517
|
+
} else {
|
|
3518
|
+
return {
|
|
3519
|
+
...visit,
|
|
3520
|
+
...(visit.metadata && {
|
|
3521
|
+
metadata: clampMetadata(visit.metadata)
|
|
3522
|
+
}),
|
|
3523
|
+
title: clampText(visit.title.trim())
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
function toEntry(visit, visitedAt) {
|
|
3528
|
+
return {
|
|
3529
|
+
...clampVisit(visit),
|
|
3530
|
+
visitedAt: visitedAt.toISOString()
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
function knownMetadata({
|
|
3534
|
+
details,
|
|
3535
|
+
type
|
|
3536
|
+
}) {
|
|
3537
|
+
if (details === undefined && type === undefined) {
|
|
3538
|
+
return undefined;
|
|
3539
|
+
} else {
|
|
3540
|
+
return {
|
|
3541
|
+
...(details !== undefined && {
|
|
3542
|
+
details
|
|
3543
|
+
}),
|
|
3544
|
+
...(type !== undefined && {
|
|
3545
|
+
type
|
|
3546
|
+
})
|
|
3547
|
+
};
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
// user-store-sync rejects a whole write over one field it doesn't know, so
|
|
3551
|
+
// anything else an entry carries (like the version older entries stored) is
|
|
3552
|
+
// dropped on the way in.
|
|
3553
|
+
function withKnownFields(entry) {
|
|
3441
3554
|
if (entry.type === "resource") {
|
|
3442
|
-
return
|
|
3555
|
+
return {
|
|
3556
|
+
pcrl: entry.pcrl,
|
|
3557
|
+
type: entry.type,
|
|
3558
|
+
visitedAt: entry.visitedAt
|
|
3559
|
+
};
|
|
3443
3560
|
} else {
|
|
3561
|
+
const metadata = entry.metadata && knownMetadata(entry.metadata);
|
|
3444
3562
|
return {
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3563
|
+
app: entry.app,
|
|
3564
|
+
...(metadata && {
|
|
3565
|
+
metadata
|
|
3566
|
+
}),
|
|
3567
|
+
path: entry.path,
|
|
3568
|
+
title: entry.title,
|
|
3569
|
+
type: entry.type,
|
|
3570
|
+
visitedAt: entry.visitedAt
|
|
3448
3571
|
};
|
|
3449
3572
|
}
|
|
3450
3573
|
}
|
|
3451
3574
|
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
3452
3575
|
// a single bad entry, a list without it) rather than throwing.
|
|
3453
|
-
function
|
|
3454
|
-
|
|
3576
|
+
function entriesFrom(value) {
|
|
3577
|
+
if (Array.isArray(value)) {
|
|
3578
|
+
return value.filter(isEntry).map(withKnownFields);
|
|
3579
|
+
} else {
|
|
3580
|
+
return [];
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
function parseRecentlyVisited(value) {
|
|
3455
3584
|
try {
|
|
3456
|
-
|
|
3585
|
+
return entriesFrom(JSON.parse(value !== null && value !== void 0 ? value : "null"));
|
|
3457
3586
|
} catch {
|
|
3458
|
-
parsed = null;
|
|
3459
|
-
}
|
|
3460
|
-
if (isRecord(parsed) && parsed.v === FORMAT_VERSION && parsed.person === personId && Array.isArray(parsed.entries)) {
|
|
3461
|
-
return parsed.entries.filter(isEntry);
|
|
3462
|
-
} else {
|
|
3463
3587
|
return [];
|
|
3464
3588
|
}
|
|
3465
3589
|
}
|
|
3466
|
-
function addVisit(entries,
|
|
3467
|
-
const key = entryKey(
|
|
3468
|
-
return [
|
|
3590
|
+
function addVisit(entries, entry) {
|
|
3591
|
+
const key = entryKey(entry);
|
|
3592
|
+
return [entry, ...entries.filter(existing => entryKey(existing) !== key)].slice(0, MAX_ENTRIES$1);
|
|
3469
3593
|
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3594
|
+
// The service may write timestamps with fewer fractional digits than
|
|
3595
|
+
// toISOString, so they're compared as times rather than strings.
|
|
3596
|
+
function visitTime({
|
|
3597
|
+
visitedAt
|
|
3598
|
+
}) {
|
|
3599
|
+
return Date.parse(visitedAt) || 0;
|
|
3600
|
+
}
|
|
3601
|
+
// One list from several, newest visit first wherever it was recorded.
|
|
3602
|
+
function mergeEntries(...lists) {
|
|
3603
|
+
const seen = new Set();
|
|
3604
|
+
return lists.flat().sort((a, b) => visitTime(b) - visitTime(a)).filter(entry => {
|
|
3605
|
+
const key = entryKey(entry);
|
|
3606
|
+
const isFirst = !seen.has(key);
|
|
3607
|
+
seen.add(key);
|
|
3608
|
+
return isFirst;
|
|
3609
|
+
}).slice(0, MAX_ENTRIES$1);
|
|
3610
|
+
}
|
|
3611
|
+
function isSameList(a, b) {
|
|
3612
|
+
return a.length === b.length && a.every((entry, index) => entryKey(entry) === entryKey(b[index]) && visitTime(entry) === visitTime(b[index]));
|
|
3476
3613
|
}
|
|
3477
3614
|
|
|
3478
|
-
// The
|
|
3615
|
+
// The app whose URL, in this environment, has the current page's host.
|
|
3479
3616
|
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
3480
|
-
// and guarantees the stored
|
|
3481
|
-
function
|
|
3617
|
+
// and guarantees the stored app rebuilds a working link.
|
|
3618
|
+
function currentApp() {
|
|
3482
3619
|
var _a;
|
|
3483
|
-
return (_a =
|
|
3620
|
+
return (_a = APPS.find(app => new URL(pcoUrl$1.pcoUrl(app)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
3484
3621
|
}
|
|
3485
|
-
function
|
|
3622
|
+
function isFilled(value) {
|
|
3623
|
+
return typeof value === "string" && value !== "";
|
|
3624
|
+
}
|
|
3625
|
+
// The tag's metadata is product-supplied JSON; only its string `type` and
|
|
3626
|
+
// `details` carry over, and an entry without either has no metadata at all.
|
|
3627
|
+
function metadataFromPage(metadata) {
|
|
3486
3628
|
var _a;
|
|
3487
3629
|
try {
|
|
3488
|
-
const
|
|
3489
|
-
|
|
3630
|
+
const {
|
|
3631
|
+
details,
|
|
3632
|
+
type
|
|
3633
|
+
} = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) !== null && _a !== void 0 ? _a : {};
|
|
3634
|
+
if (isFilled(details) || isFilled(type)) {
|
|
3635
|
+
return {
|
|
3636
|
+
...(isFilled(details) && {
|
|
3637
|
+
details
|
|
3638
|
+
}),
|
|
3639
|
+
...(isFilled(type) && {
|
|
3640
|
+
type
|
|
3641
|
+
})
|
|
3642
|
+
};
|
|
3643
|
+
} else {
|
|
3644
|
+
return undefined;
|
|
3645
|
+
}
|
|
3490
3646
|
} catch {
|
|
3491
|
-
return
|
|
3647
|
+
return undefined;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
// Staff impersonating someone browse under `/O<org id>/P<person id>/…`.
|
|
3651
|
+
function withoutImpersonation(segments) {
|
|
3652
|
+
const [org = "", person = ""] = segments;
|
|
3653
|
+
if (org.startsWith("O") && isId(org.slice(1)) && person.startsWith("P") && isId(person.slice(1))) {
|
|
3654
|
+
return segments.slice(2);
|
|
3655
|
+
} else {
|
|
3656
|
+
return segments;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
// Normalized the way home-mobile does it, so a page visited on web and on
|
|
3660
|
+
// mobile is one entry once the list syncs.
|
|
3661
|
+
function pathFromLocation({
|
|
3662
|
+
pathname,
|
|
3663
|
+
search
|
|
3664
|
+
}) {
|
|
3665
|
+
const segments = withoutImpersonation(pathname.split("/").filter(Boolean));
|
|
3666
|
+
if (segments.length === 0) {
|
|
3667
|
+
return "/";
|
|
3668
|
+
} else {
|
|
3669
|
+
return `/${segments.join("/")}${search.length > 1 ? search : ""}`;
|
|
3492
3670
|
}
|
|
3493
3671
|
}
|
|
3494
3672
|
// pco:resource is the more specific signal, so it wins when both tags are
|
|
@@ -3498,17 +3676,20 @@ function visitFromPage({
|
|
|
3498
3676
|
resource,
|
|
3499
3677
|
title
|
|
3500
3678
|
}) {
|
|
3501
|
-
const
|
|
3679
|
+
const app = currentApp();
|
|
3502
3680
|
if (resource && isPcrl(resource)) {
|
|
3503
3681
|
return {
|
|
3504
3682
|
pcrl: resource,
|
|
3505
3683
|
type: "resource"
|
|
3506
3684
|
};
|
|
3507
|
-
} else if (title && title.trim() !== "" &&
|
|
3685
|
+
} else if (title && title.trim() !== "" && app) {
|
|
3686
|
+
const pageMetadata = metadataFromPage(metadata);
|
|
3508
3687
|
return {
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3688
|
+
app,
|
|
3689
|
+
...(pageMetadata && {
|
|
3690
|
+
metadata: pageMetadata
|
|
3691
|
+
}),
|
|
3692
|
+
path: pathFromLocation(window.location),
|
|
3512
3693
|
title,
|
|
3513
3694
|
type: "page"
|
|
3514
3695
|
};
|
|
@@ -3540,9 +3721,136 @@ function useCurrentPersonId() {
|
|
|
3540
3721
|
return userId == null ? undefined : String(userId);
|
|
3541
3722
|
}
|
|
3542
3723
|
|
|
3543
|
-
//
|
|
3544
|
-
//
|
|
3545
|
-
|
|
3724
|
+
// Tokens last 15 minutes. Refreshing a minute early keeps one from expiring
|
|
3725
|
+
// between reading a token and the service checking it.
|
|
3726
|
+
const REFRESH_MARGIN_MS = 60000;
|
|
3727
|
+
const MAX_ATTEMPTS = 3;
|
|
3728
|
+
// A product without the endpoint answers 404 for the rest of the session, so
|
|
3729
|
+
// it's asked once per tab rather than on every page.
|
|
3730
|
+
const UNAVAILABLE_KEY = "topbar.user_store_sync.unavailable";
|
|
3731
|
+
// Per person, so switching connected people never writes one person's visits
|
|
3732
|
+
// with another's token.
|
|
3733
|
+
function tokenKey(personId) {
|
|
3734
|
+
return `topbar.user_store_sync.token.${personId}`;
|
|
3735
|
+
}
|
|
3736
|
+
function readSession(key) {
|
|
3737
|
+
try {
|
|
3738
|
+
return sessionStorage.getItem(key);
|
|
3739
|
+
} catch {
|
|
3740
|
+
return null;
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
function writeSession(key, value) {
|
|
3744
|
+
try {
|
|
3745
|
+
sessionStorage.setItem(key, value);
|
|
3746
|
+
} catch {
|
|
3747
|
+
// Without sessionStorage the next request just mints again.
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
function isUserStoreSyncUnavailable() {
|
|
3751
|
+
return readSession(UNAVAILABLE_KEY) === "true";
|
|
3752
|
+
}
|
|
3753
|
+
function isToken(value) {
|
|
3754
|
+
return typeof value === "object" && value !== null && typeof value.token === "string" && typeof value.url === "string" && typeof value.expiresAt === "number";
|
|
3755
|
+
}
|
|
3756
|
+
function cachedToken(personId) {
|
|
3757
|
+
var _a;
|
|
3758
|
+
try {
|
|
3759
|
+
const token = JSON.parse((_a = readSession(tokenKey(personId))) !== null && _a !== void 0 ? _a : "null");
|
|
3760
|
+
if (isToken(token) && token.expiresAt - REFRESH_MARGIN_MS > Date.now()) {
|
|
3761
|
+
return token;
|
|
3762
|
+
} else {
|
|
3763
|
+
return null;
|
|
3764
|
+
}
|
|
3765
|
+
} catch {
|
|
3766
|
+
return null;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
async function mintToken(personId) {
|
|
3770
|
+
const {
|
|
3771
|
+
json,
|
|
3772
|
+
ok,
|
|
3773
|
+
response
|
|
3774
|
+
} = await apiRequest(railsCsrfFetch, `${window.location.origin}/user_store_sync/token`, {
|
|
3775
|
+
method: "POST"
|
|
3776
|
+
});
|
|
3777
|
+
if (ok && typeof (json === null || json === void 0 ? void 0 : json.token) === "string" && typeof (json === null || json === void 0 ? void 0 : json.url) === "string") {
|
|
3778
|
+
const token = {
|
|
3779
|
+
expiresAt: Date.parse(json.expires_at),
|
|
3780
|
+
token: json.token,
|
|
3781
|
+
url: json.url
|
|
3782
|
+
};
|
|
3783
|
+
writeSession(tokenKey(personId), JSON.stringify(token));
|
|
3784
|
+
return token;
|
|
3785
|
+
} else {
|
|
3786
|
+
if ((response === null || response === void 0 ? void 0 : response.status) === 404) {
|
|
3787
|
+
writeSession(UNAVAILABLE_KEY, "true");
|
|
3788
|
+
}
|
|
3789
|
+
return null;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
async function request(personId, key, init = {}, isRetry = false) {
|
|
3793
|
+
var _a;
|
|
3794
|
+
const token = isRetry ? await mintToken(personId) : (_a = cachedToken(personId)) !== null && _a !== void 0 ? _a : await mintToken(personId);
|
|
3795
|
+
if (token) {
|
|
3796
|
+
const response = await fetch(`${token.url}/v1/data/${key}`, {
|
|
3797
|
+
...init,
|
|
3798
|
+
headers: {
|
|
3799
|
+
...init.headers,
|
|
3800
|
+
Authorization: `Bearer ${token.token}`
|
|
3801
|
+
}
|
|
3802
|
+
});
|
|
3803
|
+
if (response.status === 401 && !isRetry) {
|
|
3804
|
+
return request(personId, key, init, true);
|
|
3805
|
+
} else {
|
|
3806
|
+
return response;
|
|
3807
|
+
}
|
|
3808
|
+
} else {
|
|
3809
|
+
return null;
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
// Applies `change` to the stored value and writes the result, rereading and
|
|
3813
|
+
// retrying when another tab or device wrote in between. `change` returns null
|
|
3814
|
+
// when there's nothing to write. Resolves to the value now stored, or null
|
|
3815
|
+
// when the service couldn't be reached.
|
|
3816
|
+
async function updateStoredValue(personId, key, change, attemptsLeft = MAX_ATTEMPTS) {
|
|
3817
|
+
const read = await request(personId, key);
|
|
3818
|
+
if (read === null || read === void 0 ? void 0 : read.ok) {
|
|
3819
|
+
const {
|
|
3820
|
+
value,
|
|
3821
|
+
version
|
|
3822
|
+
} = await read.json();
|
|
3823
|
+
const next = change(value);
|
|
3824
|
+
if (next === null) {
|
|
3825
|
+
return Array.isArray(value) ? value : [];
|
|
3826
|
+
} else {
|
|
3827
|
+
const write = await request(personId, key, {
|
|
3828
|
+
body: JSON.stringify({
|
|
3829
|
+
value: next
|
|
3830
|
+
}),
|
|
3831
|
+
headers: {
|
|
3832
|
+
"Content-Type": "application/json",
|
|
3833
|
+
"If-Match": `"${version}"`
|
|
3834
|
+
},
|
|
3835
|
+
method: "PUT"
|
|
3836
|
+
});
|
|
3837
|
+
if (write === null || write === void 0 ? void 0 : write.ok) {
|
|
3838
|
+
return next;
|
|
3839
|
+
} else if ((write === null || write === void 0 ? void 0 : write.status) === 409 && attemptsLeft > 1) {
|
|
3840
|
+
return updateStoredValue(personId, key, change, attemptsLeft - 1);
|
|
3841
|
+
} else {
|
|
3842
|
+
return null;
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
} else {
|
|
3846
|
+
return null;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
// Each product keeps its own copy in localStorage, and user-store-sync joins
|
|
3851
|
+
// the copies up across products and devices wherever the product can mint a
|
|
3852
|
+
// token for it.
|
|
3853
|
+
const SYNC_KEY = "recently_viewed";
|
|
3546
3854
|
function storageKey({
|
|
3547
3855
|
env,
|
|
3548
3856
|
personId
|
|
@@ -3556,21 +3864,62 @@ const changes = createStore({
|
|
|
3556
3864
|
});
|
|
3557
3865
|
function readRecentlyVisited(scope) {
|
|
3558
3866
|
try {
|
|
3559
|
-
return parseRecentlyVisited(localStorage.getItem(storageKey(scope))
|
|
3867
|
+
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)));
|
|
3560
3868
|
} catch {
|
|
3561
3869
|
return [];
|
|
3562
3870
|
}
|
|
3563
3871
|
}
|
|
3564
|
-
function
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
changes.
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3872
|
+
function writeRecentlyVisited(scope, entries) {
|
|
3873
|
+
try {
|
|
3874
|
+
localStorage.setItem(storageKey(scope), JSON.stringify(entries));
|
|
3875
|
+
changes.setState({
|
|
3876
|
+
version: changes.getState().version + 1
|
|
3877
|
+
});
|
|
3878
|
+
} catch {
|
|
3879
|
+
// Storage can be full or blocked; recents are disposable.
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
async function syncOnce(scope) {
|
|
3883
|
+
try {
|
|
3884
|
+
const synced = await updateStoredValue(scope.personId, SYNC_KEY, stored => {
|
|
3885
|
+
const shared = entriesFrom(stored);
|
|
3886
|
+
const merged = mergeEntries(shared, readRecentlyVisited(scope));
|
|
3887
|
+
const isUnchanged = Array.isArray(stored) && stored.length === shared.length && isSameList(merged, shared);
|
|
3888
|
+
return isUnchanged ? null : merged;
|
|
3889
|
+
});
|
|
3890
|
+
if (synced) {
|
|
3891
|
+
writeRecentlyVisited(scope, mergeEntries(entriesFrom(synced), readRecentlyVisited(scope)));
|
|
3573
3892
|
}
|
|
3893
|
+
} catch {
|
|
3894
|
+
// The service is best-effort; the local copy stands on its own.
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const syncing = new Map();
|
|
3898
|
+
const syncAgain = new Set();
|
|
3899
|
+
// One sync per list at a time. Asking while one runs schedules a single
|
|
3900
|
+
// follow-up, so a visit recorded mid-sync still reaches the service.
|
|
3901
|
+
function syncRecentlyVisited(scope) {
|
|
3902
|
+
const key = storageKey(scope);
|
|
3903
|
+
const running = syncing.get(key);
|
|
3904
|
+
if (isUserStoreSyncUnavailable()) {
|
|
3905
|
+
return Promise.resolve();
|
|
3906
|
+
} else if (running) {
|
|
3907
|
+
syncAgain.add(key);
|
|
3908
|
+
return running;
|
|
3909
|
+
} else {
|
|
3910
|
+
const run = syncOnce(scope).then(() => {
|
|
3911
|
+
syncing.delete(key);
|
|
3912
|
+
return syncAgain.delete(key) ? syncRecentlyVisited(scope) : undefined;
|
|
3913
|
+
});
|
|
3914
|
+
syncing.set(key, run);
|
|
3915
|
+
return run;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
function recordVisit(visit, scope) {
|
|
3919
|
+
const entry = toEntry(visit, new Date());
|
|
3920
|
+
if (isEntry(entry)) {
|
|
3921
|
+
writeRecentlyVisited(scope, addVisit(readRecentlyVisited(scope), entry));
|
|
3922
|
+
syncRecentlyVisited(scope);
|
|
3574
3923
|
}
|
|
3575
3924
|
}
|
|
3576
3925
|
function useRecentlyVisited({
|
|
@@ -3578,6 +3927,14 @@ function useRecentlyVisited({
|
|
|
3578
3927
|
personId
|
|
3579
3928
|
}) {
|
|
3580
3929
|
const version = shim.useSyncExternalStore(changes.subscribe, () => changes.getState().version);
|
|
3930
|
+
React.useEffect(() => {
|
|
3931
|
+
if (personId) {
|
|
3932
|
+
syncRecentlyVisited({
|
|
3933
|
+
env,
|
|
3934
|
+
personId
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
}, [env, personId]);
|
|
3581
3938
|
// `version` isn't read, but listing it re-reads storage after each visit.
|
|
3582
3939
|
return React.useMemo(() => personId ? readRecentlyVisited({
|
|
3583
3940
|
env,
|
|
@@ -3822,10 +4179,10 @@ function pcrlLookupsUrl(product, pcrls) {
|
|
|
3822
4179
|
});
|
|
3823
4180
|
return `${window.location.origin}/~api/${product}/v2/pcrl_lookups?${params}`;
|
|
3824
4181
|
}
|
|
3825
|
-
// Built from the
|
|
3826
|
-
//
|
|
4182
|
+
// Built from the app rather than the current origin, so it stays right once
|
|
4183
|
+
// PCA-409 shares one list across product subdomains.
|
|
3827
4184
|
function pageUrl(entry, env) {
|
|
3828
|
-
return
|
|
4185
|
+
return new URL(entry.path, pcoUrl(env)(entry.app)).href;
|
|
3829
4186
|
}
|
|
3830
4187
|
|
|
3831
4188
|
var styles$a = {"result":"result-WCnxr"};
|
|
@@ -4018,15 +4375,16 @@ function ShowMoreWhenVisible({
|
|
|
4018
4375
|
// Unlike search results, recents come from every product, so each one names
|
|
4019
4376
|
// its product and type.
|
|
4020
4377
|
function describeVisit(visit) {
|
|
4378
|
+
var _a, _b;
|
|
4021
4379
|
const subtitle = (...parts) => parts.filter(Boolean).join(" · ");
|
|
4022
4380
|
if (visit.status === "page") {
|
|
4023
4381
|
const {
|
|
4024
|
-
|
|
4025
|
-
|
|
4382
|
+
app,
|
|
4383
|
+
metadata,
|
|
4026
4384
|
title
|
|
4027
4385
|
} = visit.entry;
|
|
4028
4386
|
return {
|
|
4029
|
-
subtitle: subtitle(productName(
|
|
4387
|
+
subtitle: subtitle(productName(app), (_a = metadata === null || metadata === void 0 ? void 0 : metadata.type) !== null && _a !== void 0 ? _a : "", (_b = metadata === null || metadata === void 0 ? void 0 : metadata.details) !== null && _b !== void 0 ? _b : ""),
|
|
4030
4388
|
title,
|
|
4031
4389
|
url: visit.url
|
|
4032
4390
|
};
|
|
@@ -4361,13 +4719,15 @@ function SearchButtonAndDialog({
|
|
|
4361
4719
|
const [open, setOpen] = React.useState(false);
|
|
4362
4720
|
React.useEffect(() => {
|
|
4363
4721
|
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
4364
|
-
// window and stopping propagation keeps them from ever seeing it.
|
|
4722
|
+
// window and stopping propagation keeps them from ever seeing it. Under a
|
|
4723
|
+
// product's modal, search stays shut rather than stacking a second modal,
|
|
4724
|
+
// and the keystroke goes on to the product.
|
|
4365
4725
|
const handleKeyDown = event => {
|
|
4366
|
-
if (isSearchShortcut(event)) {
|
|
4726
|
+
if (open && isSearchShortcut(event)) {
|
|
4367
4727
|
event.preventDefault();
|
|
4368
4728
|
event.stopPropagation();
|
|
4369
|
-
setOpen(
|
|
4370
|
-
} else if (isLegacySearchShortcut(event)) {
|
|
4729
|
+
setOpen(false);
|
|
4730
|
+
} else if (!open && (isSearchShortcut(event) || isLegacySearchShortcut(event)) && !isModalOpen()) {
|
|
4371
4731
|
event.preventDefault();
|
|
4372
4732
|
event.stopPropagation();
|
|
4373
4733
|
setOpen(true);
|
|
@@ -4379,7 +4739,7 @@ function SearchButtonAndDialog({
|
|
|
4379
4739
|
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
4380
4740
|
capture: true
|
|
4381
4741
|
});
|
|
4382
|
-
}, []);
|
|
4742
|
+
}, [open]);
|
|
4383
4743
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
|
4384
4744
|
"aria-keyshortcuts": `${isMac() ? "Meta+K" : "Control+K"} /`,
|
|
4385
4745
|
"aria-label": "Search",
|
|
@@ -33694,42 +34054,26 @@ function UserProfile({
|
|
|
33694
34054
|
|
|
33695
34055
|
var ActionType;
|
|
33696
34056
|
(function (ActionType) {
|
|
33697
|
-
ActionType["clearPending"] = "clearPending";
|
|
33698
34057
|
ActionType["close"] = "close";
|
|
33699
34058
|
ActionType["mount"] = "mount";
|
|
33700
34059
|
ActionType["open"] = "open";
|
|
33701
|
-
ActionType["setPending"] = "setPending";
|
|
33702
34060
|
ActionType["unmount"] = "unmount";
|
|
33703
34061
|
})(ActionType || (ActionType = {}));
|
|
33704
34062
|
const initialState = {
|
|
33705
34063
|
currentContent: null,
|
|
33706
|
-
open: false
|
|
33707
|
-
pending: null
|
|
34064
|
+
open: false
|
|
33708
34065
|
};
|
|
33709
34066
|
const reducer = (state, action) => {
|
|
33710
|
-
var _a;
|
|
33711
34067
|
switch (action.type) {
|
|
33712
34068
|
case ActionType.mount:
|
|
33713
34069
|
return {
|
|
33714
34070
|
...state,
|
|
33715
|
-
currentContent: action.payload.content
|
|
33716
|
-
pending: ((_a = state.pending) === null || _a === void 0 ? void 0 : _a.tool) === action.payload.content.name ? state.pending : null
|
|
34071
|
+
currentContent: action.payload.content
|
|
33717
34072
|
};
|
|
33718
34073
|
case ActionType.unmount:
|
|
33719
34074
|
return {
|
|
33720
34075
|
...state,
|
|
33721
|
-
currentContent: null
|
|
33722
|
-
pending: null
|
|
33723
|
-
};
|
|
33724
|
-
case ActionType.setPending:
|
|
33725
|
-
return {
|
|
33726
|
-
...state,
|
|
33727
|
-
pending: action.payload
|
|
33728
|
-
};
|
|
33729
|
-
case ActionType.clearPending:
|
|
33730
|
-
return {
|
|
33731
|
-
...state,
|
|
33732
|
-
pending: null
|
|
34076
|
+
currentContent: null
|
|
33733
34077
|
};
|
|
33734
34078
|
case ActionType.open:
|
|
33735
34079
|
return {
|
|
@@ -33752,7 +34096,7 @@ var styles$1 = {"toolbar":"toolbar-KHJR5","toolbar-button":"toolbar-button-Ye4h5
|
|
|
33752
34096
|
|
|
33753
34097
|
var styles = {"banner":"banner-YfQ37","stacked":"stacked-EwLRh","content":"content-lNjWf","icon":"icon-abz7q","iconShape":"iconShape-6uyot","iconSparkle":"iconSparkle-t0OzK","text":"text--Vlhl","title":"title-zOEOR","subtitle":"subtitle-ufLnk","chevron":"chevron-oOUA-","inlineContent":"inlineContent-h5sVl","sparkle":"sparkle-uuMBp","inlineTitle":"inlineTitle-ru2Rk","action":"action-fSY3n","actionLabel":"actionLabel-MRBjb","actionChevron":"actionChevron-DghlN"};
|
|
33754
34098
|
|
|
33755
|
-
const SPARKLE_PATH
|
|
34099
|
+
const SPARKLE_PATH = "M3.809 10.1332C3.87511 9.95596 4.12579 9.95588 4.19181 10.1332L4.63712 11.3363C4.64169 11.3487 4.65116 11.359 4.66349 11.3637L5.86661 11.809C6.04452 11.8749 6.04455 12.126 5.86661 12.1918L4.66349 12.6371C4.65134 12.6417 4.64173 12.6513 4.63712 12.6635L4.19181 13.8666C4.12597 14.0445 3.87486 14.0445 3.809 13.8666L3.36368 12.6635C3.35905 12.6512 3.3487 12.6417 3.33634 12.6371L2.13321 12.1918C1.95588 12.1258 1.95596 11.8751 2.13321 11.809L3.33634 11.3637C3.34877 11.3591 3.35908 11.3488 3.36368 11.3363L3.809 10.1332ZM8.52091 3.33341C8.68571 2.88899 9.31518 2.88891 9.47989 3.33341L10.5922 6.34122C10.6037 6.37226 10.6285 6.39712 10.6596 6.4086L13.6664 7.52091C14.1113 7.68553 14.1113 8.31528 13.6664 8.47989L10.6596 9.5922C10.6285 9.6037 10.6037 9.6285 10.5922 9.65958L9.47989 12.6664C9.31528 13.1113 8.68553 13.1113 8.52091 12.6664L7.4086 9.65958C7.39712 9.62853 7.37226 9.60371 7.34122 9.5922L4.33341 8.47989C3.88891 8.31518 3.88899 7.68571 4.33341 7.52091L7.34122 6.4086C7.3723 6.3971 7.3971 6.3723 7.4086 6.34122L8.52091 3.33341ZM3.809 2.13321C3.87511 1.95596 4.12579 1.95588 4.19181 2.13321L4.63712 3.33634C4.64169 3.3487 4.65116 3.35905 4.66349 3.36368L5.86661 3.809C6.04452 3.87486 6.04455 4.12597 5.86661 4.19181L4.66349 4.63712C4.65134 4.64173 4.64173 4.65134 4.63712 4.66349L4.19181 5.86661C4.12597 6.04455 3.87486 6.04452 3.809 5.86661L3.36368 4.66349C3.35905 4.65116 3.3487 4.64169 3.33634 4.63712L2.13321 4.19181C1.95588 4.12579 1.95596 3.87511 2.13321 3.809L3.33634 3.36368C3.34877 3.35908 3.35908 3.34877 3.36368 3.33634L3.809 2.13321Z";
|
|
33756
34100
|
// Same stops as the AskAIBanner border gradient, so the two stay in sync.
|
|
33757
34101
|
const GRADIENT_STOPS = [{
|
|
33758
34102
|
color: "var(--t-fill-color-tag-teal-040)",
|
|
@@ -33801,7 +34145,7 @@ function SparkleIcon({
|
|
|
33801
34145
|
stopColor: color
|
|
33802
34146
|
}
|
|
33803
34147
|
})))))), /*#__PURE__*/React.createElement("path", {
|
|
33804
|
-
d: SPARKLE_PATH
|
|
34148
|
+
d: SPARKLE_PATH,
|
|
33805
34149
|
fill: isGradient ? `url(#${gradientId})` : "currentColor"
|
|
33806
34150
|
}));
|
|
33807
34151
|
}
|
|
@@ -34209,24 +34553,16 @@ const DocumentationSearch = ({
|
|
|
34209
34553
|
// not the path, so updating it is a same-document navigation that doesn't
|
|
34210
34554
|
// reload the iframe, but also lets the route survive a forced reload (e.g.
|
|
34211
34555
|
// Turbo re-parenting the topbar).
|
|
34212
|
-
|
|
34213
|
-
// `panel` loads picobot as its own toolbar panel rather than inside Helpdesk.
|
|
34214
|
-
function buildPicobotSrc(origin, path, {
|
|
34215
|
-
panel = false
|
|
34216
|
-
} = {}) {
|
|
34556
|
+
function buildPicobotSrc(origin, path) {
|
|
34217
34557
|
const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
|
|
34218
34558
|
const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
|
|
34219
34559
|
const fragment = queryPart !== undefined ? `${safePath}?${queryPart}` : safePath;
|
|
34220
|
-
const
|
|
34221
|
-
if (panel) url.searchParams.set("panel", "1");
|
|
34222
|
-
const base = url.toString();
|
|
34560
|
+
const base = new URL("topbar", origin).toString();
|
|
34223
34561
|
return fragment ? `${base}#${fragment}` : base;
|
|
34224
34562
|
}
|
|
34225
34563
|
|
|
34226
34564
|
const LOAD_TIMEOUT_MS = 3000;
|
|
34227
|
-
function createPicobotIframe(picobotOrigin
|
|
34228
|
-
panel = false
|
|
34229
|
-
} = {}) {
|
|
34565
|
+
function createPicobotIframe(picobotOrigin) {
|
|
34230
34566
|
return /*#__PURE__*/React.forwardRef(function PicobotIframe({
|
|
34231
34567
|
currentPath: iframePath,
|
|
34232
34568
|
onChangePath: onIframeChangePath,
|
|
@@ -34282,9 +34618,7 @@ function createPicobotIframe(picobotOrigin, {
|
|
|
34282
34618
|
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, picobotOrigin);
|
|
34283
34619
|
}
|
|
34284
34620
|
}), []);
|
|
34285
|
-
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath
|
|
34286
|
-
panel
|
|
34287
|
-
});
|
|
34621
|
+
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath);
|
|
34288
34622
|
return /*#__PURE__*/React.createElement("iframe", {
|
|
34289
34623
|
ref: iframeRef,
|
|
34290
34624
|
src: iframeSrc,
|
|
@@ -34323,28 +34657,22 @@ function HelpdeskComponent({
|
|
|
34323
34657
|
currentPath,
|
|
34324
34658
|
env,
|
|
34325
34659
|
helpdeskConfig,
|
|
34326
|
-
|
|
34327
|
-
onChangePath,
|
|
34328
|
-
openPicobot
|
|
34660
|
+
onChangePath
|
|
34329
34661
|
}) {
|
|
34330
34662
|
const PicobotIframe = React.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot")), [env]);
|
|
34331
|
-
const loadingFallback = /*#__PURE__*/React.createElement(StackView$1, {
|
|
34332
|
-
flex: 1,
|
|
34333
|
-
axis: "vertical"
|
|
34334
|
-
}, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
34335
|
-
closeDrawer: closeDrawer
|
|
34336
|
-
}, /*#__PURE__*/React.createElement(StackView$1, {
|
|
34337
|
-
axis: "horizontal",
|
|
34338
|
-
spacing: 1
|
|
34339
|
-
}, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null))));
|
|
34340
|
-
// The add-on reads its args once, so mounting before the gate answers
|
|
34341
|
-
// would leave out openPicobot for good.
|
|
34342
|
-
if (isPicobotLoading) return loadingFallback;
|
|
34343
34663
|
return /*#__PURE__*/React.createElement(addOns.UiExtensionFirstPartyComponent, {
|
|
34344
34664
|
grow: 1,
|
|
34345
34665
|
display: "flex",
|
|
34346
34666
|
extensionId: env === "staging" ? "42" : "11",
|
|
34347
|
-
loadingFallback:
|
|
34667
|
+
loadingFallback: /*#__PURE__*/React.createElement(StackView$1, {
|
|
34668
|
+
flex: 1,
|
|
34669
|
+
axis: "vertical"
|
|
34670
|
+
}, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
34671
|
+
closeDrawer: closeDrawer
|
|
34672
|
+
}, /*#__PURE__*/React.createElement(StackView$1, {
|
|
34673
|
+
axis: "horizontal",
|
|
34674
|
+
spacing: 1
|
|
34675
|
+
}, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null)))),
|
|
34348
34676
|
additionalComponents: {
|
|
34349
34677
|
AskAIBanner: {
|
|
34350
34678
|
component: AskAIBanner,
|
|
@@ -34442,11 +34770,7 @@ function HelpdeskComponent({
|
|
|
34442
34770
|
currentPageParams: location.search,
|
|
34443
34771
|
currentPath,
|
|
34444
34772
|
helpdeskConfig,
|
|
34445
|
-
onChangePath
|
|
34446
|
-
// Its presence tells helpdesk that Picobot has its own panel.
|
|
34447
|
-
...(openPicobot && {
|
|
34448
|
-
openPicobot
|
|
34449
|
-
})
|
|
34773
|
+
onChangePath
|
|
34450
34774
|
}
|
|
34451
34775
|
});
|
|
34452
34776
|
}
|
|
@@ -34993,142 +35317,6 @@ const Notifications = {
|
|
|
34993
35317
|
name: "Notifications"
|
|
34994
35318
|
};
|
|
34995
35319
|
|
|
34996
|
-
// The sparkle sits clear of the unread dot at (24, 8), so both variants match.
|
|
34997
|
-
const SPARKLE_PATH = "M8.7824 18.8791C8.88371 18.5973 9.26996 18.5973 9.37127 18.8791L9.94256 20.465C10.0222 20.6863 10.1914 20.8616 10.4064 20.9435L11.6779 21.4269L11.9484 21.5304L11.9963 21.5539C12.2205 21.6839 12.2047 22.039 11.9484 22.1369L11.6779 22.2394L10.4064 22.7248C10.1915 22.8068 10.0222 22.9812 9.94256 23.2023L9.37127 24.7893L9.34881 24.8381C9.23098 25.054 8.92273 25.0539 8.80486 24.8381L8.7824 24.7893L8.2111 23.2023C8.14141 23.0088 8.0029 22.8513 7.82536 22.76L7.74723 22.7248L6.47573 22.2394L6.20522 22.1369C5.93172 22.0325 5.93164 21.6347 6.20522 21.5304L6.47476 21.4269L7.74723 20.9435C7.96226 20.8616 8.13147 20.6863 8.2111 20.465L8.7824 18.8791ZM17.5706 8.11138C17.8241 7.40747 18.7928 7.40734 19.0462 8.11138L20.472 12.0782C20.6711 12.6318 21.0952 13.0687 21.6331 13.2735L25.4867 14.7413C26.1711 15.002 26.1711 15.9983 25.4867 16.2589L21.6331 17.7267C21.0952 17.9316 20.6711 18.3684 20.472 18.922L19.2649 22.2785L19.0462 22.8889L18.9905 23.0119C18.6956 23.5517 17.9212 23.5515 17.6262 23.0119L17.5706 22.8889L17.3508 22.2795L16.1448 18.922C15.9457 18.3684 15.5206 17.9316 14.9827 17.7267L11.1291 16.2589C10.4449 15.9982 10.4449 15.0021 11.1291 14.7413L14.9827 13.2735C15.4533 13.0943 15.8379 12.7376 16.0598 12.2804L16.1448 12.0782L17.5706 8.11138ZM18.0266 12.755C17.6351 13.8438 16.7916 14.7249 15.6946 15.1427L14.7551 15.5001L15.6946 15.8575C16.7916 16.2753 17.6351 17.1564 18.0266 18.2453L18.3079 19.0275L18.5891 18.2453C18.9806 17.1564 19.8242 16.2753 20.9212 15.8575L21.8597 15.5001L20.9212 15.1427C19.8242 14.7249 18.9806 13.8439 18.5891 12.755L18.3079 11.9718L18.0266 12.755ZM8.7824 6.21097C8.88388 5.9297 9.26982 5.92966 9.37127 6.21097L9.94256 7.7979C10.0222 8.01911 10.1915 8.19346 10.4064 8.27544L11.6779 8.75982L11.8322 9.16607L11.9484 8.86334C12.2218 8.96779 12.2218 9.36542 11.9484 9.46979L11.6779 9.57233L10.4064 10.0567C10.1914 10.1387 10.0222 10.3139 9.94256 10.5352L9.37127 12.1212C9.26996 12.4029 8.88371 12.4029 8.7824 12.1212L8.2111 10.5352C8.14139 10.3415 8.00304 10.1832 7.82536 10.0919L7.74723 10.0567L6.47573 9.57233L6.20522 9.46979C5.9486 9.37195 5.93265 9.01568 6.15737 8.8858L6.20522 8.86334L7.74723 8.27544C7.96214 8.19346 8.13149 8.0191 8.2111 7.7979L8.69451 6.45413L8.7824 6.21097ZM11.9484 8.86334L11.8322 9.16607L11.6779 8.75982L11.9484 8.86334Z";
|
|
34998
|
-
// Helpdesk reads this context off the email path to open its email form the
|
|
34999
|
-
// same way a handoff inside its own drawer does.
|
|
35000
|
-
function emailPathWithContext(context = {}) {
|
|
35001
|
-
const params = new URLSearchParams();
|
|
35002
|
-
for (const key of ["conversationId", "source", "turnIndex"]) {
|
|
35003
|
-
const value = context[key];
|
|
35004
|
-
if (value !== null && value !== undefined) params.set(key, String(value));
|
|
35005
|
-
}
|
|
35006
|
-
return `email?${params}`;
|
|
35007
|
-
}
|
|
35008
|
-
function PicobotComponent({
|
|
35009
|
-
clearPending,
|
|
35010
|
-
closeDrawer,
|
|
35011
|
-
currentPath,
|
|
35012
|
-
env,
|
|
35013
|
-
onChangePath,
|
|
35014
|
-
openTool,
|
|
35015
|
-
pending
|
|
35016
|
-
}) {
|
|
35017
|
-
const PicobotIframe = React.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot"), {
|
|
35018
|
-
panel: true
|
|
35019
|
-
}), [env]);
|
|
35020
|
-
const picobotRef = React.useRef(null);
|
|
35021
|
-
const [isPicobotReady, setIsPicobotReady] = React.useState(false);
|
|
35022
|
-
// Picobot has no listener until it sends READY, so openPicobot's draft or
|
|
35023
|
-
// conversation waits for it.
|
|
35024
|
-
React.useEffect(function handOverPendingOncePicobotIsReady() {
|
|
35025
|
-
var _a, _b;
|
|
35026
|
-
if (!pending || !isPicobotReady) return;
|
|
35027
|
-
const {
|
|
35028
|
-
conversationId,
|
|
35029
|
-
draft,
|
|
35030
|
-
ticketExternalId
|
|
35031
|
-
} = pending;
|
|
35032
|
-
if (conversationId) {
|
|
35033
|
-
(_a = picobotRef.current) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
35034
|
-
payload: {
|
|
35035
|
-
conversation_id: conversationId,
|
|
35036
|
-
// Picobot asks whether this resolved the ticket, and closes it.
|
|
35037
|
-
...(ticketExternalId && {
|
|
35038
|
-
external_id: ticketExternalId,
|
|
35039
|
-
source: "ticket_submitted"
|
|
35040
|
-
})
|
|
35041
|
-
},
|
|
35042
|
-
type: "LOAD_CONVERSATION"
|
|
35043
|
-
});
|
|
35044
|
-
}
|
|
35045
|
-
if (draft) {
|
|
35046
|
-
(_b = picobotRef.current) === null || _b === void 0 ? void 0 : _b.postMessage({
|
|
35047
|
-
payload: {
|
|
35048
|
-
source: "email_form",
|
|
35049
|
-
text: draft
|
|
35050
|
-
},
|
|
35051
|
-
type: "PREFILL_COMPOSER"
|
|
35052
|
-
});
|
|
35053
|
-
}
|
|
35054
|
-
clearPending();
|
|
35055
|
-
}, [pending, isPicobotReady, clearPending]);
|
|
35056
|
-
const [iframeErrored, setIframeErrored] = React.useState(false);
|
|
35057
|
-
const [mountKey, setMountKey] = React.useState(0);
|
|
35058
|
-
const handleRetry = React.useCallback(() => {
|
|
35059
|
-
setIframeErrored(false);
|
|
35060
|
-
setIsPicobotReady(false);
|
|
35061
|
-
setMountKey(key => key + 1);
|
|
35062
|
-
}, []);
|
|
35063
|
-
const handleMessage = React.useCallback(data => {
|
|
35064
|
-
var _a;
|
|
35065
|
-
const message = data;
|
|
35066
|
-
switch (message === null || message === void 0 ? void 0 : message.type) {
|
|
35067
|
-
case "CLOSE_DRAWER":
|
|
35068
|
-
return closeDrawer();
|
|
35069
|
-
case "READY":
|
|
35070
|
-
return setIsPicobotReady(true);
|
|
35071
|
-
case "PICOBOT_SUPPORT_HANDOFF":
|
|
35072
|
-
return openTool("helpdesk", ((_a = message.payload) === null || _a === void 0 ? void 0 : _a.method) === "zoom" ? "phone" : emailPathWithContext(message.payload));
|
|
35073
|
-
case "IFRAME_LOAD_ERROR":
|
|
35074
|
-
console.warn("topbar: Picobot iframe failed to load");
|
|
35075
|
-
return setIframeErrored(true);
|
|
35076
|
-
}
|
|
35077
|
-
}, [closeDrawer, openTool]);
|
|
35078
|
-
if (iframeErrored) {
|
|
35079
|
-
return /*#__PURE__*/React.createElement(LoadError, {
|
|
35080
|
-
onContactSupport: () => openTool("helpdesk", "email"),
|
|
35081
|
-
onRetry: handleRetry
|
|
35082
|
-
});
|
|
35083
|
-
}
|
|
35084
|
-
return /*#__PURE__*/React.createElement(PicobotIframe, {
|
|
35085
|
-
key: mountKey,
|
|
35086
|
-
ref: picobotRef,
|
|
35087
|
-
currentPath: currentPath,
|
|
35088
|
-
onChangePath: onChangePath,
|
|
35089
|
-
onMessage: handleMessage
|
|
35090
|
-
});
|
|
35091
|
-
}
|
|
35092
|
-
function LoadError({
|
|
35093
|
-
onContactSupport,
|
|
35094
|
-
onRetry
|
|
35095
|
-
}) {
|
|
35096
|
-
return /*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
35097
|
-
axis: "vertical",
|
|
35098
|
-
flex: 1,
|
|
35099
|
-
alignment: "center",
|
|
35100
|
-
distribution: "center",
|
|
35101
|
-
padding: 3,
|
|
35102
|
-
spacing: 1
|
|
35103
|
-
}, /*#__PURE__*/React.createElement(tapestryReact.Heading, {
|
|
35104
|
-
level: 2
|
|
35105
|
-
}, "Picobot is temporarily unavailable"), /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
35106
|
-
size: 5,
|
|
35107
|
-
color: "var(--t-text-color-secondary)"
|
|
35108
|
-
}, "Try again or contact support."), /*#__PURE__*/React.createElement(tapestryReact.Box, {
|
|
35109
|
-
paddingTop: 2
|
|
35110
|
-
}, /*#__PURE__*/React.createElement(tapestryReact.Button, {
|
|
35111
|
-
onClick: onRetry,
|
|
35112
|
-
iconLeft: "general.refresh",
|
|
35113
|
-
theme: "primary",
|
|
35114
|
-
variant: "fill",
|
|
35115
|
-
title: "Retry"
|
|
35116
|
-
})), /*#__PURE__*/React.createElement(tapestryReact.Button, {
|
|
35117
|
-
onClick: onContactSupport,
|
|
35118
|
-
variant: "naked",
|
|
35119
|
-
theme: "primary",
|
|
35120
|
-
title: "Contact support"
|
|
35121
|
-
}));
|
|
35122
|
-
}
|
|
35123
|
-
const Picobot = {
|
|
35124
|
-
component: PicobotComponent,
|
|
35125
|
-
icons: {
|
|
35126
|
-
staticDotPath: SPARKLE_PATH,
|
|
35127
|
-
staticPath: SPARKLE_PATH
|
|
35128
|
-
},
|
|
35129
|
-
name: "Picobot"
|
|
35130
|
-
};
|
|
35131
|
-
|
|
35132
35320
|
const {
|
|
35133
35321
|
LoadingIndicator,
|
|
35134
35322
|
StackView
|
|
@@ -35524,35 +35712,6 @@ const useChatConfig = ({
|
|
|
35524
35712
|
};
|
|
35525
35713
|
};
|
|
35526
35714
|
|
|
35527
|
-
// Picobot's API rejects requests without a version header, and apiRequest
|
|
35528
|
-
// sets its own headers, so the version rides in on the fetch.
|
|
35529
|
-
const picobotFetch = (url, opts) => defaultFetch(url, {
|
|
35530
|
-
...opts,
|
|
35531
|
-
headers: {
|
|
35532
|
-
...(opts === null || opts === void 0 ? void 0 : opts.headers),
|
|
35533
|
-
"X-PCO-API-Version": "2026-08-11"
|
|
35534
|
-
}
|
|
35535
|
-
});
|
|
35536
|
-
const usePicobotConfig = ({
|
|
35537
|
-
isEnabled
|
|
35538
|
-
}) => {
|
|
35539
|
-
// Undefined until Picobot answers.
|
|
35540
|
-
const [panelEnabled, setPanelEnabled] = React.useState();
|
|
35541
|
-
React.useEffect(() => {
|
|
35542
|
-
if (!isEnabled) return;
|
|
35543
|
-
apiRequest(picobotFetch, `${window.location.origin}/~api/picobot/v2/me`).then(({
|
|
35544
|
-
json
|
|
35545
|
-
}) => {
|
|
35546
|
-
var _a, _b;
|
|
35547
|
-
return setPanelEnabled(((_b = (_a = json === null || json === void 0 ? void 0 : json.data) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b.picobot_panel_enabled) === true);
|
|
35548
|
-
}).catch(() => setPanelEnabled(false));
|
|
35549
|
-
}, [isEnabled]);
|
|
35550
|
-
return {
|
|
35551
|
-
displayPicobotIcon: panelEnabled === true,
|
|
35552
|
-
isLoading: isEnabled && panelEnabled === undefined
|
|
35553
|
-
};
|
|
35554
|
-
};
|
|
35555
|
-
|
|
35556
35715
|
const Profile = {
|
|
35557
35716
|
component: UserProfile,
|
|
35558
35717
|
icons: {
|
|
@@ -35563,16 +35722,10 @@ const Profile = {
|
|
|
35563
35722
|
};
|
|
35564
35723
|
|
|
35565
35724
|
const DURATION = 100;
|
|
35566
|
-
// Exempt paths match regardless of query, so `email?conversationId=…` is
|
|
35567
|
-
// exempt along with `email`.
|
|
35568
35725
|
function isToolPathExemptFromStorage(toolName, path) {
|
|
35569
35726
|
var _a, _b;
|
|
35570
35727
|
const tool = toolsRouteMap[toolName];
|
|
35571
|
-
|
|
35572
|
-
return (_b = (_a = tool === null || tool === void 0 ? void 0 : tool.storageExemptPaths) === null || _a === void 0 ? void 0 : _a.includes(pathWithoutQuery)) !== null && _b !== void 0 ? _b : false;
|
|
35573
|
-
}
|
|
35574
|
-
function isConversationPath(path) {
|
|
35575
|
-
return Boolean(path === null || path === void 0 ? void 0 : path.startsWith("conversations/"));
|
|
35728
|
+
return (_b = (_a = tool === null || tool === void 0 ? void 0 : tool.storageExemptPaths) === null || _a === void 0 ? void 0 : _a.includes(path)) !== null && _b !== void 0 ? _b : false;
|
|
35576
35729
|
}
|
|
35577
35730
|
function scrollToolDrawerToTop(toolName) {
|
|
35578
35731
|
var _a;
|
|
@@ -35585,7 +35738,6 @@ const TOOLS_MAP = {
|
|
|
35585
35738
|
Chat,
|
|
35586
35739
|
Helpdesk,
|
|
35587
35740
|
Notifications,
|
|
35588
|
-
Picobot,
|
|
35589
35741
|
Profile,
|
|
35590
35742
|
Tasks
|
|
35591
35743
|
};
|
|
@@ -35613,8 +35765,7 @@ function Toolbar({
|
|
|
35613
35765
|
});
|
|
35614
35766
|
const {
|
|
35615
35767
|
currentContent,
|
|
35616
|
-
open
|
|
35617
|
-
pending
|
|
35768
|
+
open
|
|
35618
35769
|
} = state;
|
|
35619
35770
|
const mounted = !!currentContent;
|
|
35620
35771
|
const storeData = useStore();
|
|
@@ -35627,15 +35778,6 @@ function Toolbar({
|
|
|
35627
35778
|
...(env === "development" ? _storeDataOverride !== null && _storeDataOverride !== void 0 ? _storeDataOverride : {} : {})
|
|
35628
35779
|
};
|
|
35629
35780
|
const updateStore = useUpdateStore();
|
|
35630
|
-
const {
|
|
35631
|
-
displayPicobotIcon,
|
|
35632
|
-
isLoading: isPicobotGateLoading
|
|
35633
|
-
} = usePicobotConfig({
|
|
35634
|
-
isEnabled: Boolean(userId)
|
|
35635
|
-
});
|
|
35636
|
-
// Picobot is a kill-switched tool: until its gate says the panel is
|
|
35637
|
-
// enabled, its routes are held, and if it never does, they're ignored.
|
|
35638
|
-
const routableTool = React.useCallback(name => name === Picobot.name.toLowerCase() && !displayPicobotIcon ? undefined : toolsRouteMap[name], [displayPicobotIcon]);
|
|
35639
35781
|
React.useEffect(() => {
|
|
35640
35782
|
if (open) {
|
|
35641
35783
|
document.body.style.overflow = "hidden";
|
|
@@ -35723,9 +35865,7 @@ function Toolbar({
|
|
|
35723
35865
|
});
|
|
35724
35866
|
}, [clearRoute]);
|
|
35725
35867
|
React.useEffect(() => {
|
|
35726
|
-
if (!toolbarFragmentRoute
|
|
35727
|
-
return;
|
|
35728
|
-
}
|
|
35868
|
+
if (!toolbarFragmentRoute) return;
|
|
35729
35869
|
const isExempt = isToolPathExemptFromStorage(toolbarFragmentRoute.tool, toolbarFragmentRoute.path);
|
|
35730
35870
|
if (isExempt) return;
|
|
35731
35871
|
const nextState = {
|
|
@@ -35735,32 +35875,12 @@ function Toolbar({
|
|
|
35735
35875
|
if (!shallowCompare(nextState, toolStateCookie)) {
|
|
35736
35876
|
setToolStateCookie(nextState);
|
|
35737
35877
|
}
|
|
35738
|
-
}, [toolbarFragmentRoute, toolStateCookie
|
|
35878
|
+
}, [toolbarFragmentRoute, toolStateCookie]);
|
|
35739
35879
|
React.useEffect(() => {
|
|
35740
|
-
|
|
35741
|
-
|
|
35742
|
-
const mayRedirectToPicobot = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path) && (isPicobotGateLoading || displayPicobotIcon);
|
|
35743
|
-
const tool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && !mayRedirectToPicobot && routableTool(toolbarFragmentRoute.tool);
|
|
35744
|
-
if (tool && (currentContent !== tool || !open)) {
|
|
35745
|
-
setToolAndOpenDrawer(tool);
|
|
35880
|
+
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && (currentContent !== toolsRouteMap[toolbarFragmentRoute.tool] || !open)) {
|
|
35881
|
+
setToolAndOpenDrawer(toolsRouteMap[toolbarFragmentRoute.tool]);
|
|
35746
35882
|
}
|
|
35747
|
-
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool,
|
|
35748
|
-
const openTool = React.useCallback((name, path, pendingValue) => {
|
|
35749
|
-
const tool = routableTool(name.toLowerCase());
|
|
35750
|
-
if (!tool) return;
|
|
35751
|
-
dispatch({
|
|
35752
|
-
payload: pendingValue === undefined ? null : {
|
|
35753
|
-
tool: tool.name,
|
|
35754
|
-
value: pendingValue
|
|
35755
|
-
},
|
|
35756
|
-
type: ActionType.setPending
|
|
35757
|
-
});
|
|
35758
|
-
onChangeToolbarFragmentRoute(tool.name.toLowerCase(), path);
|
|
35759
|
-
scrollToolDrawerToTop(tool.name);
|
|
35760
|
-
}, [onChangeToolbarFragmentRoute, routableTool]);
|
|
35761
|
-
const clearPending = React.useCallback(() => dispatch({
|
|
35762
|
-
type: ActionType.clearPending
|
|
35763
|
-
}), []);
|
|
35883
|
+
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, currentContent, open]);
|
|
35764
35884
|
const toggleDrawer = content => {
|
|
35765
35885
|
if (!open) {
|
|
35766
35886
|
return openDrawer(content);
|
|
@@ -35777,47 +35897,12 @@ function Toolbar({
|
|
|
35777
35897
|
joltPromise: storeData === null || storeData === void 0 ? void 0 : storeData.joltPromise,
|
|
35778
35898
|
userId
|
|
35779
35899
|
});
|
|
35780
|
-
|
|
35781
|
-
// send old links and saved paths there once the panel is enabled.
|
|
35782
|
-
React.useEffect(() => {
|
|
35783
|
-
if (!displayPicobotIcon) return;
|
|
35784
|
-
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path)) {
|
|
35785
|
-
onChangeToolbarFragmentRoute(Picobot.name.toLowerCase(), toolbarFragmentRoute.path);
|
|
35786
|
-
}
|
|
35787
|
-
}, [displayPicobotIcon, toolbarFragmentRoute, onChangeToolbarFragmentRoute]);
|
|
35788
|
-
React.useEffect(() => {
|
|
35789
|
-
if (!displayPicobotIcon) return;
|
|
35790
|
-
const {
|
|
35791
|
-
helpdesk,
|
|
35792
|
-
picobot
|
|
35793
|
-
} = toolStateCookie;
|
|
35794
|
-
if (!isConversationPath(helpdesk)) return;
|
|
35795
|
-
setToolStateCookie({
|
|
35796
|
-
...toolStateCookie,
|
|
35797
|
-
helpdesk: "",
|
|
35798
|
-
picobot: picobot || helpdesk
|
|
35799
|
-
});
|
|
35800
|
-
}, [displayPicobotIcon, toolStateCookie]);
|
|
35801
|
-
const openPicobot = React.useCallback(options => openTool(Picobot.name, "", options), [openTool]);
|
|
35802
|
-
// Like Helpdesk's openPicobot arg, this only exists when the panel does.
|
|
35803
|
-
React.useEffect(() => {
|
|
35804
|
-
if (!displayPicobotIcon) return undefined;
|
|
35805
|
-
window.topbar = window.topbar || {};
|
|
35806
|
-
window.topbar.openPicobot = openPicobot;
|
|
35807
|
-
return () => {
|
|
35808
|
-
delete window.topbar.openPicobot;
|
|
35809
|
-
};
|
|
35810
|
-
}, [displayPicobotIcon, openPicobot]);
|
|
35811
|
-
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat, displayPicobotIcon && Picobot].filter(Boolean);
|
|
35900
|
+
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat].filter(Boolean);
|
|
35812
35901
|
const handleToolOnClick = tool => {
|
|
35813
35902
|
toggleDrawer(tool);
|
|
35814
35903
|
scrollToolDrawerToTop(tool.name);
|
|
35815
35904
|
};
|
|
35816
35905
|
const toolSpecificProps = {
|
|
35817
|
-
Helpdesk: {
|
|
35818
|
-
isPicobotLoading: isPicobotGateLoading,
|
|
35819
|
-
openPicobot: displayPicobotIcon ? openPicobot : undefined
|
|
35820
|
-
},
|
|
35821
35906
|
Profile: {
|
|
35822
35907
|
appName,
|
|
35823
35908
|
closeDrawer,
|
|
@@ -35840,7 +35925,6 @@ function Toolbar({
|
|
|
35840
35925
|
const routeIsForCurrentTool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && toolsRouteMap[toolbarFragmentRoute.tool] === currentContent;
|
|
35841
35926
|
const currentToolProps = {
|
|
35842
35927
|
appName,
|
|
35843
|
-
clearPending,
|
|
35844
35928
|
closeDrawer,
|
|
35845
35929
|
currentPath: routeIsForCurrentTool ? toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path : null,
|
|
35846
35930
|
env,
|
|
@@ -35849,8 +35933,6 @@ function Toolbar({
|
|
|
35849
35933
|
joltPromise,
|
|
35850
35934
|
onChangePath,
|
|
35851
35935
|
openDrawer: () => currentContent && openDrawer(currentContent),
|
|
35852
|
-
openTool,
|
|
35853
|
-
pending: (pending === null || pending === void 0 ? void 0 : pending.tool) === (currentContent === null || currentContent === void 0 ? void 0 : currentContent.name) ? pending === null || pending === void 0 ? void 0 : pending.value : undefined,
|
|
35854
35936
|
userId,
|
|
35855
35937
|
...(currentContent ? toolSpecificProps[currentContent.name] : {})
|
|
35856
35938
|
};
|