@planningcenter/topbar 12.3.0 → 12.4.0-rc.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.css +7 -7
- package/dist/topbar.css.map +1 -1
- package/dist/topbar.js +1303 -425
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +1308 -430
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +1308 -430
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +1303 -425
- package/dist/topbar.umd.js.map +1 -1
- package/package.json +4 -3
package/dist/topbar.modern.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as general from '@planningcenter/icons/paths/general';
|
|
|
6
6
|
import Jolt from '@planningcenter/jolt-client';
|
|
7
7
|
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
8
8
|
import 'react/jsx-runtime';
|
|
9
|
-
import { ThemeProvider, Divider, Button as Button$1, StackView as StackView$2, Text, Icon, designSystem, Box, Heading, GridView, Avatar as Avatar$1, Link as Link$1, HeadingUppercase, List, Combobox, Logo, Dropdown, Spinner, Badge } from '@planningcenter/tapestry-react';
|
|
9
|
+
import { ThemeProvider, Divider, Button as Button$1, StackView as StackView$2, Text, Icon as Icon$1, designSystem, Box, Heading, GridView, Avatar as Avatar$1, Link as Link$1, HeadingUppercase, List, Combobox, Logo, Dropdown, Spinner, Badge } from '@planningcenter/tapestry-react';
|
|
10
10
|
import { components, UiExtensionFirstPartyComponent, UiExtensionDataContext } from '@planningcenter/add-ons';
|
|
11
11
|
import * as churchCenter from '@planningcenter/icons/paths/church-center';
|
|
12
12
|
import * as people from '@planningcenter/icons/paths/people';
|
|
@@ -156,12 +156,14 @@ function inferRailsCsrfToken() {
|
|
|
156
156
|
}
|
|
157
157
|
function apiRequest(fetch, url, {
|
|
158
158
|
method = "GET",
|
|
159
|
-
data = null
|
|
159
|
+
data = null,
|
|
160
|
+
headers = {}
|
|
160
161
|
} = {}) {
|
|
161
162
|
const options = {
|
|
162
163
|
headers: {
|
|
163
164
|
Accept: "application/json",
|
|
164
|
-
"Content-Type": "application/json"
|
|
165
|
+
"Content-Type": "application/json",
|
|
166
|
+
...headers
|
|
165
167
|
},
|
|
166
168
|
method,
|
|
167
169
|
redirect: "follow"
|
|
@@ -781,7 +783,7 @@ function useMediaQuery(query) {
|
|
|
781
783
|
return matches;
|
|
782
784
|
}
|
|
783
785
|
|
|
784
|
-
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
786
|
+
const capitalize$1 = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
785
787
|
const RESOLVED_COLOR_MODE_ATTRIBUTE = "data-resolved-color-mode";
|
|
786
788
|
function resolveColorMode(colorMode) {
|
|
787
789
|
if (colorMode === "system") {
|
|
@@ -3305,19 +3307,513 @@ function ProductTabs({
|
|
|
3305
3307
|
}, children));
|
|
3306
3308
|
}
|
|
3307
3309
|
|
|
3310
|
+
function parseValue(element) {
|
|
3311
|
+
const datatype = element.getAttribute("datatype");
|
|
3312
|
+
switch (datatype) {
|
|
3313
|
+
case "boolean":
|
|
3314
|
+
return element.content === "true" || element.content === "1";
|
|
3315
|
+
default:
|
|
3316
|
+
return element.content;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
function getAllPcoMeta() {
|
|
3320
|
+
const elements = document.head.querySelectorAll(`meta[property^="pco:"]`);
|
|
3321
|
+
const meta = {};
|
|
3322
|
+
elements.forEach(e => {
|
|
3323
|
+
const property = e.getAttribute("property");
|
|
3324
|
+
meta[property.substring("pco:".length)] = parseValue(e);
|
|
3325
|
+
});
|
|
3326
|
+
return meta;
|
|
3327
|
+
}
|
|
3328
|
+
// usePcoMeta only reads each tag's content. Recently Visited also needs the
|
|
3329
|
+
// pco:recently_visitable tag's `metadata` attribute, which can change while
|
|
3330
|
+
// the content stays the same, so it is tracked in its own store.
|
|
3331
|
+
function getRecentlyVisitableMetadata() {
|
|
3332
|
+
var _a, _b;
|
|
3333
|
+
return (_b = (_a = document.head.querySelector(`meta[property="pco:recently_visitable"]`)) === null || _a === void 0 ? void 0 : _a.getAttribute("metadata")) !== null && _b !== void 0 ? _b : null;
|
|
3334
|
+
}
|
|
3335
|
+
const store = createStore(getAllPcoMeta());
|
|
3336
|
+
const recentlyVisitableMetadataStore = createStore({
|
|
3337
|
+
metadata: getRecentlyVisitableMetadata()
|
|
3338
|
+
});
|
|
3339
|
+
function sync() {
|
|
3340
|
+
const nextState = getAllPcoMeta();
|
|
3341
|
+
if (!shallowCompare(store.getState(), nextState)) store.setState(nextState);
|
|
3342
|
+
const metadata = getRecentlyVisitableMetadata();
|
|
3343
|
+
if (recentlyVisitableMetadataStore.getState().metadata !== metadata) {
|
|
3344
|
+
recentlyVisitableMetadataStore.setState({
|
|
3345
|
+
metadata
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
const observer = new MutationObserver(sync);
|
|
3350
|
+
let hasTriggeredObserve = false;
|
|
3351
|
+
function observe() {
|
|
3352
|
+
if (hasTriggeredObserve) return;
|
|
3353
|
+
hasTriggeredObserve = true;
|
|
3354
|
+
// The module-load snapshot above can predate meta tags that appear later
|
|
3355
|
+
// in <head> (e.g. pco:resource, yielded right before </head>) if this
|
|
3356
|
+
// module's own script tag isn't deferred and runs mid-parse. Those tags
|
|
3357
|
+
// never mutate afterward, so the observer alone would never catch them.
|
|
3358
|
+
// Re-read once now, since observe() is only called once a consumer is
|
|
3359
|
+
// actually mounting, which is always after the document has fully parsed.
|
|
3360
|
+
sync();
|
|
3361
|
+
observer.observe(document.head, {
|
|
3362
|
+
attributes: true,
|
|
3363
|
+
childList: true,
|
|
3364
|
+
subtree: true
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
function usePcoMeta() {
|
|
3368
|
+
observe();
|
|
3369
|
+
return useSyncExternalStore(store.subscribe, () => store.getState());
|
|
3370
|
+
}
|
|
3371
|
+
function useRecentlyVisitableMetadata() {
|
|
3372
|
+
observe();
|
|
3373
|
+
return useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
const MAX_ENTRIES$1 = 20;
|
|
3377
|
+
const FORMAT_VERSION = 1;
|
|
3378
|
+
const MAX_TEXT_LENGTH = 200;
|
|
3379
|
+
const PRODUCTS = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3380
|
+
function isId(value) {
|
|
3381
|
+
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3382
|
+
}
|
|
3383
|
+
// A PCRL looks like `<product>/v2/<resource_type>/<id>`.
|
|
3384
|
+
function isPcrl(value) {
|
|
3385
|
+
const segments = value.split("/");
|
|
3386
|
+
return segments.length === 4 && PRODUCTS.some(product => product === segments[0]) && isId(segments[3]);
|
|
3387
|
+
}
|
|
3388
|
+
function isNonBlankString(value) {
|
|
3389
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3390
|
+
}
|
|
3391
|
+
function isRecord(value) {
|
|
3392
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3393
|
+
}
|
|
3394
|
+
function entryKey(entry) {
|
|
3395
|
+
if (entry.type === "resource") {
|
|
3396
|
+
return `resource:${entry.pcrl}`;
|
|
3397
|
+
} else {
|
|
3398
|
+
return `page:${entry.product}:${entry.path}`;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
function isEntry(value) {
|
|
3402
|
+
if (!isRecord(value)) {
|
|
3403
|
+
return false;
|
|
3404
|
+
} else if (value.type === "resource") {
|
|
3405
|
+
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3406
|
+
} else if (value.type === "page") {
|
|
3407
|
+
return isNonBlankString(value.product) && isNonBlankString(value.title) && isNonBlankString(value.path) && typeof value.details === "string";
|
|
3408
|
+
} else {
|
|
3409
|
+
return false;
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
// Cuts on code points so a surrogate pair is never split.
|
|
3413
|
+
function clampText(value) {
|
|
3414
|
+
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3415
|
+
}
|
|
3416
|
+
function clampEntry(entry) {
|
|
3417
|
+
if (entry.type === "resource") {
|
|
3418
|
+
return entry;
|
|
3419
|
+
} else {
|
|
3420
|
+
return {
|
|
3421
|
+
...entry,
|
|
3422
|
+
details: clampText(entry.details),
|
|
3423
|
+
title: clampText(entry.title)
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
3428
|
+
// a single bad entry, a list without it) rather than throwing.
|
|
3429
|
+
function parseRecentlyVisited(value, personId) {
|
|
3430
|
+
let parsed = null;
|
|
3431
|
+
try {
|
|
3432
|
+
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
3433
|
+
} catch {
|
|
3434
|
+
parsed = null;
|
|
3435
|
+
}
|
|
3436
|
+
if (isRecord(parsed) && parsed.v === FORMAT_VERSION && parsed.person === personId && Array.isArray(parsed.entries)) {
|
|
3437
|
+
return parsed.entries.filter(isEntry);
|
|
3438
|
+
} else {
|
|
3439
|
+
return [];
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function addVisit(entries, visit) {
|
|
3443
|
+
const key = entryKey(visit);
|
|
3444
|
+
return [clampEntry(visit), ...entries.filter(entry => entryKey(entry) !== key)].slice(0, MAX_ENTRIES$1);
|
|
3445
|
+
}
|
|
3446
|
+
function serializeRecentlyVisited(entries, personId) {
|
|
3447
|
+
return JSON.stringify({
|
|
3448
|
+
entries,
|
|
3449
|
+
person: personId,
|
|
3450
|
+
v: FORMAT_VERSION
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
// The product whose URL, in this environment, has the current page's host.
|
|
3455
|
+
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
3456
|
+
// and guarantees the stored product rebuilds a working link.
|
|
3457
|
+
function currentProduct() {
|
|
3458
|
+
var _a;
|
|
3459
|
+
return (_a = PRODUCTS.find(product => new URL(pcoUrl$2(product)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
3460
|
+
}
|
|
3461
|
+
function detailsFromMetadata(metadata) {
|
|
3462
|
+
var _a;
|
|
3463
|
+
try {
|
|
3464
|
+
const details = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) === null || _a === void 0 ? void 0 : _a.details;
|
|
3465
|
+
return typeof details === "string" ? details : "";
|
|
3466
|
+
} catch {
|
|
3467
|
+
return "";
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
// pco:resource is the more specific signal, so it wins when both tags are
|
|
3471
|
+
// present. A resource that isn't a PCRL is ignored rather than recorded.
|
|
3472
|
+
function visitFromPage({
|
|
3473
|
+
metadata,
|
|
3474
|
+
resource,
|
|
3475
|
+
title
|
|
3476
|
+
}) {
|
|
3477
|
+
const product = currentProduct();
|
|
3478
|
+
if (resource && isPcrl(resource)) {
|
|
3479
|
+
return {
|
|
3480
|
+
pcrl: resource,
|
|
3481
|
+
type: "resource"
|
|
3482
|
+
};
|
|
3483
|
+
} else if (title && title.trim() !== "" && product) {
|
|
3484
|
+
return {
|
|
3485
|
+
details: detailsFromMetadata(metadata),
|
|
3486
|
+
path: window.location.pathname,
|
|
3487
|
+
product,
|
|
3488
|
+
title,
|
|
3489
|
+
type: "page"
|
|
3490
|
+
};
|
|
3491
|
+
} else {
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
// The entry Recently Visited would record for the current page, or null when
|
|
3496
|
+
// the page isn't eligible. Shared by the recorder and the debug panel so the
|
|
3497
|
+
// panel always shows what actually gets stored.
|
|
3498
|
+
function useCurrentVisit() {
|
|
3499
|
+
const {
|
|
3500
|
+
recently_visitable: title,
|
|
3501
|
+
resource
|
|
3502
|
+
} = usePcoMeta();
|
|
3503
|
+
const metadata = useRecentlyVisitableMetadata();
|
|
3504
|
+
return useMemo(() => visitFromPage({
|
|
3505
|
+
metadata,
|
|
3506
|
+
resource,
|
|
3507
|
+
title
|
|
3508
|
+
}), [metadata, resource, title]);
|
|
3509
|
+
}
|
|
3510
|
+
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
3511
|
+
// and several pass it as a number.
|
|
3512
|
+
function useCurrentPersonId() {
|
|
3513
|
+
const {
|
|
3514
|
+
userId
|
|
3515
|
+
} = useStore();
|
|
3516
|
+
return userId == null ? undefined : String(userId);
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
// Per-origin localStorage is a stopgap: each product keeps its own list until
|
|
3520
|
+
// PCA-409 moves this to storage shared across product subdomains. Only this
|
|
3521
|
+
// file should need to change when it does.
|
|
3522
|
+
function storageKey({
|
|
3523
|
+
env,
|
|
3524
|
+
personId
|
|
3525
|
+
}) {
|
|
3526
|
+
return `topbar.recently_visited.${env}.${personId}`;
|
|
3527
|
+
}
|
|
3528
|
+
// localStorage doesn't notify the tab that wrote it, so readers in this tab
|
|
3529
|
+
// learn about new visits from this counter instead.
|
|
3530
|
+
const changes = createStore({
|
|
3531
|
+
version: 0
|
|
3532
|
+
});
|
|
3533
|
+
function readRecentlyVisited(scope) {
|
|
3534
|
+
try {
|
|
3535
|
+
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)), scope.personId);
|
|
3536
|
+
} catch {
|
|
3537
|
+
return [];
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
function recordVisit(visit, scope) {
|
|
3541
|
+
if (isEntry(visit)) {
|
|
3542
|
+
try {
|
|
3543
|
+
localStorage.setItem(storageKey(scope), serializeRecentlyVisited(addVisit(readRecentlyVisited(scope), visit), scope.personId));
|
|
3544
|
+
changes.setState({
|
|
3545
|
+
version: changes.getState().version + 1
|
|
3546
|
+
});
|
|
3547
|
+
} catch {
|
|
3548
|
+
// Storage can be full or blocked; recents are disposable.
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
function useRecentlyVisited({
|
|
3553
|
+
env,
|
|
3554
|
+
personId
|
|
3555
|
+
}) {
|
|
3556
|
+
const version = useSyncExternalStore(changes.subscribe, () => changes.getState().version);
|
|
3557
|
+
// `version` isn't read, but listing it re-reads storage after each visit.
|
|
3558
|
+
return useMemo(() => personId ? readRecentlyVisited({
|
|
3559
|
+
env,
|
|
3560
|
+
personId
|
|
3561
|
+
}) : [], [env, personId, version]);
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
// The versions home-mobile pins, so web and mobile read the same response
|
|
3565
|
+
// shapes. Some endpoints only exist from a version on: Registrations' me,
|
|
3566
|
+
// product_search_results, and pcrl_lookups need 2025-05-01.
|
|
3567
|
+
const API_VERSIONS = {
|
|
3568
|
+
calendar: "2022-07-07",
|
|
3569
|
+
"check-ins": "2025-05-28",
|
|
3570
|
+
giving: "2019-10-18",
|
|
3571
|
+
groups: "2023-07-10",
|
|
3572
|
+
home: "2018-08-01",
|
|
3573
|
+
people: "2023-03-21",
|
|
3574
|
+
publishing: "2024-03-25",
|
|
3575
|
+
registrations: "2025-05-01",
|
|
3576
|
+
services: "2018-11-01"
|
|
3577
|
+
};
|
|
3578
|
+
// For a `/~api/{product}/v2/...` URL, the header that pins its product's
|
|
3579
|
+
// version.
|
|
3580
|
+
function apiVersionHeaders(url) {
|
|
3581
|
+
const [,, product] = new URL(url).pathname.split("/");
|
|
3582
|
+
const version = API_VERSIONS[product];
|
|
3583
|
+
if (version) {
|
|
3584
|
+
return {
|
|
3585
|
+
"X-PCO-API-Version": version
|
|
3586
|
+
};
|
|
3587
|
+
} else {
|
|
3588
|
+
return {};
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
const CACHE_TTL_MS = 60000;
|
|
3593
|
+
const MAX_ENTRIES = 50;
|
|
3594
|
+
const cache = new Map();
|
|
3595
|
+
function cachedRequest(url) {
|
|
3596
|
+
const cached = cache.get(url);
|
|
3597
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
3598
|
+
// Re-inserting keeps the Map in recently-used order for eviction.
|
|
3599
|
+
cache.delete(url);
|
|
3600
|
+
cache.set(url, cached);
|
|
3601
|
+
return cached.promise;
|
|
3602
|
+
} else {
|
|
3603
|
+
return fetchIntoCache(url);
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
function fetchIntoCache(url) {
|
|
3607
|
+
const promise = apiRequest(defaultFetch, url, {
|
|
3608
|
+
headers: apiVersionHeaders(url)
|
|
3609
|
+
}).then(({
|
|
3610
|
+
json,
|
|
3611
|
+
ok
|
|
3612
|
+
}) => {
|
|
3613
|
+
if (!ok || json === null) throw new Error(`Request failed: ${url}`);
|
|
3614
|
+
return json;
|
|
3615
|
+
});
|
|
3616
|
+
// In-flight entries never expire, so every caller shares the one fetch.
|
|
3617
|
+
const entry = {
|
|
3618
|
+
expiresAt: Infinity,
|
|
3619
|
+
promise
|
|
3620
|
+
};
|
|
3621
|
+
cache.set(url, entry);
|
|
3622
|
+
promise.then(() => entry.expiresAt = Date.now() + CACHE_TTL_MS, () => cache.get(url) === entry && cache.delete(url));
|
|
3623
|
+
if (cache.size > MAX_ENTRIES) cache.delete(cache.keys().next().value);
|
|
3624
|
+
return promise;
|
|
3625
|
+
}
|
|
3626
|
+
function useCachedRequest(url) {
|
|
3627
|
+
const [settled, setSettled] = React__default.useState();
|
|
3628
|
+
React__default.useEffect(() => {
|
|
3629
|
+
if (url) {
|
|
3630
|
+
let current = true;
|
|
3631
|
+
cachedRequest(url).then(data => current && setSettled({
|
|
3632
|
+
data,
|
|
3633
|
+
url
|
|
3634
|
+
}), () => current && setSettled({
|
|
3635
|
+
failed: true,
|
|
3636
|
+
url
|
|
3637
|
+
}));
|
|
3638
|
+
return () => {
|
|
3639
|
+
current = false;
|
|
3640
|
+
};
|
|
3641
|
+
} else {
|
|
3642
|
+
return undefined;
|
|
3643
|
+
}
|
|
3644
|
+
}, [url]);
|
|
3645
|
+
if (!url) {
|
|
3646
|
+
return {
|
|
3647
|
+
status: "idle"
|
|
3648
|
+
};
|
|
3649
|
+
} else if ((settled === null || settled === void 0 ? void 0 : settled.url) !== url) {
|
|
3650
|
+
return {
|
|
3651
|
+
data: settled && "data" in settled ? settled.data : undefined,
|
|
3652
|
+
status: "loading"
|
|
3653
|
+
};
|
|
3654
|
+
} else if ("data" in settled) {
|
|
3655
|
+
return {
|
|
3656
|
+
data: settled.data,
|
|
3657
|
+
status: "done"
|
|
3658
|
+
};
|
|
3659
|
+
} else {
|
|
3660
|
+
return {
|
|
3661
|
+
status: "error"
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
// One request per URL, for callers that fan out to a varying number of
|
|
3666
|
+
// endpoints. Unlike `useCachedRequest`, a URL that hasn't settled yet has no
|
|
3667
|
+
// earlier data to fall back on.
|
|
3668
|
+
function useCachedRequests(urls) {
|
|
3669
|
+
const [settled, setSettled] = React__default.useState({});
|
|
3670
|
+
// Keyed by value because each settle re-renders the caller, and a caller
|
|
3671
|
+
// that builds a fresh `urls` array on render would otherwise loop forever.
|
|
3672
|
+
const key = urls.join("\n");
|
|
3673
|
+
React__default.useEffect(() => {
|
|
3674
|
+
let current = true;
|
|
3675
|
+
const settle = result => current && setSettled(prev => ({
|
|
3676
|
+
...prev,
|
|
3677
|
+
[result.url]: result
|
|
3678
|
+
}));
|
|
3679
|
+
for (const url of key.split("\n").filter(Boolean)) {
|
|
3680
|
+
cachedRequest(url).then(data => settle({
|
|
3681
|
+
data,
|
|
3682
|
+
url
|
|
3683
|
+
}), () => settle({
|
|
3684
|
+
failed: true,
|
|
3685
|
+
url
|
|
3686
|
+
}));
|
|
3687
|
+
}
|
|
3688
|
+
return () => {
|
|
3689
|
+
current = false;
|
|
3690
|
+
};
|
|
3691
|
+
}, [key]);
|
|
3692
|
+
return urls.map(url => {
|
|
3693
|
+
const result = settled[url];
|
|
3694
|
+
if (!result) {
|
|
3695
|
+
return {
|
|
3696
|
+
status: "loading"
|
|
3697
|
+
};
|
|
3698
|
+
} else if ("data" in result) {
|
|
3699
|
+
return {
|
|
3700
|
+
data: result.data,
|
|
3701
|
+
status: "done"
|
|
3702
|
+
};
|
|
3703
|
+
} else {
|
|
3704
|
+
return {
|
|
3705
|
+
status: "error"
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
const pcoUrl = pcoUrl$1;
|
|
3712
|
+
|
|
3713
|
+
// Resolves resource entries with one pcrl_lookups request per product.
|
|
3714
|
+
// pcrl_lookups reports forbidden and deleted resources the same way, so both
|
|
3715
|
+
// become `unresolved`, as does every entry in a batch whose request failed.
|
|
3716
|
+
function useHydratedRecentlyVisited(entries, env) {
|
|
3717
|
+
const batches = React__default.useMemo(() => batchByProduct(entries), [entries]);
|
|
3718
|
+
const urls = React__default.useMemo(() => batches.map(([product, pcrls]) => pcrlLookupsUrl(product, pcrls)), [batches]);
|
|
3719
|
+
const requests = useCachedRequests(urls);
|
|
3720
|
+
const lookups = new Map();
|
|
3721
|
+
batches.forEach(([, pcrls], index) => {
|
|
3722
|
+
const request = requests[index];
|
|
3723
|
+
for (const pcrl of pcrls) lookups.set(pcrl, lookupFor(pcrl, request));
|
|
3724
|
+
});
|
|
3725
|
+
return entries.map(entry => {
|
|
3726
|
+
var _a;
|
|
3727
|
+
if (entry.type === "page") {
|
|
3728
|
+
return {
|
|
3729
|
+
entry,
|
|
3730
|
+
status: "page",
|
|
3731
|
+
url: pageUrl(entry, env)
|
|
3732
|
+
};
|
|
3733
|
+
} else {
|
|
3734
|
+
const lookup = (_a = lookups.get(entry.pcrl)) !== null && _a !== void 0 ? _a : {
|
|
3735
|
+
status: "unresolved"
|
|
3736
|
+
};
|
|
3737
|
+
if (lookup.status === "resolved") {
|
|
3738
|
+
return {
|
|
3739
|
+
...lookup,
|
|
3740
|
+
entry,
|
|
3741
|
+
url: lookup.result.attributes.url
|
|
3742
|
+
};
|
|
3743
|
+
} else {
|
|
3744
|
+
return {
|
|
3745
|
+
entry,
|
|
3746
|
+
status: lookup.status
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
});
|
|
3751
|
+
}
|
|
3752
|
+
function lookupFor(pcrl, request) {
|
|
3753
|
+
if (request.status === "done") {
|
|
3754
|
+
const result = request.data.data.find(({
|
|
3755
|
+
attributes
|
|
3756
|
+
}) => attributes.pcrl === pcrl);
|
|
3757
|
+
return result ? {
|
|
3758
|
+
result,
|
|
3759
|
+
status: "resolved"
|
|
3760
|
+
} : {
|
|
3761
|
+
status: "unresolved"
|
|
3762
|
+
};
|
|
3763
|
+
} else if (request.status === "error") {
|
|
3764
|
+
return {
|
|
3765
|
+
status: "unresolved"
|
|
3766
|
+
};
|
|
3767
|
+
} else {
|
|
3768
|
+
return {
|
|
3769
|
+
status: "loading"
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
const RECENTLY_VISITED_PAGE_SIZE = 10;
|
|
3774
|
+
// A PCRL can only be looked up through its own product's API, which may not be
|
|
3775
|
+
// the product it was visited in. Each page of entries is batched on its own,
|
|
3776
|
+
// so loading the next page leaves earlier pages' URLs, and so their cache
|
|
3777
|
+
// keys, alone. Sorting keeps them stable as a page reorders.
|
|
3778
|
+
function batchByProduct(entries) {
|
|
3779
|
+
var _a;
|
|
3780
|
+
const batches = [];
|
|
3781
|
+
for (let start = 0; start < entries.length; start += RECENTLY_VISITED_PAGE_SIZE) {
|
|
3782
|
+
const page = new Map();
|
|
3783
|
+
for (const entry of entries.slice(start, start + RECENTLY_VISITED_PAGE_SIZE)) {
|
|
3784
|
+
if (entry.type === "resource") {
|
|
3785
|
+
const [product] = entry.pcrl.split("/");
|
|
3786
|
+
page.set(product, [...((_a = page.get(product)) !== null && _a !== void 0 ? _a : []), entry.pcrl]);
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
for (const [product, pcrls] of page) {
|
|
3790
|
+
batches.push([product, [...pcrls].sort()]);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
return batches;
|
|
3794
|
+
}
|
|
3795
|
+
function pcrlLookupsUrl(product, pcrls) {
|
|
3796
|
+
const params = new URLSearchParams({
|
|
3797
|
+
"where[pcrl]": pcrls.join(",")
|
|
3798
|
+
});
|
|
3799
|
+
return `${window.location.origin}/~api/${product}/v2/pcrl_lookups?${params}`;
|
|
3800
|
+
}
|
|
3801
|
+
// Built from the product rather than the current origin, so it stays right
|
|
3802
|
+
// once PCA-409 shares one list across product subdomains.
|
|
3803
|
+
function pageUrl(entry, env) {
|
|
3804
|
+
return `${pcoUrl(env)(entry.product)}${entry.path}`;
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3308
3807
|
var styles$a = {"result":"result-WCnxr"};
|
|
3309
3808
|
|
|
3310
3809
|
function SearchResult({
|
|
3311
3810
|
highlighted,
|
|
3312
3811
|
id,
|
|
3313
3812
|
onHighlight,
|
|
3314
|
-
|
|
3813
|
+
subtitle,
|
|
3814
|
+
title,
|
|
3815
|
+
url
|
|
3315
3816
|
}) {
|
|
3316
|
-
const {
|
|
3317
|
-
metadata,
|
|
3318
|
-
title,
|
|
3319
|
-
url
|
|
3320
|
-
} = result.attributes;
|
|
3321
3817
|
const ref = React__default.useRef(null);
|
|
3322
3818
|
React__default.useEffect(() => {
|
|
3323
3819
|
var _a;
|
|
@@ -3338,10 +3834,7 @@ function SearchResult({
|
|
|
3338
3834
|
ref: ref,
|
|
3339
3835
|
role: "option",
|
|
3340
3836
|
tabIndex: -1
|
|
3341
|
-
}, /*#__PURE__*/React__default.createElement("div", null, title), /*#__PURE__*/React__default.createElement("div", null,
|
|
3342
|
-
}
|
|
3343
|
-
function summarize(metadata) {
|
|
3344
|
-
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
3837
|
+
}, /*#__PURE__*/React__default.createElement("div", null, title), /*#__PURE__*/React__default.createElement("div", null, subtitle));
|
|
3345
3838
|
}
|
|
3346
3839
|
|
|
3347
3840
|
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
@@ -3413,83 +3906,127 @@ function ResultGroups({
|
|
|
3413
3906
|
id: resultId(id, resultIndex),
|
|
3414
3907
|
key: result.id,
|
|
3415
3908
|
onHighlight: () => onHighlight(resultIndex),
|
|
3416
|
-
|
|
3909
|
+
subtitle: summarize(result.attributes.metadata),
|
|
3910
|
+
title: result.attributes.title,
|
|
3911
|
+
url: result.attributes.url
|
|
3417
3912
|
});
|
|
3418
3913
|
}));
|
|
3419
3914
|
}));
|
|
3420
3915
|
}
|
|
3916
|
+
function summarize(metadata) {
|
|
3917
|
+
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
3918
|
+
}
|
|
3421
3919
|
|
|
3422
|
-
|
|
3423
|
-
const
|
|
3424
|
-
const
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3920
|
+
function useRecentVisits(env) {
|
|
3921
|
+
const personId = useCurrentPersonId();
|
|
3922
|
+
const entries = useRecentlyVisited({
|
|
3923
|
+
env,
|
|
3924
|
+
personId
|
|
3925
|
+
});
|
|
3926
|
+
const [count, setCount] = React__default.useState(RECENTLY_VISITED_PAGE_SIZE);
|
|
3927
|
+
const shown = React__default.useMemo(() => entries.slice(0, count), [entries, count]);
|
|
3928
|
+
const hydrated = useHydratedRecentlyVisited(shown, env);
|
|
3929
|
+
// Resources show up once they're found. Ones that can't be found anymore,
|
|
3930
|
+
// or that the person can no longer see, are left out.
|
|
3931
|
+
const visits = hydrated.filter(isShown);
|
|
3932
|
+
const loading = hydrated.some(({
|
|
3933
|
+
status
|
|
3934
|
+
}) => status === "loading");
|
|
3935
|
+
const showMore = React__default.useCallback(() => setCount(current => current + RECENTLY_VISITED_PAGE_SIZE), []);
|
|
3936
|
+
return {
|
|
3937
|
+
count,
|
|
3938
|
+
hasMore: entries.length > count,
|
|
3939
|
+
loading,
|
|
3940
|
+
showMore,
|
|
3941
|
+
visits
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
function isShown(visit) {
|
|
3945
|
+
return visit.status === "page" || visit.status === "resolved";
|
|
3946
|
+
}
|
|
3947
|
+
function RecentlyVisited({
|
|
3948
|
+
count,
|
|
3949
|
+
hasMore,
|
|
3950
|
+
highlightedIndex,
|
|
3951
|
+
id,
|
|
3952
|
+
loading,
|
|
3953
|
+
onHighlight,
|
|
3954
|
+
showMore,
|
|
3955
|
+
visits
|
|
3956
|
+
}) {
|
|
3957
|
+
const headingId = `${id}-heading`;
|
|
3958
|
+
if (visits.length === 0 && !hasMore && !loading) {
|
|
3959
|
+
return /*#__PURE__*/React__default.createElement("p", null, "Pages you visit will show up here.");
|
|
3432
3960
|
} else {
|
|
3433
|
-
return
|
|
3961
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
3962
|
+
id: headingId
|
|
3963
|
+
}, "Recently visited"), /*#__PURE__*/React__default.createElement("div", {
|
|
3964
|
+
"aria-labelledby": headingId,
|
|
3965
|
+
id: id,
|
|
3966
|
+
role: "listbox"
|
|
3967
|
+
}, visits.map((visit, index) => (/*#__PURE__*/React__default.createElement(SearchResult, {
|
|
3968
|
+
highlighted: index === highlightedIndex,
|
|
3969
|
+
id: resultId(id, index),
|
|
3970
|
+
key: entryKey(visit.entry),
|
|
3971
|
+
onHighlight: () => onHighlight(index),
|
|
3972
|
+
...describeVisit(visit)
|
|
3973
|
+
})))), hasMore && /*#__PURE__*/React__default.createElement(ShowMoreWhenVisible, {
|
|
3974
|
+
key: count,
|
|
3975
|
+
onVisible: showMore
|
|
3976
|
+
}));
|
|
3434
3977
|
}
|
|
3435
3978
|
}
|
|
3436
|
-
function
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3979
|
+
function ShowMoreWhenVisible({
|
|
3980
|
+
onVisible
|
|
3981
|
+
}) {
|
|
3982
|
+
const ref = React__default.useRef(null);
|
|
3983
|
+
React__default.useEffect(() => {
|
|
3984
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
3985
|
+
if (entry.isIntersecting) onVisible();
|
|
3986
|
+
});
|
|
3987
|
+
if (ref.current) observer.observe(ref.current);
|
|
3988
|
+
return () => observer.disconnect();
|
|
3989
|
+
}, [onVisible]);
|
|
3990
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
3991
|
+
ref: ref
|
|
3443
3992
|
});
|
|
3444
|
-
// In-flight entries never expire, so every caller shares the one fetch.
|
|
3445
|
-
const entry = {
|
|
3446
|
-
expiresAt: Infinity,
|
|
3447
|
-
promise
|
|
3448
|
-
};
|
|
3449
|
-
cache.set(url, entry);
|
|
3450
|
-
promise.then(() => entry.expiresAt = Date.now() + CACHE_TTL_MS, () => cache.get(url) === entry && cache.delete(url));
|
|
3451
|
-
if (cache.size > MAX_ENTRIES$1) cache.delete(cache.keys().next().value);
|
|
3452
|
-
return promise;
|
|
3453
3993
|
}
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
url
|
|
3465
|
-
}));
|
|
3466
|
-
return () => {
|
|
3467
|
-
current = false;
|
|
3468
|
-
};
|
|
3469
|
-
} else {
|
|
3470
|
-
return undefined;
|
|
3471
|
-
}
|
|
3472
|
-
}, [url]);
|
|
3473
|
-
if (!url) {
|
|
3474
|
-
return {
|
|
3475
|
-
status: "idle"
|
|
3476
|
-
};
|
|
3477
|
-
} else if ((settled === null || settled === void 0 ? void 0 : settled.url) !== url) {
|
|
3478
|
-
return {
|
|
3479
|
-
data: settled && "data" in settled ? settled.data : undefined,
|
|
3480
|
-
status: "loading"
|
|
3481
|
-
};
|
|
3482
|
-
} else if ("data" in settled) {
|
|
3994
|
+
// Unlike search results, recents come from every product, so each one names
|
|
3995
|
+
// its product and type.
|
|
3996
|
+
function describeVisit(visit) {
|
|
3997
|
+
const subtitle = (...parts) => parts.filter(Boolean).join(" · ");
|
|
3998
|
+
if (visit.status === "page") {
|
|
3999
|
+
const {
|
|
4000
|
+
details,
|
|
4001
|
+
product,
|
|
4002
|
+
title
|
|
4003
|
+
} = visit.entry;
|
|
3483
4004
|
return {
|
|
3484
|
-
|
|
3485
|
-
|
|
4005
|
+
subtitle: subtitle(productName(product), details),
|
|
4006
|
+
title,
|
|
4007
|
+
url: visit.url
|
|
3486
4008
|
};
|
|
3487
4009
|
} else {
|
|
4010
|
+
const {
|
|
4011
|
+
metadata,
|
|
4012
|
+
product,
|
|
4013
|
+
title,
|
|
4014
|
+
type
|
|
4015
|
+
} = visit.result.attributes;
|
|
3488
4016
|
return {
|
|
3489
|
-
|
|
4017
|
+
subtitle: subtitle(productName(product), type, summarize(metadata)),
|
|
4018
|
+
title,
|
|
4019
|
+
url: visit.url
|
|
3490
4020
|
};
|
|
3491
4021
|
}
|
|
3492
4022
|
}
|
|
4023
|
+
// check-ins → Check-Ins
|
|
4024
|
+
function productName(slug) {
|
|
4025
|
+
return slug.split("-").map(capitalize).join("-");
|
|
4026
|
+
}
|
|
4027
|
+
function capitalize(word) {
|
|
4028
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
4029
|
+
}
|
|
3493
4030
|
|
|
3494
4031
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
3495
4032
|
function useProductSearch(product, term) {
|
|
@@ -3580,15 +4117,16 @@ const verticalStep = key => ({
|
|
|
3580
4117
|
ArrowDown: 1,
|
|
3581
4118
|
ArrowUp: -1
|
|
3582
4119
|
})[key];
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
4120
|
+
// Remembering which list the index belongs to puts the highlight back on the
|
|
4121
|
+
// first option whenever a new list replaces it, without an effect to reset it.
|
|
4122
|
+
// A list that grows or fills in under the same `list` keeps its highlight.
|
|
4123
|
+
function useResultNavigation(options, list) {
|
|
3586
4124
|
const [highlight, setHighlight] = React__default.useState({
|
|
3587
4125
|
index: 0,
|
|
3588
|
-
|
|
4126
|
+
list
|
|
3589
4127
|
});
|
|
3590
|
-
const highlightedIndex = highlight.
|
|
3591
|
-
const highlighted =
|
|
4128
|
+
const highlightedIndex = highlight.list === list ? highlight.index : 0;
|
|
4129
|
+
const highlighted = options[highlightedIndex];
|
|
3592
4130
|
const [copiedUrl, setCopiedUrl] = React__default.useState(null);
|
|
3593
4131
|
React__default.useEffect(() => {
|
|
3594
4132
|
if (copiedUrl) {
|
|
@@ -3598,30 +4136,30 @@ function useResultNavigation(results) {
|
|
|
3598
4136
|
return undefined;
|
|
3599
4137
|
}
|
|
3600
4138
|
}, [copiedUrl]);
|
|
3601
|
-
const highlightIndex = React__default.useCallback(index => setHighlight(current => current.
|
|
4139
|
+
const highlightIndex = React__default.useCallback(index => setHighlight(current => current.list === list && current.index === index ? current : {
|
|
3602
4140
|
index,
|
|
3603
|
-
|
|
3604
|
-
}), [
|
|
4141
|
+
list
|
|
4142
|
+
}), [list]);
|
|
3605
4143
|
const handleKeyDown = event => {
|
|
3606
4144
|
const step = verticalStep(event.key);
|
|
3607
|
-
if (step &&
|
|
4145
|
+
if (step && options.length > 0) {
|
|
3608
4146
|
event.preventDefault();
|
|
3609
|
-
highlightIndex((highlightedIndex + step +
|
|
4147
|
+
highlightIndex((highlightedIndex + step + options.length) % options.length);
|
|
3610
4148
|
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
3611
4149
|
event.preventDefault();
|
|
3612
|
-
open(highlighted.
|
|
4150
|
+
open(highlighted.url, {
|
|
3613
4151
|
newTab: hasCommandKey(event)
|
|
3614
4152
|
});
|
|
3615
4153
|
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
3616
4154
|
event.preventDefault();
|
|
3617
4155
|
const {
|
|
3618
4156
|
url
|
|
3619
|
-
} = highlighted
|
|
4157
|
+
} = highlighted;
|
|
3620
4158
|
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
3621
4159
|
}
|
|
3622
4160
|
};
|
|
3623
4161
|
return {
|
|
3624
|
-
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.
|
|
4162
|
+
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.url),
|
|
3625
4163
|
handleKeyDown,
|
|
3626
4164
|
highlightIndex,
|
|
3627
4165
|
highlighted,
|
|
@@ -3646,29 +4184,66 @@ function hasSelectedText(element) {
|
|
|
3646
4184
|
}
|
|
3647
4185
|
|
|
3648
4186
|
const SEARCHABLE_PRODUCTS = [{
|
|
4187
|
+
appName: "Calendar",
|
|
4188
|
+
slug: "calendar"
|
|
4189
|
+
}, {
|
|
4190
|
+
appName: "Check-Ins",
|
|
4191
|
+
slug: "check-ins"
|
|
4192
|
+
}, {
|
|
4193
|
+
appName: "Giving",
|
|
4194
|
+
slug: "giving"
|
|
4195
|
+
}, {
|
|
3649
4196
|
appName: "Groups",
|
|
3650
4197
|
slug: "groups"
|
|
3651
4198
|
}, {
|
|
3652
4199
|
appName: "People",
|
|
3653
4200
|
slug: "people"
|
|
4201
|
+
}, {
|
|
4202
|
+
appName: "Publishing",
|
|
4203
|
+
slug: "publishing"
|
|
4204
|
+
}, {
|
|
4205
|
+
appName: "Registrations",
|
|
4206
|
+
slug: "registrations"
|
|
3654
4207
|
}, {
|
|
3655
4208
|
appName: "Services",
|
|
3656
4209
|
slug: "services"
|
|
3657
4210
|
}];
|
|
3658
4211
|
const PERMISSION_CHECKS = {
|
|
4212
|
+
"check-ins": () => fetchMe("check-ins", "permission").then(({
|
|
4213
|
+
permission
|
|
4214
|
+
}) => permission !== null),
|
|
4215
|
+
giving: personId => {
|
|
4216
|
+
if (personId === undefined) {
|
|
4217
|
+
return Promise.reject(new Error("No person to check Giving access for"));
|
|
4218
|
+
} else {
|
|
4219
|
+
return fetchAttributes(`giving/v2/person_permissions/${personId}`, {
|
|
4220
|
+
"fields[PersonPermissions]": "can_search"
|
|
4221
|
+
}).then(({
|
|
4222
|
+
can_search
|
|
4223
|
+
}) => can_search);
|
|
4224
|
+
}
|
|
4225
|
+
},
|
|
3659
4226
|
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
3660
4227
|
people_permissions,
|
|
3661
4228
|
site_administrator
|
|
3662
4229
|
}) => site_administrator || people_permissions !== null),
|
|
4230
|
+
registrations: () => fetchMe("registrations", "can_search").then(({
|
|
4231
|
+
can_search
|
|
4232
|
+
}) => can_search),
|
|
3663
4233
|
services: () => fetchMe("services", "can_search").then(({
|
|
3664
4234
|
can_search
|
|
3665
4235
|
}) => can_search)
|
|
3666
4236
|
};
|
|
3667
4237
|
function fetchMe(product, fields) {
|
|
3668
|
-
|
|
4238
|
+
return fetchAttributes(`${product}/v2/me`, {
|
|
3669
4239
|
"fields[Person]": fields
|
|
3670
4240
|
});
|
|
3671
|
-
|
|
4241
|
+
}
|
|
4242
|
+
function fetchAttributes(path, params) {
|
|
4243
|
+
const url = `${window.location.origin}/~api/${path}?${new URLSearchParams(params)}`;
|
|
4244
|
+
return apiRequest(defaultFetch, url, {
|
|
4245
|
+
headers: apiVersionHeaders(url)
|
|
4246
|
+
}).then(({
|
|
3672
4247
|
json,
|
|
3673
4248
|
ok
|
|
3674
4249
|
}) => {
|
|
@@ -3679,7 +4254,7 @@ function fetchMe(product, fields) {
|
|
|
3679
4254
|
// Permissions rarely change mid-session, so a successful check is kept until
|
|
3680
4255
|
// the page reloads. A failed one is dropped so a remount tries again.
|
|
3681
4256
|
const permissionCache = new Map();
|
|
3682
|
-
function canSearch(slug) {
|
|
4257
|
+
function canSearch(slug, personId) {
|
|
3683
4258
|
const check = PERMISSION_CHECKS[slug];
|
|
3684
4259
|
const cached = permissionCache.get(slug);
|
|
3685
4260
|
if (!check) {
|
|
@@ -3687,7 +4262,7 @@ function canSearch(slug) {
|
|
|
3687
4262
|
} else if (cached) {
|
|
3688
4263
|
return cached;
|
|
3689
4264
|
} else {
|
|
3690
|
-
const promise = check().catch(() => {
|
|
4265
|
+
const promise = check(personId).catch(() => {
|
|
3691
4266
|
permissionCache.delete(slug);
|
|
3692
4267
|
return false;
|
|
3693
4268
|
});
|
|
@@ -3701,16 +4276,18 @@ function useSearchableProducts(apps) {
|
|
|
3701
4276
|
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
3702
4277
|
slug
|
|
3703
4278
|
}) => slug).join(",");
|
|
4279
|
+
const personId = useCurrentPersonId();
|
|
3704
4280
|
const [settled, setSettled] = React__default.useState();
|
|
3705
4281
|
React__default.useEffect(() => {
|
|
3706
4282
|
let current = true;
|
|
3707
4283
|
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
3708
4284
|
Promise.all(accessible.map(({
|
|
3709
4285
|
slug
|
|
3710
|
-
}) => canSearch(slug))).then(allowed => {
|
|
4286
|
+
}) => canSearch(slug, personId))).then(allowed => {
|
|
3711
4287
|
if (current) {
|
|
3712
4288
|
setSettled({
|
|
3713
4289
|
accessibleKey,
|
|
4290
|
+
personId,
|
|
3714
4291
|
products: accessible.filter((_, index) => allowed[index])
|
|
3715
4292
|
});
|
|
3716
4293
|
}
|
|
@@ -3718,8 +4295,8 @@ function useSearchableProducts(apps) {
|
|
|
3718
4295
|
return () => {
|
|
3719
4296
|
current = false;
|
|
3720
4297
|
};
|
|
3721
|
-
}, [accessibleKey]);
|
|
3722
|
-
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey) {
|
|
4298
|
+
}, [accessibleKey, personId]);
|
|
4299
|
+
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey && settled.personId === personId) {
|
|
3723
4300
|
return {
|
|
3724
4301
|
products: settled.products,
|
|
3725
4302
|
status: "done"
|
|
@@ -3740,11 +4317,13 @@ const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !e
|
|
|
3740
4317
|
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3741
4318
|
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3742
4319
|
function ProductSearch({
|
|
3743
|
-
apps
|
|
4320
|
+
apps,
|
|
4321
|
+
env
|
|
3744
4322
|
}) {
|
|
3745
4323
|
const searchable = useSearchableProducts(apps);
|
|
3746
4324
|
if (searchable.status === "done" && searchable.products.length > 0) {
|
|
3747
4325
|
return /*#__PURE__*/React__default.createElement(SearchButtonAndDialog, {
|
|
4326
|
+
env: env,
|
|
3748
4327
|
products: searchable.products
|
|
3749
4328
|
});
|
|
3750
4329
|
} else {
|
|
@@ -3752,6 +4331,7 @@ function ProductSearch({
|
|
|
3752
4331
|
}
|
|
3753
4332
|
}
|
|
3754
4333
|
function SearchButtonAndDialog({
|
|
4334
|
+
env,
|
|
3755
4335
|
products
|
|
3756
4336
|
}) {
|
|
3757
4337
|
const [open, setOpen] = React__default.useState(false);
|
|
@@ -3789,10 +4369,15 @@ function SearchButtonAndDialog({
|
|
|
3789
4369
|
onClose: () => setOpen(false),
|
|
3790
4370
|
open: open
|
|
3791
4371
|
}, /*#__PURE__*/React__default.createElement(SearchPanel, {
|
|
4372
|
+
env: env,
|
|
3792
4373
|
products: products
|
|
3793
4374
|
})));
|
|
3794
4375
|
}
|
|
4376
|
+
// Keys the recents list for navigation, so the highlight stays put as more
|
|
4377
|
+
// recents load and as their lookups answer.
|
|
4378
|
+
const RECENTS = "recents";
|
|
3795
4379
|
function SearchPanel({
|
|
4380
|
+
env,
|
|
3796
4381
|
products
|
|
3797
4382
|
}) {
|
|
3798
4383
|
var _a, _b;
|
|
@@ -3809,7 +4394,23 @@ function SearchPanel({
|
|
|
3809
4394
|
var _a;
|
|
3810
4395
|
return (_a = groups === null || groups === void 0 ? void 0 : groups.flatMap(group => group.results)) !== null && _a !== void 0 ? _a : [];
|
|
3811
4396
|
}, [groups]);
|
|
3812
|
-
const
|
|
4397
|
+
const recents = useRecentVisits(env);
|
|
4398
|
+
const options = hasTerm ? results.map(({
|
|
4399
|
+
attributes
|
|
4400
|
+
}) => ({
|
|
4401
|
+
label: attributes.title,
|
|
4402
|
+
url: attributes.url
|
|
4403
|
+
})) : recents.visits.map(visit => {
|
|
4404
|
+
const {
|
|
4405
|
+
title,
|
|
4406
|
+
url
|
|
4407
|
+
} = describeVisit(visit);
|
|
4408
|
+
return {
|
|
4409
|
+
label: title,
|
|
4410
|
+
url
|
|
4411
|
+
};
|
|
4412
|
+
});
|
|
4413
|
+
const navigation = useResultNavigation(options, hasTerm ? results : RECENTS);
|
|
3813
4414
|
const {
|
|
3814
4415
|
highlightedIndex
|
|
3815
4416
|
} = navigation;
|
|
@@ -3835,8 +4436,8 @@ function SearchPanel({
|
|
|
3835
4436
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("input", {
|
|
3836
4437
|
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
3837
4438
|
"aria-autocomplete": "list",
|
|
3838
|
-
"aria-controls":
|
|
3839
|
-
"aria-expanded":
|
|
4439
|
+
"aria-controls": options.length > 0 ? listboxId : undefined,
|
|
4440
|
+
"aria-expanded": options.length > 0,
|
|
3840
4441
|
"aria-label": "Search",
|
|
3841
4442
|
autoFocus: true,
|
|
3842
4443
|
onChange: event => setTerm(event.target.value),
|
|
@@ -3852,17 +4453,22 @@ function SearchPanel({
|
|
|
3852
4453
|
onSelect: setSelectedSlug,
|
|
3853
4454
|
products: products,
|
|
3854
4455
|
selected: product
|
|
3855
|
-
}, /*#__PURE__*/React__default.createElement(SearchResults, {
|
|
4456
|
+
}, hasTerm ? (/*#__PURE__*/React__default.createElement(SearchResults, {
|
|
3856
4457
|
highlightedIndex: highlightedIndex,
|
|
3857
4458
|
id: listboxId,
|
|
3858
4459
|
label: `${product.appName} results`,
|
|
3859
4460
|
onHighlight: navigation.highlightIndex,
|
|
3860
4461
|
search: search,
|
|
3861
4462
|
term: term
|
|
3862
|
-
}))
|
|
4463
|
+
})) : (/*#__PURE__*/React__default.createElement(RecentlyVisited, {
|
|
4464
|
+
highlightedIndex: highlightedIndex,
|
|
4465
|
+
id: listboxId,
|
|
4466
|
+
onHighlight: navigation.highlightIndex,
|
|
4467
|
+
...recents
|
|
4468
|
+
}))), /*#__PURE__*/React__default.createElement("div", {
|
|
3863
4469
|
"aria-live": "polite",
|
|
3864
4470
|
className: styles$c.visuallyHidden
|
|
3865
|
-
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.
|
|
4471
|
+
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.label : null), /*#__PURE__*/React__default.createElement(KeyboardHints, {
|
|
3866
4472
|
copied: navigation.copied
|
|
3867
4473
|
}));
|
|
3868
4474
|
}
|
|
@@ -3881,249 +4487,6 @@ function initialProduct(products) {
|
|
|
3881
4487
|
return (_c = (_b = (_a = bySlug(lastProduct)) !== null && _a !== void 0 ? _a : bySlug("people")) !== null && _b !== void 0 ? _b : bySlug("services")) !== null && _c !== void 0 ? _c : products[0];
|
|
3882
4488
|
}
|
|
3883
4489
|
|
|
3884
|
-
function parseValue(element) {
|
|
3885
|
-
const datatype = element.getAttribute("datatype");
|
|
3886
|
-
switch (datatype) {
|
|
3887
|
-
case "boolean":
|
|
3888
|
-
return element.content === "true" || element.content === "1";
|
|
3889
|
-
default:
|
|
3890
|
-
return element.content;
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
function getAllPcoMeta() {
|
|
3894
|
-
const elements = document.head.querySelectorAll(`meta[property^="pco:"]`);
|
|
3895
|
-
const meta = {};
|
|
3896
|
-
elements.forEach(e => {
|
|
3897
|
-
const property = e.getAttribute("property");
|
|
3898
|
-
meta[property.substring("pco:".length)] = parseValue(e);
|
|
3899
|
-
});
|
|
3900
|
-
return meta;
|
|
3901
|
-
}
|
|
3902
|
-
// usePcoMeta only reads each tag's content. Recently Visited also needs the
|
|
3903
|
-
// pco:recently_visitable tag's `metadata` attribute, which can change while
|
|
3904
|
-
// the content stays the same, so it is tracked in its own store.
|
|
3905
|
-
function getRecentlyVisitableMetadata() {
|
|
3906
|
-
var _a, _b;
|
|
3907
|
-
return (_b = (_a = document.head.querySelector(`meta[property="pco:recently_visitable"]`)) === null || _a === void 0 ? void 0 : _a.getAttribute("metadata")) !== null && _b !== void 0 ? _b : null;
|
|
3908
|
-
}
|
|
3909
|
-
const store = createStore(getAllPcoMeta());
|
|
3910
|
-
const recentlyVisitableMetadataStore = createStore({
|
|
3911
|
-
metadata: getRecentlyVisitableMetadata()
|
|
3912
|
-
});
|
|
3913
|
-
function sync() {
|
|
3914
|
-
const nextState = getAllPcoMeta();
|
|
3915
|
-
if (!shallowCompare(store.getState(), nextState)) store.setState(nextState);
|
|
3916
|
-
const metadata = getRecentlyVisitableMetadata();
|
|
3917
|
-
if (recentlyVisitableMetadataStore.getState().metadata !== metadata) {
|
|
3918
|
-
recentlyVisitableMetadataStore.setState({
|
|
3919
|
-
metadata
|
|
3920
|
-
});
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
const observer = new MutationObserver(sync);
|
|
3924
|
-
let hasTriggeredObserve = false;
|
|
3925
|
-
function observe() {
|
|
3926
|
-
if (hasTriggeredObserve) return;
|
|
3927
|
-
hasTriggeredObserve = true;
|
|
3928
|
-
// The module-load snapshot above can predate meta tags that appear later
|
|
3929
|
-
// in <head> (e.g. pco:resource, yielded right before </head>) if this
|
|
3930
|
-
// module's own script tag isn't deferred and runs mid-parse. Those tags
|
|
3931
|
-
// never mutate afterward, so the observer alone would never catch them.
|
|
3932
|
-
// Re-read once now, since observe() is only called once a consumer is
|
|
3933
|
-
// actually mounting, which is always after the document has fully parsed.
|
|
3934
|
-
sync();
|
|
3935
|
-
observer.observe(document.head, {
|
|
3936
|
-
attributes: true,
|
|
3937
|
-
childList: true,
|
|
3938
|
-
subtree: true
|
|
3939
|
-
});
|
|
3940
|
-
}
|
|
3941
|
-
function usePcoMeta() {
|
|
3942
|
-
observe();
|
|
3943
|
-
return useSyncExternalStore(store.subscribe, () => store.getState());
|
|
3944
|
-
}
|
|
3945
|
-
function useRecentlyVisitableMetadata() {
|
|
3946
|
-
observe();
|
|
3947
|
-
return useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3948
|
-
}
|
|
3949
|
-
|
|
3950
|
-
const MAX_ENTRIES = 20;
|
|
3951
|
-
const FORMAT_VERSION = 1;
|
|
3952
|
-
const MAX_TEXT_LENGTH = 200;
|
|
3953
|
-
const PRODUCTS = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3954
|
-
function isId(value) {
|
|
3955
|
-
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3956
|
-
}
|
|
3957
|
-
// A PCRL looks like `<product>/v2/<resource_type>/<id>`.
|
|
3958
|
-
function isPcrl(value) {
|
|
3959
|
-
const segments = value.split("/");
|
|
3960
|
-
return segments.length === 4 && PRODUCTS.some(product => product === segments[0]) && isId(segments[3]);
|
|
3961
|
-
}
|
|
3962
|
-
function isNonBlankString(value) {
|
|
3963
|
-
return typeof value === "string" && value.trim() !== "";
|
|
3964
|
-
}
|
|
3965
|
-
function isRecord(value) {
|
|
3966
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3967
|
-
}
|
|
3968
|
-
function entryKey(entry) {
|
|
3969
|
-
if (entry.type === "resource") {
|
|
3970
|
-
return `resource:${entry.pcrl}`;
|
|
3971
|
-
} else {
|
|
3972
|
-
return `page:${entry.product}:${entry.path}`;
|
|
3973
|
-
}
|
|
3974
|
-
}
|
|
3975
|
-
function isEntry(value) {
|
|
3976
|
-
if (!isRecord(value)) {
|
|
3977
|
-
return false;
|
|
3978
|
-
} else if (value.type === "resource") {
|
|
3979
|
-
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3980
|
-
} else if (value.type === "page") {
|
|
3981
|
-
return isNonBlankString(value.product) && isNonBlankString(value.title) && isNonBlankString(value.path) && typeof value.details === "string";
|
|
3982
|
-
} else {
|
|
3983
|
-
return false;
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
// Cuts on code points so a surrogate pair is never split.
|
|
3987
|
-
function clampText(value) {
|
|
3988
|
-
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3989
|
-
}
|
|
3990
|
-
function clampEntry(entry) {
|
|
3991
|
-
if (entry.type === "resource") {
|
|
3992
|
-
return entry;
|
|
3993
|
-
} else {
|
|
3994
|
-
return {
|
|
3995
|
-
...entry,
|
|
3996
|
-
details: clampText(entry.details),
|
|
3997
|
-
title: clampText(entry.title)
|
|
3998
|
-
};
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
4002
|
-
// a single bad entry, a list without it) rather than throwing.
|
|
4003
|
-
function parseRecentlyVisited(value, personId) {
|
|
4004
|
-
let parsed = null;
|
|
4005
|
-
try {
|
|
4006
|
-
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
4007
|
-
} catch {
|
|
4008
|
-
parsed = null;
|
|
4009
|
-
}
|
|
4010
|
-
if (isRecord(parsed) && parsed.v === FORMAT_VERSION && parsed.person === personId && Array.isArray(parsed.entries)) {
|
|
4011
|
-
return parsed.entries.filter(isEntry);
|
|
4012
|
-
} else {
|
|
4013
|
-
return [];
|
|
4014
|
-
}
|
|
4015
|
-
}
|
|
4016
|
-
function addVisit(entries, visit) {
|
|
4017
|
-
const key = entryKey(visit);
|
|
4018
|
-
return [clampEntry(visit), ...entries.filter(entry => entryKey(entry) !== key)].slice(0, MAX_ENTRIES);
|
|
4019
|
-
}
|
|
4020
|
-
function serializeRecentlyVisited(entries, personId) {
|
|
4021
|
-
return JSON.stringify({
|
|
4022
|
-
entries,
|
|
4023
|
-
person: personId,
|
|
4024
|
-
v: FORMAT_VERSION
|
|
4025
|
-
});
|
|
4026
|
-
}
|
|
4027
|
-
|
|
4028
|
-
// The product whose URL, in this environment, has the current page's host.
|
|
4029
|
-
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
4030
|
-
// and guarantees the stored product rebuilds a working link.
|
|
4031
|
-
function currentProduct() {
|
|
4032
|
-
var _a;
|
|
4033
|
-
return (_a = PRODUCTS.find(product => new URL(pcoUrl$2(product)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
4034
|
-
}
|
|
4035
|
-
function detailsFromMetadata(metadata) {
|
|
4036
|
-
var _a;
|
|
4037
|
-
try {
|
|
4038
|
-
const details = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) === null || _a === void 0 ? void 0 : _a.details;
|
|
4039
|
-
return typeof details === "string" ? details : "";
|
|
4040
|
-
} catch {
|
|
4041
|
-
return "";
|
|
4042
|
-
}
|
|
4043
|
-
}
|
|
4044
|
-
// pco:resource is the more specific signal, so it wins when both tags are
|
|
4045
|
-
// present. A resource that isn't a PCRL is ignored rather than recorded.
|
|
4046
|
-
function visitFromPage({
|
|
4047
|
-
metadata,
|
|
4048
|
-
resource,
|
|
4049
|
-
title
|
|
4050
|
-
}) {
|
|
4051
|
-
const product = currentProduct();
|
|
4052
|
-
if (resource && isPcrl(resource)) {
|
|
4053
|
-
return {
|
|
4054
|
-
pcrl: resource,
|
|
4055
|
-
type: "resource"
|
|
4056
|
-
};
|
|
4057
|
-
} else if (title && title.trim() !== "" && product) {
|
|
4058
|
-
return {
|
|
4059
|
-
details: detailsFromMetadata(metadata),
|
|
4060
|
-
path: window.location.pathname,
|
|
4061
|
-
product,
|
|
4062
|
-
title,
|
|
4063
|
-
type: "page"
|
|
4064
|
-
};
|
|
4065
|
-
} else {
|
|
4066
|
-
return null;
|
|
4067
|
-
}
|
|
4068
|
-
}
|
|
4069
|
-
// The entry Recently Visited would record for the current page, or null when
|
|
4070
|
-
// the page isn't eligible. Shared by the recorder and the debug panel so the
|
|
4071
|
-
// panel always shows what actually gets stored.
|
|
4072
|
-
function useCurrentVisit() {
|
|
4073
|
-
const {
|
|
4074
|
-
recently_visitable: title,
|
|
4075
|
-
resource
|
|
4076
|
-
} = usePcoMeta();
|
|
4077
|
-
const metadata = useRecentlyVisitableMetadata();
|
|
4078
|
-
return useMemo(() => visitFromPage({
|
|
4079
|
-
metadata,
|
|
4080
|
-
resource,
|
|
4081
|
-
title
|
|
4082
|
-
}), [metadata, resource, title]);
|
|
4083
|
-
}
|
|
4084
|
-
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
4085
|
-
// and several pass it as a number.
|
|
4086
|
-
function useCurrentPersonId() {
|
|
4087
|
-
const {
|
|
4088
|
-
userId
|
|
4089
|
-
} = useStore();
|
|
4090
|
-
return userId == null ? undefined : String(userId);
|
|
4091
|
-
}
|
|
4092
|
-
|
|
4093
|
-
// Per-origin localStorage is a stopgap: each product keeps its own list until
|
|
4094
|
-
// PCA-409 moves this to storage shared across product subdomains. Only this
|
|
4095
|
-
// file should need to change when it does.
|
|
4096
|
-
function storageKey({
|
|
4097
|
-
env,
|
|
4098
|
-
personId
|
|
4099
|
-
}) {
|
|
4100
|
-
return `topbar.recently_visited.${env}.${personId}`;
|
|
4101
|
-
}
|
|
4102
|
-
// localStorage doesn't notify the tab that wrote it, so readers in this tab
|
|
4103
|
-
// learn about new visits from this counter instead.
|
|
4104
|
-
const changes = createStore({
|
|
4105
|
-
version: 0
|
|
4106
|
-
});
|
|
4107
|
-
function readRecentlyVisited(scope) {
|
|
4108
|
-
try {
|
|
4109
|
-
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)), scope.personId);
|
|
4110
|
-
} catch {
|
|
4111
|
-
return [];
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
|
-
function recordVisit(visit, scope) {
|
|
4115
|
-
if (isEntry(visit)) {
|
|
4116
|
-
try {
|
|
4117
|
-
localStorage.setItem(storageKey(scope), serializeRecentlyVisited(addVisit(readRecentlyVisited(scope), visit), scope.personId));
|
|
4118
|
-
changes.setState({
|
|
4119
|
-
version: changes.getState().version + 1
|
|
4120
|
-
});
|
|
4121
|
-
} catch {
|
|
4122
|
-
// Storage can be full or blocked; recents are disposable.
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
|
|
4127
4490
|
function useRecordRecentlyVisited({
|
|
4128
4491
|
enabled,
|
|
4129
4492
|
env
|
|
@@ -4295,7 +4658,7 @@ const UserMenu = ({
|
|
|
4295
4658
|
}, /*#__PURE__*/React__default.createElement(Text, {
|
|
4296
4659
|
color: "var(--t-text-color-secondary)",
|
|
4297
4660
|
lineHeight: "24px"
|
|
4298
|
-
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon, {
|
|
4661
|
+
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
4299
4662
|
name: "general.intoBox",
|
|
4300
4663
|
fill: "var(--t-icon-color-secondary)"
|
|
4301
4664
|
})))))));
|
|
@@ -4413,7 +4776,8 @@ const NotSmallTopbar = ({
|
|
|
4413
4776
|
margin: "auto"
|
|
4414
4777
|
}
|
|
4415
4778
|
}), !notificationsEnabled && notifications(), productSearchEnabled ? (/*#__PURE__*/React__default.createElement(ProductSearch, {
|
|
4416
|
-
apps: props.apps
|
|
4779
|
+
apps: props.apps,
|
|
4780
|
+
env: props.env
|
|
4417
4781
|
})) : search({
|
|
4418
4782
|
hideRoutes: () => setRoutesVisible(false),
|
|
4419
4783
|
showRoutes: () => setRoutesVisible(true)
|
|
@@ -32546,7 +32910,7 @@ const SmallTopbar = ({
|
|
|
32546
32910
|
setProductSwitcherVisible(false);
|
|
32547
32911
|
setRoutesMenuVisible(visible => !visible);
|
|
32548
32912
|
} : null
|
|
32549
|
-
}, /*#__PURE__*/React__default.createElement(CurrentRouteComponent, null, capitalize(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React__default.createElement("button", {
|
|
32913
|
+
}, /*#__PURE__*/React__default.createElement(CurrentRouteComponent, null, capitalize$1(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React__default.createElement("button", {
|
|
32550
32914
|
type: "button",
|
|
32551
32915
|
className: styles$5["profile-button"],
|
|
32552
32916
|
onClick: () => {
|
|
@@ -32739,8 +33103,24 @@ function Field({
|
|
|
32739
33103
|
className: styles$4["debug-pco-resource-field-value"]
|
|
32740
33104
|
}, value));
|
|
32741
33105
|
}
|
|
33106
|
+
// Only staff ghosting or impersonating a person get pco:staff-access-level,
|
|
33107
|
+
// which keeps the panel away from regular users on staging.
|
|
32742
33108
|
function DebugPcoResource({
|
|
32743
33109
|
env
|
|
33110
|
+
}) {
|
|
33111
|
+
const {
|
|
33112
|
+
"staff-access-level": staffAccessLevel
|
|
33113
|
+
} = usePcoMeta();
|
|
33114
|
+
if (isPresent(staffAccessLevel)) {
|
|
33115
|
+
return /*#__PURE__*/React__default.createElement(DebugPanel, {
|
|
33116
|
+
env: env
|
|
33117
|
+
});
|
|
33118
|
+
} else {
|
|
33119
|
+
return null;
|
|
33120
|
+
}
|
|
33121
|
+
}
|
|
33122
|
+
function DebugPanel({
|
|
33123
|
+
env
|
|
32744
33124
|
}) {
|
|
32745
33125
|
const {
|
|
32746
33126
|
recently_visitable: recentlyVisitable,
|
|
@@ -32749,12 +33129,17 @@ function DebugPcoResource({
|
|
|
32749
33129
|
const [lookup, setLookup] = useState({
|
|
32750
33130
|
status: "idle"
|
|
32751
33131
|
});
|
|
32752
|
-
const
|
|
33132
|
+
const visit = useCurrentVisit();
|
|
33133
|
+
const personId = useCurrentPersonId();
|
|
33134
|
+
const recentlyVisited = useRecentlyVisited({
|
|
33135
|
+
env,
|
|
33136
|
+
personId
|
|
33137
|
+
});
|
|
33138
|
+
const pcrl = (visit === null || visit === void 0 ? void 0 : visit.type) === "resource" ? visit.pcrl : null;
|
|
32753
33139
|
const hasRecentlyVisitable = isPresent(recentlyVisitable);
|
|
33140
|
+
const hasInvalidResource = isPresent(resource) && !isPcrl(resource);
|
|
32754
33141
|
useEffect(() => {
|
|
32755
|
-
|
|
32756
|
-
// tags are present.
|
|
32757
|
-
const url = hasResource ? pcrlLookupUrl(resource, env) : null;
|
|
33142
|
+
const url = pcrl ? pcrlLookupUrl(pcrl, env) : null;
|
|
32758
33143
|
if (!url) {
|
|
32759
33144
|
setLookup({
|
|
32760
33145
|
status: "idle"
|
|
@@ -32786,7 +33171,7 @@ function DebugPcoResource({
|
|
|
32786
33171
|
return () => {
|
|
32787
33172
|
cancelled = true;
|
|
32788
33173
|
};
|
|
32789
|
-
}, [
|
|
33174
|
+
}, [pcrl, env]);
|
|
32790
33175
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32791
33176
|
className: classnames("staff-only", styles$4["debug-pco-resource"])
|
|
32792
33177
|
}, /*#__PURE__*/React__default.createElement("div", {
|
|
@@ -32801,19 +33186,27 @@ function DebugPcoResource({
|
|
|
32801
33186
|
}), /*#__PURE__*/React__default.createElement(Field, {
|
|
32802
33187
|
label: "pco:recently_visitable",
|
|
32803
33188
|
value: describeTag("recently_visitable", recentlyVisitable)
|
|
32804
|
-
}),
|
|
33189
|
+
}), hasInvalidResource && (/*#__PURE__*/React__default.createElement("p", {
|
|
33190
|
+
className: styles$4["debug-pco-resource-note"]
|
|
33191
|
+
}, "pco:resource isn't a PCRL (product/v2/type/id), so Recently Visited ignores it.")), visit === null && (/*#__PURE__*/React__default.createElement("p", {
|
|
33192
|
+
className: styles$4["debug-pco-resource-note"]
|
|
33193
|
+
}, "This page is not eligible for Recently Visited")), pcrl && hasRecentlyVisitable && (/*#__PURE__*/React__default.createElement("p", {
|
|
32805
33194
|
className: styles$4["debug-pco-resource-note"]
|
|
32806
|
-
}, "
|
|
33195
|
+
}, "Both tags are present \u2014 pco:resource takes precedence for the Recently Visited entry.")), pcrl && lookup.status === "loading" && (/*#__PURE__*/React__default.createElement("p", {
|
|
32807
33196
|
className: styles$4["debug-pco-resource-note"]
|
|
32808
|
-
}, "
|
|
33197
|
+
}, "Looking up resource\u2026")), pcrl && lookup.status === "error" && (/*#__PURE__*/React__default.createElement("p", {
|
|
32809
33198
|
className: styles$4["debug-pco-resource-note"]
|
|
32810
|
-
},
|
|
33199
|
+
}, lookup.message)), pcrl && lookup.status === "loaded" && (/*#__PURE__*/React__default.createElement("pre", {
|
|
33200
|
+
className: styles$4["debug-pco-resource-json"]
|
|
33201
|
+
}, JSON.stringify(lookup.json, null, 2))), (visit === null || visit === void 0 ? void 0 : visit.type) === "page" && (/*#__PURE__*/React__default.createElement("p", {
|
|
32811
33202
|
className: styles$4["debug-pco-resource-note"]
|
|
32812
|
-
},
|
|
33203
|
+
}, "Recently Visited title: ", visit.title)), /*#__PURE__*/React__default.createElement("span", {
|
|
33204
|
+
className: styles$4["debug-pco-resource-field-label"]
|
|
33205
|
+
}, "Recently Visited"), personId ? (/*#__PURE__*/React__default.createElement("pre", {
|
|
32813
33206
|
className: styles$4["debug-pco-resource-json"]
|
|
32814
|
-
}, JSON.stringify(
|
|
33207
|
+
}, JSON.stringify(recentlyVisited, null, 2))) : (/*#__PURE__*/React__default.createElement("p", {
|
|
32815
33208
|
className: styles$4["debug-pco-resource-note"]
|
|
32816
|
-
}, "
|
|
33209
|
+
}, "No current person, so nothing is recorded.")));
|
|
32817
33210
|
}
|
|
32818
33211
|
|
|
32819
33212
|
var styles$3 = {"drawer-wrap":"drawer-wrap-J0rSL","is-visible":"is-visible-KTME8","drawer":"drawer-j7f4l"};
|
|
@@ -33018,15 +33411,50 @@ const SwitchView = ({
|
|
|
33018
33411
|
}));
|
|
33019
33412
|
};
|
|
33020
33413
|
|
|
33414
|
+
const collections = {
|
|
33415
|
+
general,
|
|
33416
|
+
toolbar
|
|
33417
|
+
};
|
|
33418
|
+
const paths = collections;
|
|
33419
|
+
function Icon({
|
|
33420
|
+
name,
|
|
33421
|
+
size = 16,
|
|
33422
|
+
style,
|
|
33423
|
+
theme,
|
|
33424
|
+
title,
|
|
33425
|
+
...props
|
|
33426
|
+
}) {
|
|
33427
|
+
const [collection, symbol] = name.split(".");
|
|
33428
|
+
return /*#__PURE__*/React__default.createElement("svg", {
|
|
33429
|
+
"aria-hidden": title ? undefined : true,
|
|
33430
|
+
fill: "currentColor",
|
|
33431
|
+
height: size,
|
|
33432
|
+
role: title ? "img" : undefined,
|
|
33433
|
+
style: {
|
|
33434
|
+
color: theme && `var(${theme})`,
|
|
33435
|
+
flexShrink: 0,
|
|
33436
|
+
...style
|
|
33437
|
+
},
|
|
33438
|
+
viewBox: "0 0 16 16",
|
|
33439
|
+
width: size,
|
|
33440
|
+
...props
|
|
33441
|
+
}, title && /*#__PURE__*/React__default.createElement("title", null, title), /*#__PURE__*/React__default.createElement("path", {
|
|
33442
|
+
d: paths[collection][symbol]
|
|
33443
|
+
}));
|
|
33444
|
+
}
|
|
33445
|
+
|
|
33021
33446
|
var styles$2 = {"color-mode-switcher":"color-mode-switcher-JpczO","color-mode-switcher-label":"color-mode-switcher-label-3jPM2","color-mode-switcher-modes":"color-mode-switcher-modes-u-bic"};
|
|
33022
33447
|
|
|
33023
33448
|
const COLOR_MODES = [{
|
|
33449
|
+
icon: "toolbar.sun",
|
|
33024
33450
|
label: "Light",
|
|
33025
33451
|
value: "light"
|
|
33026
33452
|
}, {
|
|
33453
|
+
icon: "toolbar.moon",
|
|
33027
33454
|
label: "Dark",
|
|
33028
33455
|
value: "dark"
|
|
33029
33456
|
}, {
|
|
33457
|
+
icon: "toolbar.display",
|
|
33030
33458
|
label: "System",
|
|
33031
33459
|
value: "system"
|
|
33032
33460
|
}];
|
|
@@ -33044,8 +33472,9 @@ function ColorModeSwitcher() {
|
|
|
33044
33472
|
}, "Appearance", " "), /*#__PURE__*/React__default.createElement("div", {
|
|
33045
33473
|
className: styles$2["color-mode-switcher-modes"]
|
|
33046
33474
|
}, COLOR_MODES.map(({
|
|
33047
|
-
|
|
33048
|
-
label
|
|
33475
|
+
icon,
|
|
33476
|
+
label,
|
|
33477
|
+
value
|
|
33049
33478
|
}) => (/*#__PURE__*/React__default.createElement("label", {
|
|
33050
33479
|
key: value
|
|
33051
33480
|
}, /*#__PURE__*/React__default.createElement("input", {
|
|
@@ -33062,8 +33491,9 @@ function ColorModeSwitcher() {
|
|
|
33062
33491
|
document.documentElement.setAttribute(RESOLVED_COLOR_MODE_ATTRIBUTE, resolveColorMode(colorMode));
|
|
33063
33492
|
updateColorMode(colorMode);
|
|
33064
33493
|
}
|
|
33065
|
-
}), /*#__PURE__*/React__default.createElement(
|
|
33066
|
-
|
|
33494
|
+
}), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33495
|
+
name: icon,
|
|
33496
|
+
size: 15
|
|
33067
33497
|
}), label)))));
|
|
33068
33498
|
}
|
|
33069
33499
|
function updateColorMode(mode) {
|
|
@@ -33078,28 +33508,6 @@ function updateColorMode(mode) {
|
|
|
33078
33508
|
method: "POST"
|
|
33079
33509
|
});
|
|
33080
33510
|
}
|
|
33081
|
-
const COLOR_MODE_ICON_PATHS = {
|
|
33082
|
-
dark: "M12.66667,9.07579 C11.90047,9.42229 11.04997,9.61514 10.15457,9.61514 C6.78392,9.61514 4.05151,6.88275 4.05151,3.51215 C4.05151,2.61663 4.24438,1.76615 4.59086,1 C2.47336,1.95762 1,4.08858 1,6.56366 C1,9.93423 3.73241,12.66663 7.103,12.66663 C9.57807,12.66663 11.70907,11.19333 12.66667,9.07579 Z",
|
|
33083
|
-
light: "M6.83333,1 L6.83333,2.16667 M6.83333,11.50003 L6.83333,12.66663 M2.16667,6.83334 L1,6.83334 M3.51657,3.51657 L2.69161,2.69161 M10.1501,3.51657 L10.9751,2.69161 M3.51657,10.15253 L2.69161,10.97743 M10.1501,10.15253 L10.9751,10.97743 M12.6667,6.83334 L11.5,6.83334 M9.75,6.83334 C9.75,8.44415 8.44415,9.75 6.83333,9.75 C5.2225,9.75 3.91667,8.44415 3.91667,6.83334 C3.91667,5.22251 5.2225,3.91667 6.83333,3.91667 C8.44415,3.91667 9.75,5.22251 9.75,6.83334 Z",
|
|
33084
|
-
system: "M4.75,12.50003 L9.41667,12.50003 M7.08334,10.16667 L7.08334,12.50003 M4.05,10.16667 L10.11665,10.16667 C11.09675,10.16667 11.58675,10.16667 11.96115,9.97592 C12.29045,9.80815 12.55815,9.54046 12.72595,9.21117 C12.91665,8.83678 12.91665,8.34678 12.91665,7.36667 L12.91665,4.8 C12.91665,3.81991 12.91665,3.32986 12.72595,2.95552 C12.55815,2.62623 12.29045,2.35852 11.96115,2.19074 C11.58675,2 11.09675,2 10.11665,2 L4.05,2 C3.06991,2 2.57986,2 2.20552,2.19074 C1.87623,2.35852 1.60852,2.62623 1.44074,2.95552 C1.25,3.32986 1.25,3.81991 1.25,4.8 L1.25,7.36667 C1.25,8.34678 1.25,8.83678 1.44074,9.21117 C1.60852,9.54046 1.87623,9.80815 2.20552,9.97592 C2.57986,10.16667 3.06991,10.16667 4.05,10.16667 Z"
|
|
33085
|
-
};
|
|
33086
|
-
function ColorModeIcon({
|
|
33087
|
-
mode
|
|
33088
|
-
}) {
|
|
33089
|
-
return /*#__PURE__*/React__default.createElement("svg", {
|
|
33090
|
-
width: "16",
|
|
33091
|
-
height: "16",
|
|
33092
|
-
viewBox: "0 0 14 14",
|
|
33093
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
33094
|
-
"aria-hidden": "true"
|
|
33095
|
-
}, /*#__PURE__*/React__default.createElement("path", {
|
|
33096
|
-
d: COLOR_MODE_ICON_PATHS[mode],
|
|
33097
|
-
strokeWidth: "1.5",
|
|
33098
|
-
fill: "none",
|
|
33099
|
-
strokeLinecap: "round",
|
|
33100
|
-
strokeLinejoin: "round"
|
|
33101
|
-
}));
|
|
33102
|
-
}
|
|
33103
33511
|
|
|
33104
33512
|
const requestClearConnectedPeopleCache = () => remove();
|
|
33105
33513
|
function UserProfile({
|
|
@@ -33164,8 +33572,8 @@ function UserProfile({
|
|
|
33164
33572
|
width: "100%"
|
|
33165
33573
|
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
33166
33574
|
name: mfaConfigured ? "general.lock" : "general.unlock",
|
|
33167
|
-
size:
|
|
33168
|
-
|
|
33575
|
+
size: 12,
|
|
33576
|
+
theme: mfaConfigured ? "--t-icon-color-status-success" : "--t-icon-color-status-error"
|
|
33169
33577
|
}), /*#__PURE__*/React__default.createElement(Text, {
|
|
33170
33578
|
size: 4,
|
|
33171
33579
|
weight: 400,
|
|
@@ -33215,7 +33623,8 @@ function UserProfile({
|
|
|
33215
33623
|
size: 3
|
|
33216
33624
|
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33217
33625
|
name: "general.intoBox",
|
|
33218
|
-
|
|
33626
|
+
size: 14,
|
|
33627
|
+
theme: "--t-icon-color-secondary"
|
|
33219
33628
|
})))), env !== "production" && /*#__PURE__*/React__default.createElement(DebugPcoResource, {
|
|
33220
33629
|
env: env
|
|
33221
33630
|
}), hasConnectedPeople && (/*#__PURE__*/React__default.createElement(StackView$2, {
|
|
@@ -33277,26 +33686,42 @@ function UserProfile({
|
|
|
33277
33686
|
|
|
33278
33687
|
var ActionType;
|
|
33279
33688
|
(function (ActionType) {
|
|
33689
|
+
ActionType["clearPending"] = "clearPending";
|
|
33280
33690
|
ActionType["close"] = "close";
|
|
33281
33691
|
ActionType["mount"] = "mount";
|
|
33282
33692
|
ActionType["open"] = "open";
|
|
33693
|
+
ActionType["setPending"] = "setPending";
|
|
33283
33694
|
ActionType["unmount"] = "unmount";
|
|
33284
33695
|
})(ActionType || (ActionType = {}));
|
|
33285
33696
|
const initialState = {
|
|
33286
33697
|
currentContent: null,
|
|
33287
|
-
open: false
|
|
33698
|
+
open: false,
|
|
33699
|
+
pending: null
|
|
33288
33700
|
};
|
|
33289
33701
|
const reducer = (state, action) => {
|
|
33702
|
+
var _a, _b;
|
|
33290
33703
|
switch (action.type) {
|
|
33291
33704
|
case ActionType.mount:
|
|
33292
33705
|
return {
|
|
33293
33706
|
...state,
|
|
33294
|
-
currentContent: action.payload.content
|
|
33707
|
+
currentContent: action.payload.content,
|
|
33708
|
+
pending: ((_a = state.pending) === null || _a === void 0 ? void 0 : _a.tool) === ((_b = action.payload.content) === null || _b === void 0 ? void 0 : _b.name) ? state.pending : null
|
|
33295
33709
|
};
|
|
33296
33710
|
case ActionType.unmount:
|
|
33297
33711
|
return {
|
|
33298
33712
|
...state,
|
|
33299
|
-
currentContent: null
|
|
33713
|
+
currentContent: null,
|
|
33714
|
+
pending: null
|
|
33715
|
+
};
|
|
33716
|
+
case ActionType.setPending:
|
|
33717
|
+
return {
|
|
33718
|
+
...state,
|
|
33719
|
+
pending: action.payload
|
|
33720
|
+
};
|
|
33721
|
+
case ActionType.clearPending:
|
|
33722
|
+
return {
|
|
33723
|
+
...state,
|
|
33724
|
+
pending: null
|
|
33300
33725
|
};
|
|
33301
33726
|
case ActionType.open:
|
|
33302
33727
|
return {
|
|
@@ -33317,11 +33742,179 @@ const useToolbarReducer = overrides => React.useReducer(reducer, {
|
|
|
33317
33742
|
|
|
33318
33743
|
var styles$1 = {"toolbar":"toolbar-KHJR5","toolbar-button":"toolbar-button-Ye4h5","is-current":"is-current-DZK47","toolbar-button--avatar":"toolbar-button--avatar-5FUik","toolbar-button--home":"toolbar-button--home-Pthd0"};
|
|
33319
33744
|
|
|
33320
|
-
|
|
33745
|
+
// PCO Box serves TLS by default, and a bare http:// frame inside an https
|
|
33746
|
+
// product page is blocked as mixed content, so follow the page's scheme.
|
|
33747
|
+
const iframeSrc = () => `${window.location.protocol}//pco.test/iframe`;
|
|
33748
|
+
const STAFF_PINK = "var(--t-fill-color-product-staff)";
|
|
33749
|
+
// The pco.test domain is only ever served by a local PCO Box, so the tool
|
|
33750
|
+
// stays hidden in staging and production — and on Topbar's own dev server,
|
|
33751
|
+
// which runs on localhost and has no pco.test to frame.
|
|
33752
|
+
function isPcoTestHost() {
|
|
33753
|
+
const {
|
|
33754
|
+
hostname
|
|
33755
|
+
} = window.location;
|
|
33756
|
+
return hostname === "pco.test" || hostname.endsWith(".pco.test");
|
|
33757
|
+
}
|
|
33758
|
+
// Flattened from the source artwork's two outline paths — its gradients,
|
|
33759
|
+
// filters and clip paths carry nothing at icon size. Those paths are authored
|
|
33760
|
+
// on a 128px canvas, hence the transform: it fits the pair into the same ~24px
|
|
33761
|
+
// optical box the sibling toolbar icons occupy. Filled explicitly rather than
|
|
33762
|
+
// with `currentColor` so the mark stays staff pink instead of inheriting the
|
|
33763
|
+
// toolbar's text color and its hover/active states.
|
|
33764
|
+
const WRENCH = "m22.42 17.2 12.96 12.95-2.5 8.78-8.78 2.5-12.96-12.96-5.86 5.86 14.89 21.46 17.56-3.34 61.63 65.62c9.44 9.51 22.17-4.75 13.34-12.87L46.85 42.92l4.01-17.76-22.14-14.27-6.3 6.31zm81.06 91.97c1.68-1.68 4.4-1.68 6.08 0 1.68 1.68 1.68 4.4 0 6.08-1.68 1.67-4.4 1.67-6.08 0-1.68-1.68-1.68-4.4 0-6.08z";
|
|
33765
|
+
const SCREWDRIVER = "m55.8 12 .76.16c8.84-2.94 28.68 8.64 24.26 18.96-1.12 1.62-.93 4.5.42 6.31L56.43 62.25c-1.39-1.39-4.48-1.36-5.9 0L9.5 101.63c-3.74 3.59-.8 6.64.78 8.25l7.36 7.36c1.61 1.6 5.86 3.76 8.88.56l38.72-40.81c1.35-1.42 1.36-4.47 0-5.87 0 0 .01-.02 0-.02l24.91-24.9c2.63 2.37 5.11 2.57 7.47 1.93 4.55-1.25 7.09 3.21 9.81 6.46l-1.23 1.21c-1.82 1.84-1.65 4.94.39 6.98s5.15 2.21 6.97.39l8.14-8.13c1.82-1.84 1.65-4.94-.4-6.97-1.84-1.85-4.51-2.09-6.37-.77-5.58-6.26-2.65-7.2-10.01-15.35L91.13 17.86C81.2 8.12 68.3 8.13 55.8 12z";
|
|
33766
|
+
function ToolsIcon() {
|
|
33767
|
+
return /*#__PURE__*/React__default.createElement("svg", {
|
|
33768
|
+
"aria-hidden": "true",
|
|
33769
|
+
width: "32",
|
|
33770
|
+
height: "32",
|
|
33771
|
+
viewBox: "0 0 32 32",
|
|
33772
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
33773
|
+
}, /*#__PURE__*/React__default.createElement("g", {
|
|
33774
|
+
fill: STAFF_PINK,
|
|
33775
|
+
transform: "translate(2.92 2.66) scale(.204)"
|
|
33776
|
+
}, /*#__PURE__*/React__default.createElement("path", {
|
|
33777
|
+
d: WRENCH
|
|
33778
|
+
}), /*#__PURE__*/React__default.createElement("path", {
|
|
33779
|
+
d: SCREWDRIVER
|
|
33780
|
+
})));
|
|
33781
|
+
}
|
|
33782
|
+
// A cross-origin frame's own history object is unreachable (touching it throws
|
|
33783
|
+
// a SecurityError), but the frame's navigations do land in the top-level joint
|
|
33784
|
+
// session history, so the *parent's* back/forward traverse them while leaving
|
|
33785
|
+
// the host page in place. Counting frame loads tracks how deep we are, which is
|
|
33786
|
+
// what keeps a click on Back from stepping past the frame's first page and
|
|
33787
|
+
// navigating the host app instead. Loads we caused are attributed to the
|
|
33788
|
+
// traversal that requested them rather than counted as new navigations.
|
|
33789
|
+
function useFrameHistory() {
|
|
33790
|
+
const [steps, setSteps] = useState({
|
|
33791
|
+
back: 0,
|
|
33792
|
+
forward: 0
|
|
33793
|
+
});
|
|
33794
|
+
// Traversal has to read the current counts outside of render, so the ref is
|
|
33795
|
+
// the source of truth and state exists to paint the buttons.
|
|
33796
|
+
const stepsRef = useRef(steps);
|
|
33797
|
+
const pendingTraversal = useRef(null);
|
|
33798
|
+
const frameIsLoaded = useRef(false);
|
|
33799
|
+
const moveTo = useCallback(next => {
|
|
33800
|
+
stepsRef.current = next;
|
|
33801
|
+
setSteps(next);
|
|
33802
|
+
}, []);
|
|
33803
|
+
const onFrameLoad = useCallback(() => {
|
|
33804
|
+
if (!frameIsLoaded.current) {
|
|
33805
|
+
// The frame's first page is the floor, not a step.
|
|
33806
|
+
frameIsLoaded.current = true;
|
|
33807
|
+
} else {
|
|
33808
|
+
const traversal = pendingTraversal.current;
|
|
33809
|
+
pendingTraversal.current = null;
|
|
33810
|
+
const {
|
|
33811
|
+
back,
|
|
33812
|
+
forward
|
|
33813
|
+
} = stepsRef.current;
|
|
33814
|
+
if (traversal === "back") {
|
|
33815
|
+
moveTo({
|
|
33816
|
+
back: back - 1,
|
|
33817
|
+
forward: forward + 1
|
|
33818
|
+
});
|
|
33819
|
+
} else if (traversal === "forward") {
|
|
33820
|
+
moveTo({
|
|
33821
|
+
back: back + 1,
|
|
33822
|
+
forward: forward - 1
|
|
33823
|
+
});
|
|
33824
|
+
} else {
|
|
33825
|
+
moveTo({
|
|
33826
|
+
back: back + 1,
|
|
33827
|
+
forward: 0
|
|
33828
|
+
});
|
|
33829
|
+
}
|
|
33830
|
+
}
|
|
33831
|
+
}, [moveTo]);
|
|
33832
|
+
// Guarded here rather than trusting the button's disabled state: traversing
|
|
33833
|
+
// with no frame step left would navigate the host app out from under us.
|
|
33834
|
+
const traverse = useCallback(direction => {
|
|
33835
|
+
if (stepsRef.current[direction] > 0) {
|
|
33836
|
+
pendingTraversal.current = direction;
|
|
33837
|
+
window.history[direction]();
|
|
33838
|
+
}
|
|
33839
|
+
}, []);
|
|
33840
|
+
return {
|
|
33841
|
+
onFrameLoad,
|
|
33842
|
+
steps,
|
|
33843
|
+
traverse
|
|
33844
|
+
};
|
|
33845
|
+
}
|
|
33846
|
+
function NavButton({
|
|
33847
|
+
direction,
|
|
33848
|
+
disabled,
|
|
33849
|
+
onClick
|
|
33850
|
+
}) {
|
|
33851
|
+
return /*#__PURE__*/React__default.createElement(Button$1
|
|
33852
|
+
// Tapestry's `disabled` suppresses the click but emits no ARIA state.
|
|
33853
|
+
, {
|
|
33854
|
+
"aria-disabled": disabled,
|
|
33855
|
+
disabled: disabled,
|
|
33856
|
+
hover: {
|
|
33857
|
+
backgroundColor: "var(--t-fill-color-neutral-070)"
|
|
33858
|
+
},
|
|
33859
|
+
icon: {
|
|
33860
|
+
fill: "var(--t-icon-color-dim)",
|
|
33861
|
+
name: direction === "back" ? "general.leftChevron" : "general.rightChevron",
|
|
33862
|
+
size: "md"
|
|
33863
|
+
},
|
|
33864
|
+
onClick: onClick,
|
|
33865
|
+
opacity: disabled ? 0.35 : 1,
|
|
33866
|
+
theme: false,
|
|
33867
|
+
title: direction === "back" ? "Back" : "Forward"
|
|
33868
|
+
});
|
|
33869
|
+
}
|
|
33870
|
+
function DeveloperToolsComponent({
|
|
33871
|
+
closeDrawer
|
|
33872
|
+
}) {
|
|
33873
|
+
const {
|
|
33874
|
+
onFrameLoad,
|
|
33875
|
+
steps,
|
|
33876
|
+
traverse
|
|
33877
|
+
} = useFrameHistory();
|
|
33878
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
33879
|
+
closeDrawer: closeDrawer
|
|
33880
|
+
}, /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
33881
|
+
alignment: "center",
|
|
33882
|
+
axis: "horizontal",
|
|
33883
|
+
spacing: 0.25
|
|
33884
|
+
}, /*#__PURE__*/React__default.createElement(NavButton, {
|
|
33885
|
+
direction: "back",
|
|
33886
|
+
disabled: steps.back === 0,
|
|
33887
|
+
onClick: () => traverse("back")
|
|
33888
|
+
}), /*#__PURE__*/React__default.createElement(NavButton, {
|
|
33889
|
+
direction: "forward",
|
|
33890
|
+
disabled: steps.forward === 0,
|
|
33891
|
+
onClick: () => traverse("forward")
|
|
33892
|
+
}), /*#__PURE__*/React__default.createElement(DrawerHeading, {
|
|
33893
|
+
paddingLeft: 0.5
|
|
33894
|
+
}, "Developer Tools"))), /*#__PURE__*/React__default.createElement(Box, {
|
|
33895
|
+
as: "iframe",
|
|
33896
|
+
border: "none",
|
|
33897
|
+
flex: "1",
|
|
33898
|
+
minHeight: 0,
|
|
33899
|
+
onLoad: onFrameLoad,
|
|
33900
|
+
src: iframeSrc(),
|
|
33901
|
+
title: "Developer Tools",
|
|
33902
|
+
width: "100%"
|
|
33903
|
+
}));
|
|
33904
|
+
}
|
|
33905
|
+
const DeveloperTools = {
|
|
33906
|
+
component: DeveloperToolsComponent,
|
|
33907
|
+
icons: {
|
|
33908
|
+
component: ToolsIcon
|
|
33909
|
+
},
|
|
33910
|
+
label: "Developer Tools",
|
|
33911
|
+
// No space: `name` becomes an element id and a pendo attribute.
|
|
33912
|
+
name: "DeveloperTools"
|
|
33913
|
+
};
|
|
33321
33914
|
|
|
33322
33915
|
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"};
|
|
33323
33916
|
|
|
33324
|
-
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";
|
|
33917
|
+
const SPARKLE_PATH$1 = "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";
|
|
33325
33918
|
// Same stops as the AskAIBanner border gradient, so the two stay in sync.
|
|
33326
33919
|
const GRADIENT_STOPS = [{
|
|
33327
33920
|
color: "var(--t-fill-color-tag-teal-040)",
|
|
@@ -33370,7 +33963,7 @@ function SparkleIcon({
|
|
|
33370
33963
|
stopColor: color
|
|
33371
33964
|
}
|
|
33372
33965
|
})))))), /*#__PURE__*/React__default.createElement("path", {
|
|
33373
|
-
d: SPARKLE_PATH,
|
|
33966
|
+
d: SPARKLE_PATH$1,
|
|
33374
33967
|
fill: isGradient ? `url(#${gradientId})` : "currentColor"
|
|
33375
33968
|
}));
|
|
33376
33969
|
}
|
|
@@ -33402,7 +33995,7 @@ function StackedContent({
|
|
|
33402
33995
|
className: styles.title
|
|
33403
33996
|
}, title), /*#__PURE__*/React__default.createElement("span", {
|
|
33404
33997
|
className: styles.subtitle
|
|
33405
|
-
}, "Support answers tailored to you"))), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33998
|
+
}, "Support answers tailored to you"))), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33406
33999
|
className: styles.chevron,
|
|
33407
34000
|
name: "general.rightChevron",
|
|
33408
34001
|
size: "16px"
|
|
@@ -33423,7 +34016,7 @@ function InlineContent({
|
|
|
33423
34016
|
className: styles.action
|
|
33424
34017
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
33425
34018
|
className: styles.actionLabel
|
|
33426
|
-
}, action), /*#__PURE__*/React__default.createElement(Icon, {
|
|
34019
|
+
}, action), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33427
34020
|
className: styles.actionChevron,
|
|
33428
34021
|
name: "general.rightChevron",
|
|
33429
34022
|
size: "14px"
|
|
@@ -33646,7 +34239,7 @@ const HeadingIcon = ({
|
|
|
33646
34239
|
const logoName = appName.toLowerCase().replace(/\s+/g, "-");
|
|
33647
34240
|
// Accounts has no tapestry Logo, so use the cog icon
|
|
33648
34241
|
if (logoName === "accounts") {
|
|
33649
|
-
return /*#__PURE__*/React__default.createElement(Icon, {
|
|
34242
|
+
return /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33650
34243
|
name: "general.cog",
|
|
33651
34244
|
color: "foregroundSecondary"
|
|
33652
34245
|
});
|
|
@@ -33710,7 +34303,7 @@ const CustomSearchBox = ({
|
|
|
33710
34303
|
},
|
|
33711
34304
|
placeholder: "Search our documentation",
|
|
33712
34305
|
value: query,
|
|
33713
|
-
renderRight: /*#__PURE__*/React__default.createElement(Icon, {
|
|
34306
|
+
renderRight: /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33714
34307
|
name: "general.search"
|
|
33715
34308
|
}),
|
|
33716
34309
|
onChange: e => setQuery(e.target.value),
|
|
@@ -33778,16 +34371,24 @@ const DocumentationSearch = ({
|
|
|
33778
34371
|
// not the path, so updating it is a same-document navigation that doesn't
|
|
33779
34372
|
// reload the iframe, but also lets the route survive a forced reload (e.g.
|
|
33780
34373
|
// Turbo re-parenting the topbar).
|
|
33781
|
-
|
|
34374
|
+
//
|
|
34375
|
+
// `panel` loads picobot as its own toolbar panel rather than inside Helpdesk.
|
|
34376
|
+
function buildPicobotSrc(origin, path, {
|
|
34377
|
+
panel = false
|
|
34378
|
+
} = {}) {
|
|
33782
34379
|
const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
|
|
33783
34380
|
const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
|
|
33784
34381
|
const fragment = queryPart !== undefined ? `${safePath}?${queryPart}` : safePath;
|
|
33785
|
-
const
|
|
34382
|
+
const url = new URL("topbar", origin);
|
|
34383
|
+
if (panel) url.searchParams.set("panel", "1");
|
|
34384
|
+
const base = url.toString();
|
|
33786
34385
|
return fragment ? `${base}#${fragment}` : base;
|
|
33787
34386
|
}
|
|
33788
34387
|
|
|
33789
34388
|
const LOAD_TIMEOUT_MS = 3000;
|
|
33790
|
-
function createPicobotIframe(picobotOrigin
|
|
34389
|
+
function createPicobotIframe(picobotOrigin, {
|
|
34390
|
+
panel = false
|
|
34391
|
+
} = {}) {
|
|
33791
34392
|
return /*#__PURE__*/React__default.forwardRef(function PicobotIframe({
|
|
33792
34393
|
currentPath: iframePath,
|
|
33793
34394
|
onChangePath: onIframeChangePath,
|
|
@@ -33843,7 +34444,9 @@ function createPicobotIframe(picobotOrigin) {
|
|
|
33843
34444
|
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, picobotOrigin);
|
|
33844
34445
|
}
|
|
33845
34446
|
}), []);
|
|
33846
|
-
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath
|
|
34447
|
+
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath, {
|
|
34448
|
+
panel
|
|
34449
|
+
});
|
|
33847
34450
|
return /*#__PURE__*/React__default.createElement("iframe", {
|
|
33848
34451
|
ref: iframeRef,
|
|
33849
34452
|
src: iframeSrc,
|
|
@@ -33882,22 +34485,28 @@ function HelpdeskComponent({
|
|
|
33882
34485
|
currentPath,
|
|
33883
34486
|
env,
|
|
33884
34487
|
helpdeskConfig,
|
|
33885
|
-
|
|
34488
|
+
isPicobotLoading,
|
|
34489
|
+
onChangePath,
|
|
34490
|
+
openPicobot
|
|
33886
34491
|
}) {
|
|
33887
34492
|
const PicobotIframe = React__default.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot")), [env]);
|
|
34493
|
+
const loadingFallback = /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
34494
|
+
flex: 1,
|
|
34495
|
+
axis: "vertical"
|
|
34496
|
+
}, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
34497
|
+
closeDrawer: closeDrawer
|
|
34498
|
+
}, /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
34499
|
+
axis: "horizontal",
|
|
34500
|
+
spacing: 1
|
|
34501
|
+
}, /*#__PURE__*/React__default.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React__default.createElement(LoadingIndicator$1, null))));
|
|
34502
|
+
// The add-on reads its args once, so mounting before the gate answers
|
|
34503
|
+
// would leave out openPicobot for good.
|
|
34504
|
+
if (isPicobotLoading) return loadingFallback;
|
|
33888
34505
|
return /*#__PURE__*/React__default.createElement(UiExtensionFirstPartyComponent, {
|
|
33889
34506
|
grow: 1,
|
|
33890
34507
|
display: "flex",
|
|
33891
34508
|
extensionId: env === "staging" ? "42" : "11",
|
|
33892
|
-
loadingFallback:
|
|
33893
|
-
flex: 1,
|
|
33894
|
-
axis: "vertical"
|
|
33895
|
-
}, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
33896
|
-
closeDrawer: closeDrawer
|
|
33897
|
-
}, /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
33898
|
-
axis: "horizontal",
|
|
33899
|
-
spacing: 1
|
|
33900
|
-
}, /*#__PURE__*/React__default.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React__default.createElement(LoadingIndicator$1, null)))),
|
|
34509
|
+
loadingFallback: loadingFallback,
|
|
33901
34510
|
additionalComponents: {
|
|
33902
34511
|
AskAIBanner: {
|
|
33903
34512
|
component: AskAIBanner,
|
|
@@ -33995,7 +34604,11 @@ function HelpdeskComponent({
|
|
|
33995
34604
|
currentPageParams: location.search,
|
|
33996
34605
|
currentPath,
|
|
33997
34606
|
helpdeskConfig,
|
|
33998
|
-
onChangePath
|
|
34607
|
+
onChangePath,
|
|
34608
|
+
// Its presence tells helpdesk that Picobot has its own panel.
|
|
34609
|
+
...(openPicobot && {
|
|
34610
|
+
openPicobot
|
|
34611
|
+
})
|
|
33999
34612
|
}
|
|
34000
34613
|
});
|
|
34001
34614
|
}
|
|
@@ -34542,6 +35155,142 @@ const Notifications = {
|
|
|
34542
35155
|
name: "Notifications"
|
|
34543
35156
|
};
|
|
34544
35157
|
|
|
35158
|
+
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";
|
|
35159
|
+
// Helpdesk reads this context off the email path to open its email form the
|
|
35160
|
+
// same way a handoff inside its own drawer does.
|
|
35161
|
+
function emailPathWithContext(context = {}) {
|
|
35162
|
+
const params = new URLSearchParams();
|
|
35163
|
+
for (const key of ["conversationId", "source", "turnIndex"]) {
|
|
35164
|
+
const value = context[key];
|
|
35165
|
+
if (value !== null && value !== undefined) params.set(key, String(value));
|
|
35166
|
+
}
|
|
35167
|
+
return `email?${params}`;
|
|
35168
|
+
}
|
|
35169
|
+
function PicobotComponent({
|
|
35170
|
+
clearPending,
|
|
35171
|
+
closeDrawer,
|
|
35172
|
+
currentPath,
|
|
35173
|
+
env,
|
|
35174
|
+
onChangePath,
|
|
35175
|
+
openTool,
|
|
35176
|
+
pending
|
|
35177
|
+
}) {
|
|
35178
|
+
const PicobotIframe = React__default.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot"), {
|
|
35179
|
+
panel: true
|
|
35180
|
+
}), [env]);
|
|
35181
|
+
const picobotRef = React__default.useRef(null);
|
|
35182
|
+
const [isPicobotReady, setIsPicobotReady] = React__default.useState(false);
|
|
35183
|
+
// Picobot has no listener until it sends READY, so openPicobot's draft or
|
|
35184
|
+
// conversation waits for it.
|
|
35185
|
+
React__default.useEffect(function handOverPendingOncePicobotIsReady() {
|
|
35186
|
+
var _a, _b;
|
|
35187
|
+
if (!pending || !isPicobotReady) return;
|
|
35188
|
+
const {
|
|
35189
|
+
conversationId,
|
|
35190
|
+
draft,
|
|
35191
|
+
ticketExternalId
|
|
35192
|
+
} = pending;
|
|
35193
|
+
if (conversationId) {
|
|
35194
|
+
(_a = picobotRef.current) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
35195
|
+
payload: {
|
|
35196
|
+
conversation_id: conversationId,
|
|
35197
|
+
// Picobot asks whether this resolved the ticket, and closes it.
|
|
35198
|
+
...(ticketExternalId && {
|
|
35199
|
+
external_id: ticketExternalId,
|
|
35200
|
+
source: "ticket_submitted"
|
|
35201
|
+
})
|
|
35202
|
+
},
|
|
35203
|
+
type: "LOAD_CONVERSATION"
|
|
35204
|
+
});
|
|
35205
|
+
} else if (draft) {
|
|
35206
|
+
// Not both: Picobot cancels a conversation load when a prefill
|
|
35207
|
+
// starts a new conversation.
|
|
35208
|
+
(_b = picobotRef.current) === null || _b === void 0 ? void 0 : _b.postMessage({
|
|
35209
|
+
payload: {
|
|
35210
|
+
source: "email_form",
|
|
35211
|
+
text: draft
|
|
35212
|
+
},
|
|
35213
|
+
type: "PREFILL_COMPOSER"
|
|
35214
|
+
});
|
|
35215
|
+
}
|
|
35216
|
+
clearPending();
|
|
35217
|
+
}, [pending, isPicobotReady, clearPending]);
|
|
35218
|
+
const [iframeErrored, setIframeErrored] = React__default.useState(false);
|
|
35219
|
+
const [mountKey, setMountKey] = React__default.useState(0);
|
|
35220
|
+
const handleRetry = React__default.useCallback(() => {
|
|
35221
|
+
setIframeErrored(false);
|
|
35222
|
+
setIsPicobotReady(false);
|
|
35223
|
+
setMountKey(key => key + 1);
|
|
35224
|
+
}, []);
|
|
35225
|
+
const handleMessage = React__default.useCallback(data => {
|
|
35226
|
+
var _a;
|
|
35227
|
+
const message = data;
|
|
35228
|
+
switch (message === null || message === void 0 ? void 0 : message.type) {
|
|
35229
|
+
case "CLOSE_DRAWER":
|
|
35230
|
+
return closeDrawer();
|
|
35231
|
+
case "READY":
|
|
35232
|
+
return setIsPicobotReady(true);
|
|
35233
|
+
case "PICOBOT_SUPPORT_HANDOFF":
|
|
35234
|
+
return openTool("helpdesk", ((_a = message.payload) === null || _a === void 0 ? void 0 : _a.method) === "zoom" ? "phone" : emailPathWithContext(message.payload));
|
|
35235
|
+
case "IFRAME_LOAD_ERROR":
|
|
35236
|
+
console.warn("topbar: Picobot iframe failed to load");
|
|
35237
|
+
return setIframeErrored(true);
|
|
35238
|
+
}
|
|
35239
|
+
}, [closeDrawer, openTool]);
|
|
35240
|
+
if (iframeErrored) {
|
|
35241
|
+
return /*#__PURE__*/React__default.createElement(LoadError, {
|
|
35242
|
+
onContactSupport: () => openTool("helpdesk", "email"),
|
|
35243
|
+
onRetry: handleRetry
|
|
35244
|
+
});
|
|
35245
|
+
}
|
|
35246
|
+
return /*#__PURE__*/React__default.createElement(PicobotIframe, {
|
|
35247
|
+
key: mountKey,
|
|
35248
|
+
ref: picobotRef,
|
|
35249
|
+
currentPath: currentPath,
|
|
35250
|
+
onChangePath: onChangePath,
|
|
35251
|
+
onMessage: handleMessage
|
|
35252
|
+
});
|
|
35253
|
+
}
|
|
35254
|
+
function LoadError({
|
|
35255
|
+
onContactSupport,
|
|
35256
|
+
onRetry
|
|
35257
|
+
}) {
|
|
35258
|
+
return /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
35259
|
+
axis: "vertical",
|
|
35260
|
+
flex: 1,
|
|
35261
|
+
alignment: "center",
|
|
35262
|
+
distribution: "center",
|
|
35263
|
+
padding: 3,
|
|
35264
|
+
spacing: 1
|
|
35265
|
+
}, /*#__PURE__*/React__default.createElement(Heading, {
|
|
35266
|
+
level: 2
|
|
35267
|
+
}, "Picobot is temporarily unavailable"), /*#__PURE__*/React__default.createElement(Text, {
|
|
35268
|
+
size: 5,
|
|
35269
|
+
color: "var(--t-text-color-secondary)"
|
|
35270
|
+
}, "Try again or contact support."), /*#__PURE__*/React__default.createElement(Box, {
|
|
35271
|
+
paddingTop: 2
|
|
35272
|
+
}, /*#__PURE__*/React__default.createElement(Button$1, {
|
|
35273
|
+
onClick: onRetry,
|
|
35274
|
+
iconLeft: "general.refresh",
|
|
35275
|
+
theme: "primary",
|
|
35276
|
+
variant: "fill",
|
|
35277
|
+
title: "Retry"
|
|
35278
|
+
})), /*#__PURE__*/React__default.createElement(Button$1, {
|
|
35279
|
+
onClick: onContactSupport,
|
|
35280
|
+
variant: "naked",
|
|
35281
|
+
theme: "primary",
|
|
35282
|
+
title: "Contact support"
|
|
35283
|
+
}));
|
|
35284
|
+
}
|
|
35285
|
+
const Picobot = {
|
|
35286
|
+
component: PicobotComponent,
|
|
35287
|
+
icons: {
|
|
35288
|
+
staticDotPath: SPARKLE_PATH,
|
|
35289
|
+
staticPath: SPARKLE_PATH
|
|
35290
|
+
},
|
|
35291
|
+
name: "Picobot"
|
|
35292
|
+
};
|
|
35293
|
+
|
|
34545
35294
|
const {
|
|
34546
35295
|
LoadingIndicator,
|
|
34547
35296
|
StackView
|
|
@@ -34937,6 +35686,41 @@ const useChatConfig = ({
|
|
|
34937
35686
|
};
|
|
34938
35687
|
};
|
|
34939
35688
|
|
|
35689
|
+
// Picobot's API rejects requests without a version header, and apiRequest
|
|
35690
|
+
// sets its own headers, so the version rides in on the fetch.
|
|
35691
|
+
const picobotFetch = (url, opts) => defaultFetch(url, {
|
|
35692
|
+
...opts,
|
|
35693
|
+
headers: {
|
|
35694
|
+
...(opts === null || opts === void 0 ? void 0 : opts.headers),
|
|
35695
|
+
"X-PCO-API-Version": "2026-08-11"
|
|
35696
|
+
}
|
|
35697
|
+
});
|
|
35698
|
+
// Routes wait on the gate, so a slow Picobot can't hold them up for long.
|
|
35699
|
+
const GATE_TIMEOUT_MS = 3000;
|
|
35700
|
+
const usePicobotConfig = ({
|
|
35701
|
+
isEnabled
|
|
35702
|
+
}) => {
|
|
35703
|
+
// Undefined until Picobot answers.
|
|
35704
|
+
const [panelEnabled, setPanelEnabled] = useState();
|
|
35705
|
+
useEffect(() => {
|
|
35706
|
+
if (!isEnabled) return undefined;
|
|
35707
|
+
// Whatever settles first wins, so a late answer can't reopen the gate.
|
|
35708
|
+
const settle = enabled => setPanelEnabled(current => current !== null && current !== void 0 ? current : enabled);
|
|
35709
|
+
const timeout = setTimeout(() => settle(false), GATE_TIMEOUT_MS);
|
|
35710
|
+
apiRequest(picobotFetch, `${window.location.origin}/~api/picobot/v2/me`).then(({
|
|
35711
|
+
json
|
|
35712
|
+
}) => {
|
|
35713
|
+
var _a, _b;
|
|
35714
|
+
return settle(((_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);
|
|
35715
|
+
}).catch(() => settle(false)).finally(() => clearTimeout(timeout));
|
|
35716
|
+
return () => clearTimeout(timeout);
|
|
35717
|
+
}, [isEnabled]);
|
|
35718
|
+
return {
|
|
35719
|
+
displayPicobotIcon: panelEnabled === true,
|
|
35720
|
+
isLoading: isEnabled && panelEnabled === undefined
|
|
35721
|
+
};
|
|
35722
|
+
};
|
|
35723
|
+
|
|
34940
35724
|
const Profile = {
|
|
34941
35725
|
component: UserProfile,
|
|
34942
35726
|
icons: {
|
|
@@ -34947,10 +35731,16 @@ const Profile = {
|
|
|
34947
35731
|
};
|
|
34948
35732
|
|
|
34949
35733
|
const DURATION = 100;
|
|
35734
|
+
// Exempt paths match regardless of query, so `email?conversationId=…` is
|
|
35735
|
+
// exempt along with `email`.
|
|
34950
35736
|
function isToolPathExemptFromStorage(toolName, path) {
|
|
34951
35737
|
var _a, _b;
|
|
34952
35738
|
const tool = toolsRouteMap[toolName];
|
|
34953
|
-
|
|
35739
|
+
const [pathWithoutQuery] = path.split("?");
|
|
35740
|
+
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;
|
|
35741
|
+
}
|
|
35742
|
+
function isConversationPath(path) {
|
|
35743
|
+
return Boolean(path === null || path === void 0 ? void 0 : path.startsWith("conversations/"));
|
|
34954
35744
|
}
|
|
34955
35745
|
function scrollToolDrawerToTop(toolName) {
|
|
34956
35746
|
var _a;
|
|
@@ -34963,6 +35753,7 @@ const TOOLS_MAP = {
|
|
|
34963
35753
|
Chat,
|
|
34964
35754
|
Helpdesk,
|
|
34965
35755
|
Notifications,
|
|
35756
|
+
Picobot,
|
|
34966
35757
|
Profile,
|
|
34967
35758
|
Tasks
|
|
34968
35759
|
};
|
|
@@ -34990,7 +35781,8 @@ function Toolbar({
|
|
|
34990
35781
|
});
|
|
34991
35782
|
const {
|
|
34992
35783
|
currentContent,
|
|
34993
|
-
open
|
|
35784
|
+
open,
|
|
35785
|
+
pending
|
|
34994
35786
|
} = state;
|
|
34995
35787
|
const mounted = !!currentContent;
|
|
34996
35788
|
const storeData = useStore();
|
|
@@ -35003,6 +35795,15 @@ function Toolbar({
|
|
|
35003
35795
|
...(env === "development" ? _storeDataOverride !== null && _storeDataOverride !== void 0 ? _storeDataOverride : {} : {})
|
|
35004
35796
|
};
|
|
35005
35797
|
const updateStore = useUpdateStore();
|
|
35798
|
+
const {
|
|
35799
|
+
displayPicobotIcon,
|
|
35800
|
+
isLoading: isPicobotGateLoading
|
|
35801
|
+
} = usePicobotConfig({
|
|
35802
|
+
isEnabled: Boolean(userId)
|
|
35803
|
+
});
|
|
35804
|
+
// Picobot is a kill-switched tool: until its gate says the panel is
|
|
35805
|
+
// enabled, its routes are held, and if it never does, they're ignored.
|
|
35806
|
+
const routableTool = useCallback(name => name === Picobot.name.toLowerCase() && !displayPicobotIcon ? undefined : toolsRouteMap[name], [displayPicobotIcon]);
|
|
35006
35807
|
React__default.useEffect(() => {
|
|
35007
35808
|
if (open) {
|
|
35008
35809
|
document.body.style.overflow = "hidden";
|
|
@@ -35090,7 +35891,9 @@ function Toolbar({
|
|
|
35090
35891
|
});
|
|
35091
35892
|
}, [clearRoute]);
|
|
35092
35893
|
React__default.useEffect(() => {
|
|
35093
|
-
if (!toolbarFragmentRoute)
|
|
35894
|
+
if (!toolbarFragmentRoute || !routableTool(toolbarFragmentRoute.tool)) {
|
|
35895
|
+
return;
|
|
35896
|
+
}
|
|
35094
35897
|
const isExempt = isToolPathExemptFromStorage(toolbarFragmentRoute.tool, toolbarFragmentRoute.path);
|
|
35095
35898
|
if (isExempt) return;
|
|
35096
35899
|
const nextState = {
|
|
@@ -35100,12 +35903,32 @@ function Toolbar({
|
|
|
35100
35903
|
if (!shallowCompare(nextState, toolStateCookie)) {
|
|
35101
35904
|
setToolStateCookie(nextState);
|
|
35102
35905
|
}
|
|
35103
|
-
}, [toolbarFragmentRoute, toolStateCookie]);
|
|
35906
|
+
}, [toolbarFragmentRoute, toolStateCookie, routableTool]);
|
|
35104
35907
|
React__default.useEffect(() => {
|
|
35105
|
-
|
|
35106
|
-
|
|
35908
|
+
// Helpdesk conversations may belong to Picobot, so they wait for its gate
|
|
35909
|
+
// rather than flashing open in Helpdesk first.
|
|
35910
|
+
const mayRedirectToPicobot = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path) && (isPicobotGateLoading || displayPicobotIcon);
|
|
35911
|
+
const tool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && !mayRedirectToPicobot && routableTool(toolbarFragmentRoute.tool);
|
|
35912
|
+
if (tool && (currentContent !== tool || !open)) {
|
|
35913
|
+
setToolAndOpenDrawer(tool);
|
|
35107
35914
|
}
|
|
35108
|
-
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, currentContent, open]);
|
|
35915
|
+
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path, currentContent, open, routableTool, isPicobotGateLoading, displayPicobotIcon]);
|
|
35916
|
+
const openTool = useCallback((name, path, pendingValue) => {
|
|
35917
|
+
const tool = routableTool(name.toLowerCase());
|
|
35918
|
+
if (!tool) return;
|
|
35919
|
+
dispatch({
|
|
35920
|
+
payload: pendingValue === undefined ? null : {
|
|
35921
|
+
tool: tool.name,
|
|
35922
|
+
value: pendingValue
|
|
35923
|
+
},
|
|
35924
|
+
type: ActionType.setPending
|
|
35925
|
+
});
|
|
35926
|
+
onChangeToolbarFragmentRoute(tool.name.toLowerCase(), path);
|
|
35927
|
+
scrollToolDrawerToTop(tool.name);
|
|
35928
|
+
}, [onChangeToolbarFragmentRoute, routableTool]);
|
|
35929
|
+
const clearPending = useCallback(() => dispatch({
|
|
35930
|
+
type: ActionType.clearPending
|
|
35931
|
+
}), []);
|
|
35109
35932
|
const toggleDrawer = content => {
|
|
35110
35933
|
if (!open) {
|
|
35111
35934
|
return openDrawer(content);
|
|
@@ -35122,12 +35945,52 @@ function Toolbar({
|
|
|
35122
35945
|
joltPromise: storeData === null || storeData === void 0 ? void 0 : storeData.joltPromise,
|
|
35123
35946
|
userId
|
|
35124
35947
|
});
|
|
35125
|
-
|
|
35948
|
+
// Picobot conversations moved from Helpdesk to the Picobot panel, so
|
|
35949
|
+
// send old links and saved paths there once the panel is enabled.
|
|
35950
|
+
React__default.useEffect(() => {
|
|
35951
|
+
if (!displayPicobotIcon) return;
|
|
35952
|
+
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path)) {
|
|
35953
|
+
onChangeToolbarFragmentRoute(Picobot.name.toLowerCase(), toolbarFragmentRoute.path);
|
|
35954
|
+
}
|
|
35955
|
+
}, [displayPicobotIcon, toolbarFragmentRoute, onChangeToolbarFragmentRoute]);
|
|
35956
|
+
React__default.useEffect(() => {
|
|
35957
|
+
if (!displayPicobotIcon) return;
|
|
35958
|
+
const {
|
|
35959
|
+
helpdesk,
|
|
35960
|
+
picobot
|
|
35961
|
+
} = toolStateCookie;
|
|
35962
|
+
if (!isConversationPath(helpdesk)) return;
|
|
35963
|
+
setToolStateCookie({
|
|
35964
|
+
...toolStateCookie,
|
|
35965
|
+
helpdesk: "",
|
|
35966
|
+
picobot: picobot || helpdesk
|
|
35967
|
+
});
|
|
35968
|
+
}, [displayPicobotIcon, toolStateCookie]);
|
|
35969
|
+
const openPicobot = useCallback(options => {
|
|
35970
|
+
var _a;
|
|
35971
|
+
// Moving an open Picobot off its path reloads or navigates the iframe,
|
|
35972
|
+
// which would race the options it's being handed.
|
|
35973
|
+
const path = currentContent === Picobot ? (_a = toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path) !== null && _a !== void 0 ? _a : "" : "";
|
|
35974
|
+
openTool(Picobot.name, path, options);
|
|
35975
|
+
}, [openTool, currentContent, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path]);
|
|
35976
|
+
React__default.useEffect(() => {
|
|
35977
|
+
if (!displayPicobotIcon) return undefined;
|
|
35978
|
+
window.topbar = window.topbar || {};
|
|
35979
|
+
window.topbar.openPicobot = openPicobot;
|
|
35980
|
+
return () => {
|
|
35981
|
+
delete window.topbar.openPicobot;
|
|
35982
|
+
};
|
|
35983
|
+
}, [displayPicobotIcon, openPicobot]);
|
|
35984
|
+
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat, displayPicobotIcon && Picobot, isPcoTestHost() && DeveloperTools].filter(Boolean);
|
|
35126
35985
|
const handleToolOnClick = tool => {
|
|
35127
35986
|
toggleDrawer(tool);
|
|
35128
35987
|
scrollToolDrawerToTop(tool.name);
|
|
35129
35988
|
};
|
|
35130
35989
|
const toolSpecificProps = {
|
|
35990
|
+
Helpdesk: {
|
|
35991
|
+
isPicobotLoading: isPicobotGateLoading,
|
|
35992
|
+
openPicobot: displayPicobotIcon ? openPicobot : undefined
|
|
35993
|
+
},
|
|
35131
35994
|
Profile: {
|
|
35132
35995
|
appName,
|
|
35133
35996
|
closeDrawer,
|
|
@@ -35150,6 +36013,7 @@ function Toolbar({
|
|
|
35150
36013
|
const routeIsForCurrentTool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && toolsRouteMap[toolbarFragmentRoute.tool] === currentContent;
|
|
35151
36014
|
const currentToolProps = {
|
|
35152
36015
|
appName,
|
|
36016
|
+
clearPending,
|
|
35153
36017
|
closeDrawer,
|
|
35154
36018
|
currentPath: routeIsForCurrentTool ? toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path : null,
|
|
35155
36019
|
env,
|
|
@@ -35158,6 +36022,8 @@ function Toolbar({
|
|
|
35158
36022
|
joltPromise,
|
|
35159
36023
|
onChangePath,
|
|
35160
36024
|
openDrawer: () => currentContent && openDrawer(currentContent),
|
|
36025
|
+
openTool,
|
|
36026
|
+
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,
|
|
35161
36027
|
userId,
|
|
35162
36028
|
...(currentContent ? toolSpecificProps[currentContent.name] : {})
|
|
35163
36029
|
};
|
|
@@ -35181,25 +36047,24 @@ function Toolbar({
|
|
|
35181
36047
|
})), /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35182
36048
|
className: styles$1["toolbar-button--home"],
|
|
35183
36049
|
onClick: () => closeDrawer()
|
|
35184
|
-
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
36050
|
+
}, /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
35185
36051
|
name: "toolbar.planningCenter",
|
|
35186
36052
|
size: "24px"
|
|
35187
36053
|
})), [HelpDeskTool, ...TOOLS].map(tool => {
|
|
36054
|
+
var _a;
|
|
35188
36055
|
const current = open && currentContent === tool;
|
|
35189
36056
|
const indicator = indicatorState[tool.name];
|
|
35190
36057
|
return /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35191
36058
|
current: current,
|
|
35192
36059
|
onClick: () => handleToolOnClick(tool),
|
|
35193
36060
|
id: getDataPendoAttribute(tool.name),
|
|
35194
|
-
"aria-label": `Open ${tool.name} drawer`,
|
|
36061
|
+
"aria-label": `Open ${(_a = tool.label) !== null && _a !== void 0 ? _a : tool.name} drawer`,
|
|
35195
36062
|
"data-pendo": getDataPendoAttribute(tool.name),
|
|
35196
36063
|
key: tool.name
|
|
35197
|
-
},
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
dot: true
|
|
35202
|
-
})));
|
|
36064
|
+
}, /*#__PURE__*/React__default.createElement(ToolbarToolIcon, {
|
|
36065
|
+
icons: tool.icons,
|
|
36066
|
+
dot: !!indicator
|
|
36067
|
+
}));
|
|
35203
36068
|
})), /*#__PURE__*/React__default.createElement(DrawerWrap, {
|
|
35204
36069
|
isVisible: open
|
|
35205
36070
|
}, mounted && (/*#__PURE__*/React__default.createElement(Drawer, {
|
|
@@ -35223,6 +36088,19 @@ const ToolbarButton = ({
|
|
|
35223
36088
|
...restProps
|
|
35224
36089
|
}, children);
|
|
35225
36090
|
};
|
|
36091
|
+
function ToolbarToolIcon({
|
|
36092
|
+
dot,
|
|
36093
|
+
icons
|
|
36094
|
+
}) {
|
|
36095
|
+
if ("component" in icons) {
|
|
36096
|
+
return /*#__PURE__*/React__default.createElement(icons.component, null);
|
|
36097
|
+
} else {
|
|
36098
|
+
return /*#__PURE__*/React__default.createElement(ToolbarButtonIcon, {
|
|
36099
|
+
path: dot ? icons.staticDotPath : icons.staticPath,
|
|
36100
|
+
dot: dot
|
|
36101
|
+
});
|
|
36102
|
+
}
|
|
36103
|
+
}
|
|
35226
36104
|
function ToolbarButtonIcon({
|
|
35227
36105
|
dot,
|
|
35228
36106
|
path
|