@planningcenter/topbar 12.3.0 → 12.4.0-qa-958.1
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 +582 -17
- package/dist/topbar.css.map +1 -1
- package/dist/topbar.js +2358 -864
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +2362 -869
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +2362 -869
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +2360 -867
- package/dist/topbar.umd.js.map +1 -1
- package/package.json +4 -3
package/dist/topbar.module.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Link, Button, Popover, PopoverTrigger, PopoverContent, Modal } from '@planningcenter/tapestry';
|
|
1
|
+
import { Link, Button, Popover, PopoverTrigger, PopoverContent, Flex, Select, Modal, ModalHeader } from '@planningcenter/tapestry';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default, { useState, useEffect, useMemo, useLayoutEffect, useContext, createContext, useCallback, useRef } from 'react';
|
|
4
4
|
import pcoUrl$1, { pcoUrl as pcoUrl$2, pcoApiUrl } from '@planningcenter/url';
|
|
@@ -6,18 +6,19 @@ 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
|
|
9
|
+
import * as toolbar from '@planningcenter/icons/paths/toolbar';
|
|
10
|
+
import * as logomarks from '@planningcenter/icons/paths/logomark';
|
|
11
|
+
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
12
|
import { components, UiExtensionFirstPartyComponent, UiExtensionDataContext } from '@planningcenter/add-ons';
|
|
11
13
|
import * as churchCenter from '@planningcenter/icons/paths/church-center';
|
|
12
14
|
import * as people from '@planningcenter/icons/paths/people';
|
|
13
|
-
import * as toolbar from '@planningcenter/icons/paths/toolbar';
|
|
14
15
|
import { InlineWidget } from 'react-calendly';
|
|
15
16
|
import algoliasearch from 'algoliasearch';
|
|
16
17
|
import { useSearchBox, useHits, InstantSearch, Configure } from 'react-instantsearch-core';
|
|
17
18
|
import { TransitionGroup, Transition } from 'react-transition-group';
|
|
18
19
|
import datetimeFmt from '@planningcenter/datetime-fmt';
|
|
19
20
|
|
|
20
|
-
var styles$
|
|
21
|
+
var styles$s = {"accessibility-toolbar":"accessibility-toolbar-jgjUG"};
|
|
21
22
|
|
|
22
23
|
const AccessibilityToolbar = ({
|
|
23
24
|
children
|
|
@@ -29,7 +30,7 @@ const AccessibilityToolbar = ({
|
|
|
29
30
|
(_a = document.getElementById("main-content")) === null || _a === void 0 ? void 0 : _a.focus();
|
|
30
31
|
};
|
|
31
32
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32
|
-
className: styles$
|
|
33
|
+
className: styles$s["accessibility-toolbar"]
|
|
33
34
|
}, /*#__PURE__*/React__default.createElement(Link, {
|
|
34
35
|
href: "#main-content",
|
|
35
36
|
kind: "primary",
|
|
@@ -156,12 +157,14 @@ function inferRailsCsrfToken() {
|
|
|
156
157
|
}
|
|
157
158
|
function apiRequest(fetch, url, {
|
|
158
159
|
method = "GET",
|
|
159
|
-
data = null
|
|
160
|
+
data = null,
|
|
161
|
+
headers = {}
|
|
160
162
|
} = {}) {
|
|
161
163
|
const options = {
|
|
162
164
|
headers: {
|
|
163
165
|
Accept: "application/json",
|
|
164
|
-
"Content-Type": "application/json"
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
...headers
|
|
165
168
|
},
|
|
166
169
|
method,
|
|
167
170
|
redirect: "follow"
|
|
@@ -346,11 +349,11 @@ const X = ({
|
|
|
346
349
|
fill: fill
|
|
347
350
|
})));
|
|
348
351
|
|
|
349
|
-
var styles$
|
|
352
|
+
var styles$r = {"announcement":"announcement-pI5tM","announcement--platform":"announcement--platform-EcUn2","announcement--promo":"announcement--promo-FarUk","announcement-icon":"announcement-icon-Ov39-","announcement-content":"announcement-content-q2ad6","dismiss":"dismiss-wUAoU","dismiss--promo":"dismiss--promo-7zvO4"};
|
|
350
353
|
|
|
351
354
|
function AnnouncementIcon() {
|
|
352
355
|
return /*#__PURE__*/React__default.createElement("svg", {
|
|
353
|
-
className: styles$
|
|
356
|
+
className: styles$r["announcement-icon"],
|
|
354
357
|
width: "16",
|
|
355
358
|
height: "16",
|
|
356
359
|
viewBox: "0 0 16 16",
|
|
@@ -475,7 +478,7 @@ let StyledAnnouncement$1 = class StyledAnnouncement extends React__default.Compo
|
|
|
475
478
|
} = this.props;
|
|
476
479
|
const isPromo = variant === "promo";
|
|
477
480
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
478
|
-
className: classnames(styles$
|
|
481
|
+
className: classnames(styles$r["announcement"], styles$r[`announcement--${variant !== null && variant !== void 0 ? variant : "platform"}`]),
|
|
479
482
|
"data-announcement-variant": variant !== null && variant !== void 0 ? variant : "platform",
|
|
480
483
|
style: isPromo ? {
|
|
481
484
|
backgroundImage: promoBackgroundUrl,
|
|
@@ -484,7 +487,7 @@ let StyledAnnouncement$1 = class StyledAnnouncement extends React__default.Compo
|
|
|
484
487
|
backgroundSize: "cover"
|
|
485
488
|
} : undefined
|
|
486
489
|
}, !isPromo && /*#__PURE__*/React__default.createElement(AnnouncementIcon, null), /*#__PURE__*/React__default.createElement("div", {
|
|
487
|
-
className: styles$
|
|
490
|
+
className: styles$r["announcement-content"],
|
|
488
491
|
style: style,
|
|
489
492
|
...platformProps
|
|
490
493
|
}));
|
|
@@ -533,7 +536,7 @@ function StyledDismissButton$1({
|
|
|
533
536
|
const isPromo = variant === "promo";
|
|
534
537
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
535
538
|
type: "button",
|
|
536
|
-
className: classnames(styles$
|
|
539
|
+
className: classnames(styles$r.dismiss, isPromo && styles$r["dismiss--promo"]),
|
|
537
540
|
...props
|
|
538
541
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
539
542
|
fill: isPromo ? "currentColor" : "white",
|
|
@@ -613,9 +616,9 @@ class StyledAnnouncement extends React__default.Component {
|
|
|
613
616
|
...platformProps
|
|
614
617
|
} = this.props;
|
|
615
618
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
616
|
-
className: styles$
|
|
619
|
+
className: styles$r["announcement"]
|
|
617
620
|
}, /*#__PURE__*/React__default.createElement(AnnouncementIcon, null), /*#__PURE__*/React__default.createElement("div", {
|
|
618
|
-
className: styles$
|
|
621
|
+
className: styles$r["announcement-content"],
|
|
619
622
|
style: style,
|
|
620
623
|
...platformProps
|
|
621
624
|
}));
|
|
@@ -626,7 +629,7 @@ function StyledDismissButton({
|
|
|
626
629
|
}) {
|
|
627
630
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
628
631
|
type: "button",
|
|
629
|
-
className: styles$
|
|
632
|
+
className: styles$r.dismiss,
|
|
630
633
|
...props
|
|
631
634
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
632
635
|
fill: "white",
|
|
@@ -781,7 +784,7 @@ function useMediaQuery(query) {
|
|
|
781
784
|
return matches;
|
|
782
785
|
}
|
|
783
786
|
|
|
784
|
-
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
787
|
+
const capitalize$1 = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
785
788
|
const RESOLVED_COLOR_MODE_ATTRIBUTE = "data-resolved-color-mode";
|
|
786
789
|
function resolveColorMode(colorMode) {
|
|
787
790
|
if (colorMode === "system") {
|
|
@@ -1748,7 +1751,7 @@ function MonoAppIcon({
|
|
|
1748
1751
|
})) : (/*#__PURE__*/React__default.createElement("div", null, "appName not supported")));
|
|
1749
1752
|
}
|
|
1750
1753
|
|
|
1751
|
-
var styles$
|
|
1754
|
+
var styles$q = {"icon-slot":"icon-slot-m3ZIk","name":"name-UNqbW"};
|
|
1752
1755
|
|
|
1753
1756
|
const AppRow = ({
|
|
1754
1757
|
appName,
|
|
@@ -1757,24 +1760,24 @@ const AppRow = ({
|
|
|
1757
1760
|
}) => {
|
|
1758
1761
|
if (appName === "Accounts") {
|
|
1759
1762
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
|
|
1760
|
-
className: styles$
|
|
1763
|
+
className: styles$q["icon-slot"]
|
|
1761
1764
|
}, /*#__PURE__*/React__default.createElement(MonoAppIcon, {
|
|
1762
1765
|
appName: "Accounts",
|
|
1763
1766
|
colors: colors,
|
|
1764
1767
|
size: iconSize
|
|
1765
1768
|
})), /*#__PURE__*/React__default.createElement("span", {
|
|
1766
|
-
className: styles$
|
|
1769
|
+
className: styles$q.name
|
|
1767
1770
|
}, "Account settings"));
|
|
1768
1771
|
}
|
|
1769
1772
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
1770
1773
|
appName: appName.replace(/[\s-]/, ""),
|
|
1771
1774
|
size: iconSize
|
|
1772
1775
|
}), /*#__PURE__*/React__default.createElement("span", {
|
|
1773
|
-
className: styles$
|
|
1776
|
+
className: styles$q.name
|
|
1774
1777
|
}, appName));
|
|
1775
1778
|
};
|
|
1776
1779
|
|
|
1777
|
-
var styles$
|
|
1780
|
+
var styles$p = {"button":"button-XMbeQ","label":"label-884of","chevron":"chevron-RRg9k"};
|
|
1778
1781
|
|
|
1779
1782
|
const AppsButton = /*#__PURE__*/React__default.forwardRef(({
|
|
1780
1783
|
appName,
|
|
@@ -1786,10 +1789,10 @@ const AppsButton = /*#__PURE__*/React__default.forwardRef(({
|
|
|
1786
1789
|
...restProps,
|
|
1787
1790
|
ref: ref,
|
|
1788
1791
|
"aria-label": `App switcher: ${appName}`,
|
|
1789
|
-
className: styles$
|
|
1792
|
+
className: styles$p.button,
|
|
1790
1793
|
kind: "neutral-inline",
|
|
1791
1794
|
label: /*#__PURE__*/React__default.createElement("span", {
|
|
1792
|
-
className: styles$
|
|
1795
|
+
className: styles$p.label
|
|
1793
1796
|
}, isAccounts ? "Account settings" : appName),
|
|
1794
1797
|
prefix: isAccounts ? (/*#__PURE__*/React__default.createElement(MonoAppIcon, {
|
|
1795
1798
|
appName: "Accounts",
|
|
@@ -1800,13 +1803,13 @@ const AppsButton = /*#__PURE__*/React__default.forwardRef(({
|
|
|
1800
1803
|
size: 22
|
|
1801
1804
|
})),
|
|
1802
1805
|
suffix: /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, {
|
|
1803
|
-
className: `${styles$
|
|
1806
|
+
className: `${styles$p.chevron} symbol`
|
|
1804
1807
|
})
|
|
1805
1808
|
});
|
|
1806
1809
|
});
|
|
1807
1810
|
AppsButton.displayName = "AppsButton";
|
|
1808
1811
|
|
|
1809
|
-
var styles$
|
|
1812
|
+
var styles$o = {"nav":"nav-y-6ML","popover-content":"popover-content-jOAAY","list":"list-jUxvp","link":"link-LMp2h","current":"current-4U4VL","accounts-item":"accounts-item-erJW0","accounts-link":"accounts-link-4jwPW"};
|
|
1810
1813
|
|
|
1811
1814
|
const AppsMenu = ({
|
|
1812
1815
|
appName,
|
|
@@ -1827,8 +1830,8 @@ const AppsMenu = ({
|
|
|
1827
1830
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
1828
1831
|
"aria-current": isCurrent ? "page" : undefined,
|
|
1829
1832
|
"aria-label": name,
|
|
1830
|
-
className: classnames(styles$
|
|
1831
|
-
[styles$
|
|
1833
|
+
className: classnames(styles$o.link, {
|
|
1834
|
+
[styles$o.current]: isCurrent
|
|
1832
1835
|
}),
|
|
1833
1836
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
1834
1837
|
"data-turbolinks": false,
|
|
@@ -1844,22 +1847,22 @@ const AppsMenu = ({
|
|
|
1844
1847
|
colors: colors
|
|
1845
1848
|
})), /*#__PURE__*/React__default.createElement(PopoverContent, {
|
|
1846
1849
|
"aria-label": "Switch app",
|
|
1847
|
-
className: styles$
|
|
1850
|
+
className: styles$o["popover-content"],
|
|
1848
1851
|
placement: "bottom start"
|
|
1849
1852
|
}, /*#__PURE__*/React__default.createElement("nav", {
|
|
1850
|
-
className: styles$
|
|
1853
|
+
className: styles$o.nav,
|
|
1851
1854
|
"data-pendo": "app-switcher"
|
|
1852
1855
|
}, /*#__PURE__*/React__default.createElement("ul", {
|
|
1853
|
-
className: styles$
|
|
1856
|
+
className: styles$o.list,
|
|
1854
1857
|
role: "list"
|
|
1855
1858
|
}, homeProduct && renderProductLink(homeProduct.name), restProducts.map(({
|
|
1856
1859
|
name
|
|
1857
1860
|
}) => renderProductLink(name)), accountsProduct && (/*#__PURE__*/React__default.createElement("li", {
|
|
1858
|
-
className: styles$
|
|
1861
|
+
className: styles$o["accounts-item"]
|
|
1859
1862
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
1860
1863
|
"aria-current": appName === accountsProduct.name ? "page" : undefined,
|
|
1861
1864
|
"aria-label": "Account settings",
|
|
1862
|
-
className: classnames(styles$
|
|
1865
|
+
className: classnames(styles$o.link, styles$o["accounts-link"]),
|
|
1863
1866
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
1864
1867
|
"data-turbolinks": false,
|
|
1865
1868
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -2067,7 +2070,7 @@ function MonoAppName({
|
|
|
2067
2070
|
})) : (/*#__PURE__*/React__default.createElement("div", null, "appName not supported"));
|
|
2068
2071
|
}
|
|
2069
2072
|
|
|
2070
|
-
var styles$
|
|
2073
|
+
var styles$n = {"unbutton":"unbutton-lEbTR"};
|
|
2071
2074
|
|
|
2072
2075
|
function Unbutton({
|
|
2073
2076
|
className,
|
|
@@ -2076,7 +2079,7 @@ function Unbutton({
|
|
|
2076
2079
|
...nativeProps
|
|
2077
2080
|
}) {
|
|
2078
2081
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
2079
|
-
className: classnames(styles$
|
|
2082
|
+
className: classnames(styles$n.unbutton, className),
|
|
2080
2083
|
style: {
|
|
2081
2084
|
...style
|
|
2082
2085
|
},
|
|
@@ -2085,7 +2088,7 @@ function Unbutton({
|
|
|
2085
2088
|
});
|
|
2086
2089
|
}
|
|
2087
2090
|
|
|
2088
|
-
var styles$
|
|
2091
|
+
var styles$m = {"button":"button-wsipc","button-accounts":"button-accounts-zQmBM","chevron":"chevron-obyBF","is-expanded":"is-expanded-PoCQ9"};
|
|
2089
2092
|
|
|
2090
2093
|
const LegacyAppsButton = ({
|
|
2091
2094
|
appName,
|
|
@@ -2100,8 +2103,8 @@ const LegacyAppsButton = ({
|
|
|
2100
2103
|
"aria-haspopup": "true",
|
|
2101
2104
|
"aria-label": `App switcher: ${appName}`,
|
|
2102
2105
|
onClick: onClick,
|
|
2103
|
-
className: classnames(styles$
|
|
2104
|
-
[styles$
|
|
2106
|
+
className: classnames(styles$m.button, {
|
|
2107
|
+
[styles$m["is-expanded"]]: expanded
|
|
2105
2108
|
}),
|
|
2106
2109
|
type: "button",
|
|
2107
2110
|
style: style,
|
|
@@ -2110,7 +2113,7 @@ const LegacyAppsButton = ({
|
|
|
2110
2113
|
appName: "Accounts",
|
|
2111
2114
|
colors: colors
|
|
2112
2115
|
}), /*#__PURE__*/React__default.createElement("div", {
|
|
2113
|
-
className: styles$
|
|
2116
|
+
className: styles$m["button-accounts"]
|
|
2114
2117
|
}, "Account settings"))) : (/*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
2115
2118
|
appName: appName.replace(/[\s-]/, "")
|
|
2116
2119
|
}), /*#__PURE__*/React__default.createElement(MonoAppName, {
|
|
@@ -2118,7 +2121,7 @@ const LegacyAppsButton = ({
|
|
|
2118
2121
|
key: "mono-app-name",
|
|
2119
2122
|
size: 24
|
|
2120
2123
|
}))), /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, {
|
|
2121
|
-
className: styles$
|
|
2124
|
+
className: styles$m.chevron,
|
|
2122
2125
|
key: "disclosure-chevron-icon"
|
|
2123
2126
|
}));
|
|
2124
2127
|
};
|
|
@@ -2251,7 +2254,7 @@ class Outsider extends React__default.Component {
|
|
|
2251
2254
|
}
|
|
2252
2255
|
}
|
|
2253
2256
|
|
|
2254
|
-
var styles$
|
|
2257
|
+
var styles$l = {"popup-root":"popup-root-SeL5u","popup":"popup-nQXYW","is-visible":"is-visible-3DAUP"};
|
|
2255
2258
|
|
|
2256
2259
|
const Popup = ({
|
|
2257
2260
|
className,
|
|
@@ -2259,8 +2262,8 @@ const Popup = ({
|
|
|
2259
2262
|
isVisible,
|
|
2260
2263
|
...nativeProps
|
|
2261
2264
|
}) => (/*#__PURE__*/React__default.createElement(C, {
|
|
2262
|
-
className: classnames(styles$
|
|
2263
|
-
[styles$
|
|
2265
|
+
className: classnames(styles$l.popup, {
|
|
2266
|
+
[styles$l["is-visible"]]: isVisible
|
|
2264
2267
|
}, className),
|
|
2265
2268
|
...nativeProps
|
|
2266
2269
|
}));
|
|
@@ -2268,11 +2271,11 @@ const Popup = ({
|
|
|
2268
2271
|
const PopupRoot = ({
|
|
2269
2272
|
...nativeProps
|
|
2270
2273
|
}) => (/*#__PURE__*/React__default.createElement("div", {
|
|
2271
|
-
className: styles$
|
|
2274
|
+
className: styles$l["popup-root"],
|
|
2272
2275
|
...nativeProps
|
|
2273
2276
|
}));
|
|
2274
2277
|
|
|
2275
|
-
var styles$
|
|
2278
|
+
var styles$k = {"popup":"popup-bVhSa"};
|
|
2276
2279
|
|
|
2277
2280
|
const LegacyAppsMenu = ({
|
|
2278
2281
|
appName,
|
|
@@ -2310,7 +2313,7 @@ const LegacyAppsMenu = ({
|
|
|
2310
2313
|
cleanup: !visible,
|
|
2311
2314
|
component: Outsider,
|
|
2312
2315
|
onOutsideClick: toggle,
|
|
2313
|
-
className: styles$
|
|
2316
|
+
className: styles$k.popup,
|
|
2314
2317
|
isVisible: visible
|
|
2315
2318
|
}, /*#__PURE__*/React__default.createElement("menu", {
|
|
2316
2319
|
ref: menuRef,
|
|
@@ -3220,29 +3223,121 @@ const useUpdateStore = () => useCallback(updates => {
|
|
|
3220
3223
|
});
|
|
3221
3224
|
}, []);
|
|
3222
3225
|
|
|
3223
|
-
const
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
const
|
|
3228
|
-
function
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3226
|
+
const collections = {
|
|
3227
|
+
general,
|
|
3228
|
+
toolbar
|
|
3229
|
+
};
|
|
3230
|
+
const paths = collections;
|
|
3231
|
+
function Icon({
|
|
3232
|
+
name,
|
|
3233
|
+
size = 16,
|
|
3234
|
+
style,
|
|
3235
|
+
theme,
|
|
3236
|
+
title,
|
|
3237
|
+
...props
|
|
3238
|
+
}) {
|
|
3239
|
+
const [collection, symbol] = name.split(".");
|
|
3240
|
+
return /*#__PURE__*/React__default.createElement("svg", {
|
|
3241
|
+
"aria-hidden": title ? undefined : true,
|
|
3242
|
+
fill: "currentColor",
|
|
3243
|
+
height: size,
|
|
3244
|
+
role: title ? "img" : undefined,
|
|
3245
|
+
style: {
|
|
3246
|
+
color: theme && `var(${theme})`,
|
|
3247
|
+
flexShrink: 0,
|
|
3248
|
+
...style
|
|
3249
|
+
},
|
|
3250
|
+
viewBox: "0 0 16 16",
|
|
3251
|
+
width: size,
|
|
3252
|
+
...props
|
|
3253
|
+
}, title && /*#__PURE__*/React__default.createElement("title", null, title), /*#__PURE__*/React__default.createElement("path", {
|
|
3254
|
+
d: paths[collection][symbol]
|
|
3255
|
+
}));
|
|
3234
3256
|
}
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3257
|
+
|
|
3258
|
+
const SEARCHABLE_PRODUCTS = [{
|
|
3259
|
+
appName: "Calendar",
|
|
3260
|
+
slug: "calendar"
|
|
3261
|
+
}, {
|
|
3262
|
+
appName: "Check-Ins",
|
|
3263
|
+
slug: "check-ins"
|
|
3264
|
+
}, {
|
|
3265
|
+
appName: "Giving",
|
|
3266
|
+
slug: "giving"
|
|
3267
|
+
}, {
|
|
3268
|
+
appName: "Groups",
|
|
3269
|
+
slug: "groups"
|
|
3270
|
+
}, {
|
|
3271
|
+
appName: "People",
|
|
3272
|
+
slug: "people"
|
|
3273
|
+
}, {
|
|
3274
|
+
appName: "Publishing",
|
|
3275
|
+
slug: "publishing"
|
|
3276
|
+
}, {
|
|
3277
|
+
appName: "Registrations",
|
|
3278
|
+
slug: "registrations"
|
|
3279
|
+
}, {
|
|
3280
|
+
appName: "Services",
|
|
3281
|
+
slug: "services"
|
|
3282
|
+
}];
|
|
3283
|
+
const KEYBOARD_SHORTCUTS = {
|
|
3284
|
+
copyLink: {
|
|
3285
|
+
key: "c",
|
|
3286
|
+
label: "Copy link",
|
|
3287
|
+
modifierKey: true
|
|
3288
|
+
},
|
|
3289
|
+
openInNewTab: {
|
|
3290
|
+
label: "Open in new tab"},
|
|
3291
|
+
openSearch: {
|
|
3292
|
+
key: "k",
|
|
3293
|
+
label: "Open search",
|
|
3294
|
+
modifierKey: true
|
|
3295
|
+
},
|
|
3296
|
+
select: {
|
|
3297
|
+
label: "Select",
|
|
3298
|
+
modifierKey: false,
|
|
3299
|
+
next: "ArrowDown",
|
|
3300
|
+
previous: "ArrowUp"
|
|
3240
3301
|
}
|
|
3241
|
-
}
|
|
3302
|
+
};
|
|
3303
|
+
|
|
3304
|
+
const isMac = () => /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
3305
|
+
const hasCommandKey = event => isMac() ? event.metaKey : event.ctrlKey;
|
|
3306
|
+
const matchesShortcut = (shortcut, event) => event.key.toLowerCase() === shortcut.key && !event.altKey && !event.shiftKey && hasCommandKey(event) === shortcut.modifierKey;
|
|
3307
|
+
const isSearchShortcut = event => matchesShortcut(KEYBOARD_SHORTCUTS.openSearch, event);
|
|
3308
|
+
// Products opened their own search with `/` before this one replaced it.
|
|
3309
|
+
const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !event.ctrlKey && !event.metaKey && !isTypingIn(event.target);
|
|
3310
|
+
const isCopyShortcut = event => matchesShortcut(KEYBOARD_SHORTCUTS.copyLink, event);
|
|
3311
|
+
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3312
|
+
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3313
|
+
const verticalStep = key => ({
|
|
3314
|
+
[KEYBOARD_SHORTCUTS.select.next]: 1,
|
|
3315
|
+
[KEYBOARD_SHORTCUTS.select.previous]: -1
|
|
3316
|
+
})[key];
|
|
3317
|
+
const horizontalStep = key => ({
|
|
3318
|
+
ArrowLeft: -1,
|
|
3319
|
+
ArrowRight: 1
|
|
3320
|
+
})[key];
|
|
3242
3321
|
|
|
3243
|
-
var styles$
|
|
3322
|
+
var styles$j = {"searchButton":"searchButton-fYa5-","mobile":"mobile-ksZyA"};
|
|
3244
3323
|
|
|
3245
|
-
|
|
3324
|
+
function SearchButton({
|
|
3325
|
+
mobile = false,
|
|
3326
|
+
onClick
|
|
3327
|
+
}) {
|
|
3328
|
+
return /*#__PURE__*/React__default.createElement("button", {
|
|
3329
|
+
"aria-keyshortcuts": mobile ? undefined : `${isMac() ? "Meta" : "Control"}+${KEYBOARD_SHORTCUTS.openSearch.key.toUpperCase()} /`,
|
|
3330
|
+
"aria-label": "Search",
|
|
3331
|
+
className: classnames(styles$j.searchButton, {
|
|
3332
|
+
[styles$j.mobile]: mobile
|
|
3333
|
+
}),
|
|
3334
|
+
onClick: onClick,
|
|
3335
|
+
type: "button"
|
|
3336
|
+
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
3337
|
+
name: "general.search",
|
|
3338
|
+
size: mobile ? 18 : 14
|
|
3339
|
+
}));
|
|
3340
|
+
}
|
|
3246
3341
|
|
|
3247
3342
|
// useId isn't available on React 17, which our peer range still supports.
|
|
3248
3343
|
let instanceCount$1 = 0;
|
|
@@ -3251,173 +3346,33 @@ function useInstanceId(prefix) {
|
|
|
3251
3346
|
return id;
|
|
3252
3347
|
}
|
|
3253
3348
|
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
}
|
|
3279
|
-
};
|
|
3280
|
-
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
3281
|
-
"aria-label": "Product",
|
|
3282
|
-
onKeyDown: handleKeyDown,
|
|
3283
|
-
role: "tablist"
|
|
3284
|
-
}, products.map(product => (/*#__PURE__*/React__default.createElement("button", {
|
|
3285
|
-
"aria-controls": `${id}-panel`,
|
|
3286
|
-
"aria-selected": product === selected,
|
|
3287
|
-
className: styles$b.tab,
|
|
3288
|
-
id: tabId(product.slug),
|
|
3289
|
-
key: product.slug,
|
|
3290
|
-
onClick: () => onSelect(product.slug),
|
|
3291
|
-
ref: element => {
|
|
3292
|
-
if (element) {
|
|
3293
|
-
tabRefs.current.set(product.slug, element);
|
|
3294
|
-
} else {
|
|
3295
|
-
tabRefs.current.delete(product.slug);
|
|
3296
|
-
}
|
|
3297
|
-
},
|
|
3298
|
-
role: "tab",
|
|
3299
|
-
tabIndex: product === selected ? 0 : -1,
|
|
3300
|
-
type: "button"
|
|
3301
|
-
}, product.appName)))), /*#__PURE__*/React__default.createElement("div", {
|
|
3302
|
-
"aria-labelledby": tabId(selected.slug),
|
|
3303
|
-
id: `${id}-panel`,
|
|
3304
|
-
role: "tabpanel"
|
|
3305
|
-
}, children));
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
var styles$a = {"result":"result-WCnxr"};
|
|
3309
|
-
|
|
3310
|
-
function SearchResult({
|
|
3311
|
-
highlighted,
|
|
3312
|
-
id,
|
|
3313
|
-
onHighlight,
|
|
3314
|
-
result
|
|
3315
|
-
}) {
|
|
3316
|
-
const {
|
|
3317
|
-
metadata,
|
|
3318
|
-
title,
|
|
3319
|
-
url
|
|
3320
|
-
} = result.attributes;
|
|
3321
|
-
const ref = React__default.useRef(null);
|
|
3322
|
-
React__default.useEffect(() => {
|
|
3323
|
-
var _a;
|
|
3324
|
-
if (highlighted) (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
3325
|
-
block: "nearest"
|
|
3326
|
-
});
|
|
3327
|
-
}, [highlighted]);
|
|
3328
|
-
// Focus stays in the search input, which points at the highlighted result
|
|
3329
|
-
// with aria-activedescendant, so results are kept out of the tab order.
|
|
3330
|
-
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
3331
|
-
// pointer, and that shouldn't steal the highlight.
|
|
3332
|
-
return /*#__PURE__*/React__default.createElement("a", {
|
|
3333
|
-
"aria-selected": highlighted,
|
|
3334
|
-
className: styles$a.result,
|
|
3335
|
-
href: url,
|
|
3336
|
-
id: id,
|
|
3337
|
-
onMouseMove: onHighlight,
|
|
3338
|
-
ref: ref,
|
|
3339
|
-
role: "option",
|
|
3340
|
-
tabIndex: -1
|
|
3341
|
-
}, /*#__PURE__*/React__default.createElement("div", null, title), /*#__PURE__*/React__default.createElement("div", null, summarize(metadata)));
|
|
3342
|
-
}
|
|
3343
|
-
function summarize(metadata) {
|
|
3344
|
-
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
3348
|
-
function SearchResults({
|
|
3349
|
-
search,
|
|
3350
|
-
term,
|
|
3351
|
-
...navigation
|
|
3352
|
-
}) {
|
|
3353
|
-
var _a;
|
|
3354
|
-
switch (search.status) {
|
|
3355
|
-
case "idle":
|
|
3356
|
-
return null;
|
|
3357
|
-
case "loading":
|
|
3358
|
-
if ((_a = search.groups) === null || _a === void 0 ? void 0 : _a.length) {
|
|
3359
|
-
return /*#__PURE__*/React__default.createElement(ResultGroups, {
|
|
3360
|
-
busy: true,
|
|
3361
|
-
groups: search.groups,
|
|
3362
|
-
...navigation
|
|
3363
|
-
});
|
|
3364
|
-
}
|
|
3365
|
-
return /*#__PURE__*/React__default.createElement("p", {
|
|
3366
|
-
role: "status"
|
|
3367
|
-
}, "Searching\u2026");
|
|
3368
|
-
case "error":
|
|
3369
|
-
return /*#__PURE__*/React__default.createElement("p", {
|
|
3370
|
-
role: "alert"
|
|
3371
|
-
}, "Search isn\u2019t working right now. Try again.");
|
|
3372
|
-
case "done":
|
|
3373
|
-
if (search.groups.length === 0) {
|
|
3374
|
-
return /*#__PURE__*/React__default.createElement("p", {
|
|
3375
|
-
role: "status"
|
|
3376
|
-
}, "No results for \u201C", term.trim(), "\u201D");
|
|
3377
|
-
}
|
|
3378
|
-
return /*#__PURE__*/React__default.createElement(ResultGroups, {
|
|
3379
|
-
groups: search.groups,
|
|
3380
|
-
...navigation
|
|
3381
|
-
});
|
|
3349
|
+
// The versions home-mobile pins, so web and mobile read the same response
|
|
3350
|
+
// shapes. Some endpoints only exist from a version on: Registrations' me,
|
|
3351
|
+
// product_search_results, and pcrl_lookups need 2025-05-01.
|
|
3352
|
+
const API_VERSIONS = {
|
|
3353
|
+
calendar: "2022-07-07",
|
|
3354
|
+
"check-ins": "2025-05-28",
|
|
3355
|
+
giving: "2019-10-18",
|
|
3356
|
+
groups: "2023-07-10",
|
|
3357
|
+
home: "2018-08-01",
|
|
3358
|
+
people: "2023-03-21",
|
|
3359
|
+
publishing: "2024-03-25",
|
|
3360
|
+
registrations: "2025-05-01",
|
|
3361
|
+
services: "2018-11-01"
|
|
3362
|
+
};
|
|
3363
|
+
// For a `/~api/{product}/v2/...` URL, the header that pins its product's
|
|
3364
|
+
// version.
|
|
3365
|
+
function apiVersionHeaders(url) {
|
|
3366
|
+
const [,, product] = new URL(url).pathname.split("/");
|
|
3367
|
+
const version = API_VERSIONS[product];
|
|
3368
|
+
if (version) {
|
|
3369
|
+
return {
|
|
3370
|
+
"X-PCO-API-Version": version
|
|
3371
|
+
};
|
|
3372
|
+
} else {
|
|
3373
|
+
return {};
|
|
3382
3374
|
}
|
|
3383
3375
|
}
|
|
3384
|
-
function ResultGroups({
|
|
3385
|
-
busy = false,
|
|
3386
|
-
groups,
|
|
3387
|
-
highlightedIndex,
|
|
3388
|
-
id,
|
|
3389
|
-
label,
|
|
3390
|
-
onHighlight
|
|
3391
|
-
}) {
|
|
3392
|
-
let index = 0;
|
|
3393
|
-
return /*#__PURE__*/React__default.createElement("div", {
|
|
3394
|
-
"aria-busy": busy,
|
|
3395
|
-
"aria-label": label,
|
|
3396
|
-
id: id,
|
|
3397
|
-
role: "listbox"
|
|
3398
|
-
}, groups.map(({
|
|
3399
|
-
results,
|
|
3400
|
-
type
|
|
3401
|
-
}, groupIndex) => {
|
|
3402
|
-
const headingId = `${id}-group-${groupIndex}`;
|
|
3403
|
-
return /*#__PURE__*/React__default.createElement("div", {
|
|
3404
|
-
"aria-labelledby": headingId,
|
|
3405
|
-
key: type,
|
|
3406
|
-
role: "group"
|
|
3407
|
-
}, /*#__PURE__*/React__default.createElement("div", {
|
|
3408
|
-
id: headingId
|
|
3409
|
-
}, type), results.map(result => {
|
|
3410
|
-
const resultIndex = index++;
|
|
3411
|
-
return /*#__PURE__*/React__default.createElement(SearchResult, {
|
|
3412
|
-
highlighted: resultIndex === highlightedIndex,
|
|
3413
|
-
id: resultId(id, resultIndex),
|
|
3414
|
-
key: result.id,
|
|
3415
|
-
onHighlight: () => onHighlight(resultIndex),
|
|
3416
|
-
result: result
|
|
3417
|
-
});
|
|
3418
|
-
}));
|
|
3419
|
-
}));
|
|
3420
|
-
}
|
|
3421
3376
|
|
|
3422
3377
|
const CACHE_TTL_MS = 60000;
|
|
3423
3378
|
const MAX_ENTRIES$1 = 50;
|
|
@@ -3434,7 +3389,9 @@ function cachedRequest(url) {
|
|
|
3434
3389
|
}
|
|
3435
3390
|
}
|
|
3436
3391
|
function fetchIntoCache(url) {
|
|
3437
|
-
const promise = apiRequest(defaultFetch, url
|
|
3392
|
+
const promise = apiRequest(defaultFetch, url, {
|
|
3393
|
+
headers: apiVersionHeaders(url)
|
|
3394
|
+
}).then(({
|
|
3438
3395
|
json,
|
|
3439
3396
|
ok
|
|
3440
3397
|
}) => {
|
|
@@ -3490,44 +3447,52 @@ function useCachedRequest(url) {
|
|
|
3490
3447
|
};
|
|
3491
3448
|
}
|
|
3492
3449
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
const
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
const
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
return {
|
|
3517
|
-
|
|
3518
|
-
status: "done"
|
|
3519
|
-
};
|
|
3520
|
-
} else if (request.status === "error") {
|
|
3521
|
-
return {
|
|
3522
|
-
status: "error"
|
|
3523
|
-
};
|
|
3524
|
-
} else {
|
|
3525
|
-
return {
|
|
3526
|
-
groups,
|
|
3527
|
-
status: "loading"
|
|
3450
|
+
// One request per URL, for callers that fan out to a varying number of
|
|
3451
|
+
// endpoints. Unlike `useCachedRequest`, a URL that hasn't settled yet has no
|
|
3452
|
+
// earlier data to fall back on.
|
|
3453
|
+
function useCachedRequests(urls) {
|
|
3454
|
+
const [settled, setSettled] = React__default.useState({});
|
|
3455
|
+
// Keyed by value because each settle re-renders the caller, and a caller
|
|
3456
|
+
// that builds a fresh `urls` array on render would otherwise loop forever.
|
|
3457
|
+
const key = urls.join("\n");
|
|
3458
|
+
React__default.useEffect(() => {
|
|
3459
|
+
let current = true;
|
|
3460
|
+
const settle = result => current && setSettled(prev => ({
|
|
3461
|
+
...prev,
|
|
3462
|
+
[result.url]: result
|
|
3463
|
+
}));
|
|
3464
|
+
for (const url of key.split("\n").filter(Boolean)) {
|
|
3465
|
+
cachedRequest(url).then(data => settle({
|
|
3466
|
+
data,
|
|
3467
|
+
url
|
|
3468
|
+
}), () => settle({
|
|
3469
|
+
failed: true,
|
|
3470
|
+
url
|
|
3471
|
+
}));
|
|
3472
|
+
}
|
|
3473
|
+
return () => {
|
|
3474
|
+
current = false;
|
|
3528
3475
|
};
|
|
3529
|
-
}
|
|
3476
|
+
}, [key]);
|
|
3477
|
+
return urls.map(url => {
|
|
3478
|
+
const result = settled[url];
|
|
3479
|
+
if (!result) {
|
|
3480
|
+
return {
|
|
3481
|
+
status: "loading"
|
|
3482
|
+
};
|
|
3483
|
+
} else if ("data" in result) {
|
|
3484
|
+
return {
|
|
3485
|
+
data: result.data,
|
|
3486
|
+
status: "done"
|
|
3487
|
+
};
|
|
3488
|
+
} else {
|
|
3489
|
+
return {
|
|
3490
|
+
status: "error"
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
});
|
|
3530
3494
|
}
|
|
3495
|
+
|
|
3531
3496
|
function productSearchUrl(product, query) {
|
|
3532
3497
|
const params = new URLSearchParams({
|
|
3533
3498
|
per_page: "100",
|
|
@@ -3558,6 +3523,53 @@ function groupByType({
|
|
|
3558
3523
|
};
|
|
3559
3524
|
return [...groups.values()].sort((a, b) => rank(a.type) - rank(b.type));
|
|
3560
3525
|
}
|
|
3526
|
+
|
|
3527
|
+
function useDebouncedValue(value, delay) {
|
|
3528
|
+
const [debounced, setDebounced] = React__default.useState(value);
|
|
3529
|
+
React__default.useEffect(() => {
|
|
3530
|
+
const timeout = setTimeout(() => setDebounced(value), delay);
|
|
3531
|
+
return () => clearTimeout(timeout);
|
|
3532
|
+
}, [value, delay]);
|
|
3533
|
+
return debounced;
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
3537
|
+
function useProductSearch(product, term) {
|
|
3538
|
+
const query = term.trim();
|
|
3539
|
+
const debouncedQuery = useDebouncedValue(query, SEARCH_DEBOUNCE_MS);
|
|
3540
|
+
const request = useCachedRequest(debouncedQuery ? productSearchUrl(product, debouncedQuery) : null);
|
|
3541
|
+
const response = "data" in request ? request.data : undefined;
|
|
3542
|
+
const settledProduct = useSettledProduct(product, request.status);
|
|
3543
|
+
// While a request loads, the previous response stays on screen, but only
|
|
3544
|
+
// if it was for this product: People results must never show under the
|
|
3545
|
+
// Services tab.
|
|
3546
|
+
const visibleResponse = request.status === "done" || settledProduct === product ? response : undefined;
|
|
3547
|
+
const groups = React__default.useMemo(() => visibleResponse && groupByType(visibleResponse), [visibleResponse]);
|
|
3548
|
+
if (!query) {
|
|
3549
|
+
return {
|
|
3550
|
+
status: "idle"
|
|
3551
|
+
};
|
|
3552
|
+
} else if (query !== debouncedQuery) {
|
|
3553
|
+
return {
|
|
3554
|
+
groups,
|
|
3555
|
+
status: "loading"
|
|
3556
|
+
};
|
|
3557
|
+
} else if (request.status === "done") {
|
|
3558
|
+
return {
|
|
3559
|
+
groups: groups !== null && groups !== void 0 ? groups : [],
|
|
3560
|
+
status: "done"
|
|
3561
|
+
};
|
|
3562
|
+
} else if (request.status === "error") {
|
|
3563
|
+
return {
|
|
3564
|
+
status: "error"
|
|
3565
|
+
};
|
|
3566
|
+
} else {
|
|
3567
|
+
return {
|
|
3568
|
+
groups,
|
|
3569
|
+
status: "loading"
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3561
3573
|
// The product the most recent finished request was for.
|
|
3562
3574
|
function useSettledProduct(product, status) {
|
|
3563
3575
|
const [settledProduct, setSettledProduct] = React__default.useState();
|
|
@@ -3566,29 +3578,489 @@ function useSettledProduct(product, status) {
|
|
|
3566
3578
|
}, [product, status]);
|
|
3567
3579
|
return settledProduct;
|
|
3568
3580
|
}
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3581
|
+
|
|
3582
|
+
function parseValue(element) {
|
|
3583
|
+
const datatype = element.getAttribute("datatype");
|
|
3584
|
+
switch (datatype) {
|
|
3585
|
+
case "boolean":
|
|
3586
|
+
return element.content === "true" || element.content === "1";
|
|
3587
|
+
default:
|
|
3588
|
+
return element.content;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
function getAllPcoMeta() {
|
|
3592
|
+
const elements = document.head.querySelectorAll(`meta[property^="pco:"]`);
|
|
3593
|
+
const meta = {};
|
|
3594
|
+
elements.forEach(e => {
|
|
3595
|
+
const property = e.getAttribute("property");
|
|
3596
|
+
meta[property.substring("pco:".length)] = parseValue(e);
|
|
3597
|
+
});
|
|
3598
|
+
return meta;
|
|
3599
|
+
}
|
|
3600
|
+
// usePcoMeta only reads each tag's content. Recently Visited also needs the
|
|
3601
|
+
// pco:recently_visitable tag's `metadata` attribute, which can change while
|
|
3602
|
+
// the content stays the same, so it is tracked in its own store.
|
|
3603
|
+
function getRecentlyVisitableMetadata() {
|
|
3604
|
+
var _a, _b;
|
|
3605
|
+
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;
|
|
3606
|
+
}
|
|
3607
|
+
const store = createStore(getAllPcoMeta());
|
|
3608
|
+
const recentlyVisitableMetadataStore = createStore({
|
|
3609
|
+
metadata: getRecentlyVisitableMetadata()
|
|
3610
|
+
});
|
|
3611
|
+
function sync() {
|
|
3612
|
+
const nextState = getAllPcoMeta();
|
|
3613
|
+
if (!shallowCompare(store.getState(), nextState)) store.setState(nextState);
|
|
3614
|
+
const metadata = getRecentlyVisitableMetadata();
|
|
3615
|
+
if (recentlyVisitableMetadataStore.getState().metadata !== metadata) {
|
|
3616
|
+
recentlyVisitableMetadataStore.setState({
|
|
3617
|
+
metadata
|
|
3618
|
+
});
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
const observer = new MutationObserver(sync);
|
|
3622
|
+
let hasTriggeredObserve = false;
|
|
3623
|
+
function observe() {
|
|
3624
|
+
if (hasTriggeredObserve) return;
|
|
3625
|
+
hasTriggeredObserve = true;
|
|
3626
|
+
// The module-load snapshot above can predate meta tags that appear later
|
|
3627
|
+
// in <head> (e.g. pco:resource, yielded right before </head>) if this
|
|
3628
|
+
// module's own script tag isn't deferred and runs mid-parse. Those tags
|
|
3629
|
+
// never mutate afterward, so the observer alone would never catch them.
|
|
3630
|
+
// Re-read once now, since observe() is only called once a consumer is
|
|
3631
|
+
// actually mounting, which is always after the document has fully parsed.
|
|
3632
|
+
sync();
|
|
3633
|
+
observer.observe(document.head, {
|
|
3634
|
+
attributes: true,
|
|
3635
|
+
childList: true,
|
|
3636
|
+
subtree: true
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
function usePcoMeta() {
|
|
3640
|
+
observe();
|
|
3641
|
+
return useSyncExternalStore(store.subscribe, () => store.getState());
|
|
3642
|
+
}
|
|
3643
|
+
function useRecentlyVisitableMetadata() {
|
|
3644
|
+
observe();
|
|
3645
|
+
return useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3576
3646
|
}
|
|
3577
3647
|
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3648
|
+
// Entries match the ones home-mobile stores, so one list can sync across web
|
|
3649
|
+
// and mobile.
|
|
3650
|
+
const ENTRY_VERSION = 3;
|
|
3651
|
+
const MAX_ENTRIES = 50;
|
|
3652
|
+
const MAX_TEXT_LENGTH = 200;
|
|
3653
|
+
const PRODUCTS$1 = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3654
|
+
// Login has no pages worth returning to.
|
|
3655
|
+
const APPS = PRODUCTS$1.filter(product => product !== "login");
|
|
3656
|
+
function isId(value) {
|
|
3657
|
+
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3658
|
+
}
|
|
3659
|
+
// A PCRL looks like `<product>/v2/<resource_type>/<id>`.
|
|
3660
|
+
function isPcrl(value) {
|
|
3661
|
+
const segments = value.split("/");
|
|
3662
|
+
return segments.length === 4 && PRODUCTS$1.some(product => product === segments[0]) && isId(segments[3]);
|
|
3663
|
+
}
|
|
3664
|
+
function isApp(value) {
|
|
3665
|
+
return APPS.some(app => app === value);
|
|
3666
|
+
}
|
|
3667
|
+
// Links are built by resolving the path against its app's origin, so a stored
|
|
3668
|
+
// path must not be able to resolve anywhere else (`//evil.com`, `/\evil.com`).
|
|
3669
|
+
function isPath(value) {
|
|
3670
|
+
const origin = "https://app.invalid";
|
|
3671
|
+
try {
|
|
3672
|
+
return typeof value === "string" && value.startsWith("/") && new URL(value, origin).origin === origin;
|
|
3673
|
+
} catch {
|
|
3674
|
+
return false;
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
function isNonBlankString(value) {
|
|
3678
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3679
|
+
}
|
|
3680
|
+
function isOptionalString(value) {
|
|
3681
|
+
return value === undefined || typeof value === "string";
|
|
3682
|
+
}
|
|
3683
|
+
function isRecord(value) {
|
|
3684
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3685
|
+
}
|
|
3686
|
+
function isPageMetadata(value) {
|
|
3687
|
+
return value === undefined || isRecord(value) && isOptionalString(value.details) && isOptionalString(value.type);
|
|
3688
|
+
}
|
|
3689
|
+
// The query string is part of the link but not of the page's identity, so
|
|
3690
|
+
// revisiting with other filters moves the existing entry instead of adding one.
|
|
3691
|
+
function entryKey(entry) {
|
|
3692
|
+
if (entry.type === "resource") {
|
|
3693
|
+
return `resource:${entry.pcrl}`;
|
|
3694
|
+
} else {
|
|
3695
|
+
return `page:${entry.app}:${entry.path.split("?")[0]}`;
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
function isEntry(value) {
|
|
3699
|
+
if (!isRecord(value) || value.version !== ENTRY_VERSION || typeof value.visitedAt !== "string") {
|
|
3700
|
+
return false;
|
|
3701
|
+
} else if (value.type === "resource") {
|
|
3702
|
+
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3703
|
+
} else if (value.type === "page") {
|
|
3704
|
+
return isApp(value.app) && isPath(value.path) && isNonBlankString(value.title) && isPageMetadata(value.metadata);
|
|
3705
|
+
} else {
|
|
3706
|
+
return false;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
// Cuts on code points so a surrogate pair is never split.
|
|
3710
|
+
function clampText(value) {
|
|
3711
|
+
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3712
|
+
}
|
|
3713
|
+
function clampMetadata({
|
|
3714
|
+
details,
|
|
3715
|
+
type
|
|
3716
|
+
}) {
|
|
3717
|
+
return {
|
|
3718
|
+
...(details && {
|
|
3719
|
+
details: clampText(details)
|
|
3720
|
+
}),
|
|
3721
|
+
...(type && {
|
|
3722
|
+
type: clampText(type)
|
|
3723
|
+
})
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
function clampVisit(visit) {
|
|
3727
|
+
if (visit.type === "resource") {
|
|
3728
|
+
return visit;
|
|
3729
|
+
} else {
|
|
3730
|
+
return {
|
|
3731
|
+
...visit,
|
|
3732
|
+
...(visit.metadata && {
|
|
3733
|
+
metadata: clampMetadata(visit.metadata)
|
|
3734
|
+
}),
|
|
3735
|
+
title: clampText(visit.title.trim())
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
function toEntry(visit, visitedAt) {
|
|
3740
|
+
return {
|
|
3741
|
+
...clampVisit(visit),
|
|
3742
|
+
version: ENTRY_VERSION,
|
|
3743
|
+
visitedAt: visitedAt.toISOString()
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
3747
|
+
// a single bad entry, a list without it) rather than throwing.
|
|
3748
|
+
function parseRecentlyVisited(value) {
|
|
3749
|
+
let parsed = null;
|
|
3750
|
+
try {
|
|
3751
|
+
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
3752
|
+
} catch {
|
|
3753
|
+
parsed = null;
|
|
3754
|
+
}
|
|
3755
|
+
if (Array.isArray(parsed)) {
|
|
3756
|
+
return parsed.filter(isEntry);
|
|
3757
|
+
} else {
|
|
3758
|
+
return [];
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
function addVisit(entries, entry) {
|
|
3762
|
+
const key = entryKey(entry);
|
|
3763
|
+
return [entry, ...entries.filter(existing => entryKey(existing) !== key)].slice(0, MAX_ENTRIES);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
// The app whose URL, in this environment, has the current page's host.
|
|
3767
|
+
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
3768
|
+
// and guarantees the stored app rebuilds a working link.
|
|
3769
|
+
function currentApp() {
|
|
3770
|
+
var _a;
|
|
3771
|
+
return (_a = APPS.find(app => new URL(pcoUrl$2(app)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
3772
|
+
}
|
|
3773
|
+
function isFilled(value) {
|
|
3774
|
+
return typeof value === "string" && value !== "";
|
|
3775
|
+
}
|
|
3776
|
+
// The tag's metadata is product-supplied JSON; only its string `type` and
|
|
3777
|
+
// `details` carry over, and an entry without either has no metadata at all.
|
|
3778
|
+
function metadataFromPage(metadata) {
|
|
3779
|
+
var _a;
|
|
3780
|
+
try {
|
|
3781
|
+
const {
|
|
3782
|
+
details,
|
|
3783
|
+
type
|
|
3784
|
+
} = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) !== null && _a !== void 0 ? _a : {};
|
|
3785
|
+
if (isFilled(details) || isFilled(type)) {
|
|
3786
|
+
return {
|
|
3787
|
+
...(isFilled(details) && {
|
|
3788
|
+
details
|
|
3789
|
+
}),
|
|
3790
|
+
...(isFilled(type) && {
|
|
3791
|
+
type
|
|
3792
|
+
})
|
|
3793
|
+
};
|
|
3794
|
+
} else {
|
|
3795
|
+
return undefined;
|
|
3796
|
+
}
|
|
3797
|
+
} catch {
|
|
3798
|
+
return undefined;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
// Staff impersonating someone browse under `/O<org id>/P<person id>/…`.
|
|
3802
|
+
function withoutImpersonation(segments) {
|
|
3803
|
+
const [org = "", person = ""] = segments;
|
|
3804
|
+
if (org.startsWith("O") && isId(org.slice(1)) && person.startsWith("P") && isId(person.slice(1))) {
|
|
3805
|
+
return segments.slice(2);
|
|
3806
|
+
} else {
|
|
3807
|
+
return segments;
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
// Normalized the way home-mobile does it, so a page visited on web and on
|
|
3811
|
+
// mobile is one entry once the list syncs.
|
|
3812
|
+
function pathFromLocation({
|
|
3813
|
+
pathname,
|
|
3814
|
+
search
|
|
3815
|
+
}) {
|
|
3816
|
+
const segments = withoutImpersonation(pathname.split("/").filter(Boolean));
|
|
3817
|
+
if (segments.length === 0) {
|
|
3818
|
+
return "/";
|
|
3819
|
+
} else {
|
|
3820
|
+
return `/${segments.join("/")}${search.length > 1 ? search : ""}`;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
// pco:resource is the more specific signal, so it wins when both tags are
|
|
3824
|
+
// present. A resource that isn't a PCRL is ignored rather than recorded.
|
|
3825
|
+
function visitFromPage({
|
|
3826
|
+
metadata,
|
|
3827
|
+
resource,
|
|
3828
|
+
title
|
|
3829
|
+
}) {
|
|
3830
|
+
const app = currentApp();
|
|
3831
|
+
if (resource && isPcrl(resource)) {
|
|
3832
|
+
return {
|
|
3833
|
+
pcrl: resource,
|
|
3834
|
+
type: "resource"
|
|
3835
|
+
};
|
|
3836
|
+
} else if (title && title.trim() !== "" && app) {
|
|
3837
|
+
const pageMetadata = metadataFromPage(metadata);
|
|
3838
|
+
return {
|
|
3839
|
+
app,
|
|
3840
|
+
...(pageMetadata && {
|
|
3841
|
+
metadata: pageMetadata
|
|
3842
|
+
}),
|
|
3843
|
+
path: pathFromLocation(window.location),
|
|
3844
|
+
title,
|
|
3845
|
+
type: "page"
|
|
3846
|
+
};
|
|
3847
|
+
} else {
|
|
3848
|
+
return null;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
// The entry Recently Visited would record for the current page, or null when
|
|
3852
|
+
// the page isn't eligible. Shared by the recorder and the debug panel so the
|
|
3853
|
+
// panel always shows what actually gets stored.
|
|
3854
|
+
function useCurrentVisit() {
|
|
3855
|
+
const {
|
|
3856
|
+
recently_visitable: title,
|
|
3857
|
+
resource
|
|
3858
|
+
} = usePcoMeta();
|
|
3859
|
+
const metadata = useRecentlyVisitableMetadata();
|
|
3860
|
+
return useMemo(() => visitFromPage({
|
|
3861
|
+
metadata,
|
|
3862
|
+
resource,
|
|
3863
|
+
title
|
|
3864
|
+
}), [metadata, resource, title]);
|
|
3865
|
+
}
|
|
3866
|
+
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
3867
|
+
// and several pass it as a number.
|
|
3868
|
+
function useCurrentPersonId() {
|
|
3869
|
+
const {
|
|
3870
|
+
userId
|
|
3871
|
+
} = useStore();
|
|
3872
|
+
return userId == null ? undefined : String(userId);
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
// Per-origin localStorage is a stopgap: each product keeps its own list until
|
|
3876
|
+
// PCA-409 moves this to storage shared across product subdomains. Only this
|
|
3877
|
+
// file should need to change when it does.
|
|
3878
|
+
function storageKey({
|
|
3879
|
+
env,
|
|
3880
|
+
personId
|
|
3881
|
+
}) {
|
|
3882
|
+
return `topbar.recently_visited.${env}.${personId}`;
|
|
3883
|
+
}
|
|
3884
|
+
// localStorage doesn't notify the tab that wrote it, so readers in this tab
|
|
3885
|
+
// learn about new visits from this counter instead.
|
|
3886
|
+
const changes = createStore({
|
|
3887
|
+
version: 0
|
|
3888
|
+
});
|
|
3889
|
+
function readRecentlyVisited(scope) {
|
|
3890
|
+
try {
|
|
3891
|
+
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)));
|
|
3892
|
+
} catch {
|
|
3893
|
+
return [];
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
function recordVisit(visit, scope) {
|
|
3897
|
+
const entry = toEntry(visit, new Date());
|
|
3898
|
+
if (isEntry(entry)) {
|
|
3899
|
+
try {
|
|
3900
|
+
localStorage.setItem(storageKey(scope), JSON.stringify(addVisit(readRecentlyVisited(scope), entry)));
|
|
3901
|
+
changes.setState({
|
|
3902
|
+
version: changes.getState().version + 1
|
|
3903
|
+
});
|
|
3904
|
+
} catch {
|
|
3905
|
+
// Storage can be full or blocked; recents are disposable.
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
function useRecentlyVisited({
|
|
3910
|
+
env,
|
|
3911
|
+
personId
|
|
3912
|
+
}) {
|
|
3913
|
+
const version = useSyncExternalStore(changes.subscribe, () => changes.getState().version);
|
|
3914
|
+
// `version` isn't read, but listing it re-reads storage after each visit.
|
|
3915
|
+
return useMemo(() => personId ? readRecentlyVisited({
|
|
3916
|
+
env,
|
|
3917
|
+
personId
|
|
3918
|
+
}) : [], [env, personId, version]);
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
const pcoUrl = pcoUrl$1;
|
|
3922
|
+
|
|
3923
|
+
// Resolves resource entries with one pcrl_lookups request per product.
|
|
3924
|
+
// pcrl_lookups reports forbidden and deleted resources the same way, so both
|
|
3925
|
+
// become `unresolved`, as does every entry in a batch whose request failed.
|
|
3926
|
+
function useHydratedRecentlyVisited(entries, env) {
|
|
3927
|
+
const batches = React__default.useMemo(() => batchByProduct(entries), [entries]);
|
|
3928
|
+
const urls = React__default.useMemo(() => batches.map(([product, pcrls]) => pcrlLookupsUrl(product, pcrls)), [batches]);
|
|
3929
|
+
const requests = useCachedRequests(urls);
|
|
3930
|
+
const lookups = new Map();
|
|
3931
|
+
batches.forEach(([, pcrls], index) => {
|
|
3932
|
+
const request = requests[index];
|
|
3933
|
+
for (const pcrl of pcrls) lookups.set(pcrl, lookupFor(pcrl, request));
|
|
3589
3934
|
});
|
|
3590
|
-
|
|
3591
|
-
|
|
3935
|
+
return entries.map(entry => {
|
|
3936
|
+
var _a;
|
|
3937
|
+
if (entry.type === "page") {
|
|
3938
|
+
return {
|
|
3939
|
+
entry,
|
|
3940
|
+
status: "page",
|
|
3941
|
+
url: pageUrl(entry, env)
|
|
3942
|
+
};
|
|
3943
|
+
} else {
|
|
3944
|
+
const lookup = (_a = lookups.get(entry.pcrl)) !== null && _a !== void 0 ? _a : {
|
|
3945
|
+
status: "unresolved"
|
|
3946
|
+
};
|
|
3947
|
+
if (lookup.status === "resolved") {
|
|
3948
|
+
return {
|
|
3949
|
+
...lookup,
|
|
3950
|
+
entry,
|
|
3951
|
+
url: lookup.result.attributes.url
|
|
3952
|
+
};
|
|
3953
|
+
} else {
|
|
3954
|
+
return {
|
|
3955
|
+
entry,
|
|
3956
|
+
status: lookup.status
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
function lookupFor(pcrl, request) {
|
|
3963
|
+
if (request.status === "done") {
|
|
3964
|
+
const result = request.data.data.find(({
|
|
3965
|
+
attributes
|
|
3966
|
+
}) => attributes.pcrl === pcrl);
|
|
3967
|
+
return result ? {
|
|
3968
|
+
result,
|
|
3969
|
+
status: "resolved"
|
|
3970
|
+
} : {
|
|
3971
|
+
status: "unresolved"
|
|
3972
|
+
};
|
|
3973
|
+
} else if (request.status === "error") {
|
|
3974
|
+
return {
|
|
3975
|
+
status: "unresolved"
|
|
3976
|
+
};
|
|
3977
|
+
} else {
|
|
3978
|
+
return {
|
|
3979
|
+
status: "loading"
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
const RECENTLY_VISITED_PAGE_SIZE = 10;
|
|
3984
|
+
// A PCRL can only be looked up through its own product's API, which may not be
|
|
3985
|
+
// the product it was visited in. Each page of entries is batched on its own,
|
|
3986
|
+
// so loading the next page leaves earlier pages' URLs, and so their cache
|
|
3987
|
+
// keys, alone. Sorting keeps them stable as a page reorders.
|
|
3988
|
+
function batchByProduct(entries) {
|
|
3989
|
+
var _a;
|
|
3990
|
+
const batches = [];
|
|
3991
|
+
for (let start = 0; start < entries.length; start += RECENTLY_VISITED_PAGE_SIZE) {
|
|
3992
|
+
const page = new Map();
|
|
3993
|
+
for (const entry of entries.slice(start, start + RECENTLY_VISITED_PAGE_SIZE)) {
|
|
3994
|
+
if (entry.type === "resource") {
|
|
3995
|
+
const [product] = entry.pcrl.split("/");
|
|
3996
|
+
page.set(product, [...((_a = page.get(product)) !== null && _a !== void 0 ? _a : []), entry.pcrl]);
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
for (const [product, pcrls] of page) {
|
|
4000
|
+
batches.push([product, [...pcrls].sort()]);
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
return batches;
|
|
4004
|
+
}
|
|
4005
|
+
function pcrlLookupsUrl(product, pcrls) {
|
|
4006
|
+
const params = new URLSearchParams({
|
|
4007
|
+
"where[pcrl]": pcrls.join(",")
|
|
4008
|
+
});
|
|
4009
|
+
return `${window.location.origin}/~api/${product}/v2/pcrl_lookups?${params}`;
|
|
4010
|
+
}
|
|
4011
|
+
// Built from the app rather than the current origin, so it stays right once
|
|
4012
|
+
// PCA-409 shares one list across product subdomains.
|
|
4013
|
+
function pageUrl(entry, env) {
|
|
4014
|
+
return new URL(entry.path, pcoUrl(env)(entry.app)).href;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
function useRecentVisits(env) {
|
|
4018
|
+
const personId = useCurrentPersonId();
|
|
4019
|
+
const entries = useRecentlyVisited({
|
|
4020
|
+
env,
|
|
4021
|
+
personId
|
|
4022
|
+
});
|
|
4023
|
+
const [count, setCount] = React__default.useState(RECENTLY_VISITED_PAGE_SIZE);
|
|
4024
|
+
const shown = React__default.useMemo(() => entries.slice(0, count), [entries, count]);
|
|
4025
|
+
const hydrated = useHydratedRecentlyVisited(shown, env);
|
|
4026
|
+
// Resources show up once they're found. Ones that can't be found anymore,
|
|
4027
|
+
// or that the person can no longer see, are left out.
|
|
4028
|
+
const visits = hydrated.filter(isShown);
|
|
4029
|
+
const loading = hydrated.some(({
|
|
4030
|
+
status
|
|
4031
|
+
}) => status === "loading");
|
|
4032
|
+
const showMore = React__default.useCallback(() => setCount(current => current + RECENTLY_VISITED_PAGE_SIZE), []);
|
|
4033
|
+
return {
|
|
4034
|
+
count,
|
|
4035
|
+
hasMore: entries.length > count,
|
|
4036
|
+
loading,
|
|
4037
|
+
showMore,
|
|
4038
|
+
visits
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
function isShown(visit) {
|
|
4042
|
+
return visit.status === "page" || visit.status === "resolved";
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
function open(url, {
|
|
4046
|
+
newTab
|
|
4047
|
+
}) {
|
|
4048
|
+
if (newTab) {
|
|
4049
|
+
window.open(url, "_blank", "noopener");
|
|
4050
|
+
} else {
|
|
4051
|
+
window.location.assign(url);
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
// With text selected, ⌘C should copy that text as usual, not the link.
|
|
4055
|
+
function hasSelectedText(element) {
|
|
4056
|
+
var _a, _b;
|
|
4057
|
+
const selectedInField = element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
4058
|
+
return selectedInField || ((_b = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "") !== "";
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
const COPIED_FEEDBACK_MS = 1500;
|
|
4062
|
+
// `copiedUrl` stays set briefly after a copy so the UI can confirm it.
|
|
4063
|
+
function useCopyLink() {
|
|
3592
4064
|
const [copiedUrl, setCopiedUrl] = React__default.useState(null);
|
|
3593
4065
|
React__default.useEffect(() => {
|
|
3594
4066
|
if (copiedUrl) {
|
|
@@ -3598,201 +4070,721 @@ function useResultNavigation(results) {
|
|
|
3598
4070
|
return undefined;
|
|
3599
4071
|
}
|
|
3600
4072
|
}, [copiedUrl]);
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
4073
|
+
const copy = React__default.useCallback(url => {
|
|
4074
|
+
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
4075
|
+
}, []);
|
|
4076
|
+
return {
|
|
4077
|
+
copiedUrl,
|
|
4078
|
+
copy
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
// Remembering which list the index belongs to puts the highlight back on the
|
|
4083
|
+
// first item whenever a new list replaces it, without an effect to reset it.
|
|
4084
|
+
// A list that grows or fills in under the same `list` keeps its highlight.
|
|
4085
|
+
// Disabled, nothing is highlighted and the keys and pointer leave it alone.
|
|
4086
|
+
function useResultNavigation(items, list, enabled = true) {
|
|
4087
|
+
const [highlight, setHighlight] = React__default.useState({
|
|
4088
|
+
index: 0,
|
|
4089
|
+
list
|
|
4090
|
+
});
|
|
4091
|
+
const highlightedIndex = highlight.list === list ? highlight.index : 0;
|
|
4092
|
+
const highlighted = enabled ? items[highlightedIndex] : undefined;
|
|
4093
|
+
const {
|
|
4094
|
+
copiedUrl,
|
|
4095
|
+
copy
|
|
4096
|
+
} = useCopyLink();
|
|
4097
|
+
const highlightIndex = React__default.useCallback(index => {
|
|
4098
|
+
if (!enabled) return;
|
|
4099
|
+
setHighlight(current => current.list === list && current.index === index ? current : {
|
|
4100
|
+
index,
|
|
4101
|
+
list
|
|
4102
|
+
});
|
|
4103
|
+
}, [enabled, list]);
|
|
3605
4104
|
const handleKeyDown = event => {
|
|
4105
|
+
if (!enabled) return;
|
|
3606
4106
|
const step = verticalStep(event.key);
|
|
3607
|
-
if (step &&
|
|
4107
|
+
if (step && items.length > 0) {
|
|
3608
4108
|
event.preventDefault();
|
|
3609
|
-
highlightIndex((highlightedIndex + step +
|
|
4109
|
+
highlightIndex((highlightedIndex + step + items.length) % items.length);
|
|
3610
4110
|
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
3611
4111
|
event.preventDefault();
|
|
3612
|
-
open(highlighted.
|
|
4112
|
+
open(highlighted.url, {
|
|
3613
4113
|
newTab: hasCommandKey(event)
|
|
3614
4114
|
});
|
|
3615
4115
|
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
3616
4116
|
event.preventDefault();
|
|
3617
|
-
|
|
3618
|
-
url
|
|
3619
|
-
} = highlighted.attributes;
|
|
3620
|
-
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
4117
|
+
copy(highlighted.url);
|
|
3621
4118
|
}
|
|
3622
4119
|
};
|
|
3623
4120
|
return {
|
|
3624
|
-
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.
|
|
4121
|
+
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.url),
|
|
3625
4122
|
handleKeyDown,
|
|
3626
4123
|
highlightIndex,
|
|
3627
4124
|
highlighted,
|
|
3628
4125
|
highlightedIndex: highlighted ? highlightedIndex : null
|
|
3629
4126
|
};
|
|
3630
4127
|
}
|
|
3631
|
-
|
|
3632
|
-
|
|
4128
|
+
|
|
4129
|
+
var styles$i = {"modal":"modal-NR3TO","mobile":"mobile-4e5rC","header":"header-c3SLe","visuallyHidden":"visuallyHidden-C28ju","panel":"panel-HlF7S"};
|
|
4130
|
+
|
|
4131
|
+
function groupSections(groups = []) {
|
|
4132
|
+
return groups.map(({
|
|
4133
|
+
results,
|
|
4134
|
+
type
|
|
4135
|
+
}) => ({
|
|
4136
|
+
heading: type,
|
|
4137
|
+
items: results.map(resultItem),
|
|
4138
|
+
key: type
|
|
4139
|
+
}));
|
|
4140
|
+
}
|
|
4141
|
+
function resultItem({
|
|
4142
|
+
attributes,
|
|
4143
|
+
id
|
|
3633
4144
|
}) {
|
|
3634
|
-
|
|
3635
|
-
|
|
4145
|
+
return {
|
|
4146
|
+
category: attributes.category,
|
|
4147
|
+
imageUrl: attributes.image_url,
|
|
4148
|
+
key: id,
|
|
4149
|
+
product: attributes.product,
|
|
4150
|
+
subtitle: summarize(attributes.metadata),
|
|
4151
|
+
title: attributes.title,
|
|
4152
|
+
url: attributes.url
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
function visitItem(visit) {
|
|
4156
|
+
return {
|
|
4157
|
+
key: entryKey(visit.entry),
|
|
4158
|
+
...describeVisit(visit)
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4161
|
+
function summarize(metadata) {
|
|
4162
|
+
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
4163
|
+
}
|
|
4164
|
+
// Unlike search results, recents come from every product, so each one names
|
|
4165
|
+
// its product and type.
|
|
4166
|
+
function describeVisit(visit) {
|
|
4167
|
+
var _a, _b;
|
|
4168
|
+
const subtitle = (...parts) => parts.filter(Boolean).join(" · ");
|
|
4169
|
+
if (visit.status === "page") {
|
|
4170
|
+
const {
|
|
4171
|
+
app,
|
|
4172
|
+
metadata,
|
|
4173
|
+
title
|
|
4174
|
+
} = visit.entry;
|
|
4175
|
+
return {
|
|
4176
|
+
category: "generic",
|
|
4177
|
+
imageUrl: null,
|
|
4178
|
+
product: app,
|
|
4179
|
+
subtitle: subtitle(productName(app), (_a = metadata === null || metadata === void 0 ? void 0 : metadata.type) !== null && _a !== void 0 ? _a : "", (_b = metadata === null || metadata === void 0 ? void 0 : metadata.details) !== null && _b !== void 0 ? _b : ""),
|
|
4180
|
+
title,
|
|
4181
|
+
url: visit.url
|
|
4182
|
+
};
|
|
3636
4183
|
} else {
|
|
3637
|
-
|
|
4184
|
+
const {
|
|
4185
|
+
category,
|
|
4186
|
+
image_url,
|
|
4187
|
+
metadata,
|
|
4188
|
+
product,
|
|
4189
|
+
title,
|
|
4190
|
+
type
|
|
4191
|
+
} = visit.result.attributes;
|
|
4192
|
+
return {
|
|
4193
|
+
category,
|
|
4194
|
+
imageUrl: image_url,
|
|
4195
|
+
product,
|
|
4196
|
+
subtitle: subtitle(productName(product), type, summarize(metadata)),
|
|
4197
|
+
title,
|
|
4198
|
+
url: visit.url
|
|
4199
|
+
};
|
|
3638
4200
|
}
|
|
3639
4201
|
}
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
return
|
|
4202
|
+
// check-ins → Check-Ins
|
|
4203
|
+
function productName(slug) {
|
|
4204
|
+
return slug.split("-").map(capitalize).join("-");
|
|
4205
|
+
}
|
|
4206
|
+
function capitalize(word) {
|
|
4207
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
3646
4208
|
}
|
|
3647
4209
|
|
|
3648
|
-
const
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
}
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
4210
|
+
const STORAGE_KEY = "topbar.product_search.last_product";
|
|
4211
|
+
function readLastProduct() {
|
|
4212
|
+
try {
|
|
4213
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
4214
|
+
} catch {
|
|
4215
|
+
return null;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
function saveLastProduct(slug) {
|
|
4219
|
+
try {
|
|
4220
|
+
localStorage.setItem(STORAGE_KEY, slug);
|
|
4221
|
+
} catch {
|
|
4222
|
+
// Storage can be full or blocked; search still works without it.
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
function initialProduct(products) {
|
|
4226
|
+
var _a, _b, _c;
|
|
4227
|
+
const lastProduct = readLastProduct();
|
|
4228
|
+
const bySlug = slug => products.find(product => product.slug === slug);
|
|
4229
|
+
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];
|
|
4230
|
+
}
|
|
4231
|
+
|
|
4232
|
+
var styles$h = {"container":"container-YDdM1","hint":"hint-017Rd","keycap":"keycap-ENJM2","copied":"copied-DUFa1"};
|
|
4233
|
+
|
|
4234
|
+
const SYMBOLS = {
|
|
4235
|
+
command: {
|
|
4236
|
+
label: "⌘",
|
|
4237
|
+
paths: ["M7 9a2 2 0 1 1 2 -2v10a2 2 0 1 1 -2 -2h10a2 2 0 1 1 -2 2v-10a2 2 0 1 1 2 2h-10"]
|
|
4238
|
+
},
|
|
4239
|
+
down: {
|
|
4240
|
+
label: "↓",
|
|
4241
|
+
paths: ["M12 6l0 12", "M16 14l-4 4", "M8 14l4 4"]
|
|
4242
|
+
},
|
|
4243
|
+
return: {
|
|
4244
|
+
label: "↵",
|
|
4245
|
+
paths: ["M18 6v6a3 3 0 0 1 -3 3h-10l4 -4m0 8l-4 -4"]
|
|
4246
|
+
},
|
|
4247
|
+
up: {
|
|
4248
|
+
label: "↑",
|
|
4249
|
+
paths: ["M12 6l0 12", "M16 10l-4 -4", "M8 10l4 -4"]
|
|
4250
|
+
}
|
|
3666
4251
|
};
|
|
3667
|
-
function
|
|
3668
|
-
|
|
3669
|
-
|
|
4252
|
+
function Keycap(props) {
|
|
4253
|
+
return /*#__PURE__*/React__default.createElement("kbd", {
|
|
4254
|
+
className: styles$h.keycap
|
|
4255
|
+
}, "symbol" in props ? (/*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("svg", {
|
|
4256
|
+
"aria-hidden": true,
|
|
4257
|
+
fill: "none",
|
|
4258
|
+
stroke: "currentColor",
|
|
4259
|
+
strokeLinecap: "round",
|
|
4260
|
+
strokeLinejoin: "round",
|
|
4261
|
+
viewBox: "0 0 24 24"
|
|
4262
|
+
}, SYMBOLS[props.symbol].paths.map(d => (/*#__PURE__*/React__default.createElement("path", {
|
|
4263
|
+
d: d,
|
|
4264
|
+
key: d
|
|
4265
|
+
})))), /*#__PURE__*/React__default.createElement("span", {
|
|
4266
|
+
className: styles$i.visuallyHidden
|
|
4267
|
+
}, SYMBOLS[props.symbol].label))) : props.label);
|
|
4268
|
+
}
|
|
4269
|
+
function CommandKey() {
|
|
4270
|
+
return isMac() ? /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4271
|
+
symbol: "command"
|
|
4272
|
+
}) : /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4273
|
+
label: "Ctrl"
|
|
3670
4274
|
});
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
4275
|
+
}
|
|
4276
|
+
function EnterKey() {
|
|
4277
|
+
return isMac() ? /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4278
|
+
symbol: "return"
|
|
4279
|
+
}) : /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4280
|
+
label: "Enter"
|
|
3677
4281
|
});
|
|
3678
4282
|
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
4283
|
+
const {
|
|
4284
|
+
copyLink,
|
|
4285
|
+
openInNewTab,
|
|
4286
|
+
openSearch,
|
|
4287
|
+
select
|
|
4288
|
+
} = KEYBOARD_SHORTCUTS;
|
|
4289
|
+
function KeyboardHints({
|
|
4290
|
+
copied
|
|
4291
|
+
}) {
|
|
4292
|
+
return /*#__PURE__*/React__default.createElement("ul", {
|
|
4293
|
+
"aria-label": "Keyboard shortcuts",
|
|
4294
|
+
className: styles$h.container
|
|
4295
|
+
}, /*#__PURE__*/React__default.createElement("li", {
|
|
4296
|
+
className: styles$h.hint
|
|
4297
|
+
}, /*#__PURE__*/React__default.createElement(Flex, {
|
|
4298
|
+
gap: 0.5
|
|
4299
|
+
}, select.modifierKey, /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4300
|
+
symbol: "up"
|
|
4301
|
+
}), /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4302
|
+
symbol: "down"
|
|
4303
|
+
})), select.label), /*#__PURE__*/React__default.createElement("li", {
|
|
4304
|
+
className: styles$h.hint
|
|
4305
|
+
}, /*#__PURE__*/React__default.createElement(Flex, {
|
|
4306
|
+
gap: 0.5
|
|
4307
|
+
}, /*#__PURE__*/React__default.createElement(CommandKey, null), /*#__PURE__*/React__default.createElement(EnterKey, null)), openInNewTab.label), /*#__PURE__*/React__default.createElement("li", {
|
|
4308
|
+
className: classnames(styles$h.hint, copied && styles$h.copied)
|
|
4309
|
+
}, /*#__PURE__*/React__default.createElement(Flex, {
|
|
4310
|
+
gap: 0.5
|
|
4311
|
+
}, /*#__PURE__*/React__default.createElement(CommandKey, null), /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4312
|
+
label: copyLink.key.toUpperCase()
|
|
4313
|
+
})), /*#__PURE__*/React__default.createElement("span", {
|
|
4314
|
+
role: "status"
|
|
4315
|
+
}, copied ? "Copied!" : copyLink.label)), /*#__PURE__*/React__default.createElement("li", {
|
|
4316
|
+
className: styles$h.hint
|
|
4317
|
+
}, /*#__PURE__*/React__default.createElement(Flex, {
|
|
4318
|
+
gap: 0.5
|
|
4319
|
+
}, /*#__PURE__*/React__default.createElement(CommandKey, null), /*#__PURE__*/React__default.createElement(Keycap, {
|
|
4320
|
+
label: openSearch.key.toUpperCase()
|
|
4321
|
+
})), openSearch.label));
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
var styles$g = {"icon":"icon-XKLvN","squircle":"squircle-JCHr6"};
|
|
4325
|
+
|
|
4326
|
+
const PRODUCTS = {
|
|
4327
|
+
accounts: {
|
|
4328
|
+
gradient: "--t-fill-color-product-accounts-gradient-brand",
|
|
4329
|
+
path: logomarks.accounts
|
|
4330
|
+
},
|
|
4331
|
+
api: {
|
|
4332
|
+
gradient: "--t-fill-color-product-api-gradient-brand",
|
|
4333
|
+
path: logomarks.api
|
|
4334
|
+
},
|
|
4335
|
+
calendar: {
|
|
4336
|
+
gradient: "--t-fill-color-product-calendar-gradient-brand",
|
|
4337
|
+
path: logomarks.calendar
|
|
4338
|
+
},
|
|
4339
|
+
"check-ins": {
|
|
4340
|
+
gradient: "--t-fill-color-product-checkins-gradient-brand",
|
|
4341
|
+
path: logomarks.checkIns
|
|
4342
|
+
},
|
|
4343
|
+
"church-center": {
|
|
4344
|
+
gradient: "--t-fill-color-product-cc-gradient-brand",
|
|
4345
|
+
path: logomarks.churchCenter
|
|
4346
|
+
},
|
|
4347
|
+
giving: {
|
|
4348
|
+
gradient: "--t-fill-color-product-giving-gradient-brand",
|
|
4349
|
+
path: logomarks.giving
|
|
4350
|
+
},
|
|
4351
|
+
groups: {
|
|
4352
|
+
gradient: "--t-fill-color-product-groups-gradient-brand",
|
|
4353
|
+
path: logomarks.groups
|
|
4354
|
+
},
|
|
4355
|
+
home: {
|
|
4356
|
+
gradient: "--t-fill-color-product-home-gradient-brand",
|
|
4357
|
+
path: logomarks.home
|
|
4358
|
+
},
|
|
4359
|
+
"music-stand": {
|
|
4360
|
+
gradient: "--t-fill-color-product-musicstand-gradient-brand",
|
|
4361
|
+
path: logomarks.musicStand
|
|
4362
|
+
},
|
|
4363
|
+
people: {
|
|
4364
|
+
gradient: "--t-fill-color-product-people-gradient-brand",
|
|
4365
|
+
path: logomarks.people
|
|
4366
|
+
},
|
|
4367
|
+
publishing: {
|
|
4368
|
+
gradient: "--t-fill-color-product-publishing-gradient-brand",
|
|
4369
|
+
path: logomarks.publishing
|
|
4370
|
+
},
|
|
4371
|
+
registrations: {
|
|
4372
|
+
gradient: "--t-fill-color-product-registrations-gradient-brand",
|
|
4373
|
+
path: logomarks.registrations
|
|
4374
|
+
},
|
|
4375
|
+
services: {
|
|
4376
|
+
gradient: "--t-fill-color-product-services-gradient-brand",
|
|
4377
|
+
path: logomarks.services
|
|
3696
4378
|
}
|
|
4379
|
+
};
|
|
4380
|
+
const isProductIconSlug = slug => Object.prototype.hasOwnProperty.call(PRODUCTS, slug);
|
|
4381
|
+
const maskFor = path => `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="${path}"/></svg>`)}")`;
|
|
4382
|
+
function ProductIcon({
|
|
4383
|
+
size = 16,
|
|
4384
|
+
slug
|
|
4385
|
+
}) {
|
|
4386
|
+
const {
|
|
4387
|
+
gradient,
|
|
4388
|
+
path
|
|
4389
|
+
} = PRODUCTS[slug];
|
|
4390
|
+
const mask = maskFor(path);
|
|
4391
|
+
return /*#__PURE__*/React__default.createElement("span", {
|
|
4392
|
+
"aria-hidden": true,
|
|
4393
|
+
className: styles$g.icon,
|
|
4394
|
+
style: {
|
|
4395
|
+
height: size,
|
|
4396
|
+
width: size
|
|
4397
|
+
}
|
|
4398
|
+
}, /*#__PURE__*/React__default.createElement("span", {
|
|
4399
|
+
className: styles$g.squircle,
|
|
4400
|
+
style: {
|
|
4401
|
+
WebkitMaskImage: mask,
|
|
4402
|
+
background: `var(${gradient})`,
|
|
4403
|
+
maskImage: mask
|
|
4404
|
+
}
|
|
4405
|
+
}));
|
|
3697
4406
|
}
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
4407
|
+
|
|
4408
|
+
var styles$f = {"picker":"picker-iuzzj","option":"option--7LXg"};
|
|
4409
|
+
|
|
4410
|
+
// Phones have no room for a tab per product, so they pick from a dropdown.
|
|
4411
|
+
// Complex mode renders each option's logomark, in the list and the trigger.
|
|
4412
|
+
function ProductSelect({
|
|
4413
|
+
children,
|
|
4414
|
+
onSelect,
|
|
4415
|
+
products,
|
|
4416
|
+
selected
|
|
4417
|
+
}) {
|
|
4418
|
+
const options = products.map(({
|
|
4419
|
+
appName,
|
|
3702
4420
|
slug
|
|
3703
|
-
}) =>
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
4421
|
+
}) => ({
|
|
4422
|
+
label: (/*#__PURE__*/React__default.createElement("span", {
|
|
4423
|
+
className: styles$f.option
|
|
4424
|
+
}, /*#__PURE__*/React__default.createElement(ProductIcon, {
|
|
4425
|
+
slug: slug
|
|
4426
|
+
}), appName)),
|
|
4427
|
+
textValue: appName,
|
|
4428
|
+
value: slug
|
|
4429
|
+
}));
|
|
4430
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
4431
|
+
className: styles$f.picker
|
|
4432
|
+
}, /*#__PURE__*/React__default.createElement(Select, {
|
|
4433
|
+
"aria-label": "Product",
|
|
4434
|
+
complex: true,
|
|
4435
|
+
onChange: event => onSelect(event.target.value),
|
|
4436
|
+
options: options,
|
|
4437
|
+
value: selected.slug
|
|
4438
|
+
})), /*#__PURE__*/React__default.createElement("div", {
|
|
4439
|
+
className: styles$i.panel
|
|
4440
|
+
}, children));
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
var styles$e = {"tablist":"tablist-7ASvc","tab":"tab-dz8ki"};
|
|
4444
|
+
|
|
4445
|
+
function ProductTabs({
|
|
4446
|
+
children,
|
|
4447
|
+
onKeyDown,
|
|
4448
|
+
onSelect,
|
|
4449
|
+
products,
|
|
4450
|
+
selected
|
|
4451
|
+
}) {
|
|
4452
|
+
const id = useInstanceId("product-tabs");
|
|
4453
|
+
const tabId = slug => `${id}-tab-${slug}`;
|
|
4454
|
+
const tabRefs = React__default.useRef(new Map());
|
|
4455
|
+
const handleKeyDown = event => {
|
|
4456
|
+
var _a;
|
|
4457
|
+
const offset = horizontalStep(event.key);
|
|
4458
|
+
if (offset) {
|
|
4459
|
+
event.preventDefault();
|
|
4460
|
+
const index = products.indexOf(selected);
|
|
4461
|
+
const next = products[(index + offset + products.length) % products.length];
|
|
4462
|
+
onSelect(next.slug);
|
|
4463
|
+
(_a = tabRefs.current.get(next.slug)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4464
|
+
} else {
|
|
4465
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
4466
|
+
}
|
|
4467
|
+
};
|
|
4468
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
4469
|
+
"aria-label": "Product",
|
|
4470
|
+
className: styles$e.tablist,
|
|
4471
|
+
onKeyDown: handleKeyDown,
|
|
4472
|
+
role: "tablist"
|
|
4473
|
+
}, products.map(product => (/*#__PURE__*/React__default.createElement("button", {
|
|
4474
|
+
"aria-controls": `${id}-panel`,
|
|
4475
|
+
"aria-selected": product === selected,
|
|
4476
|
+
className: styles$e.tab,
|
|
4477
|
+
id: tabId(product.slug),
|
|
4478
|
+
key: product.slug,
|
|
4479
|
+
onClick: () => onSelect(product.slug),
|
|
4480
|
+
ref: element => {
|
|
4481
|
+
if (element) {
|
|
4482
|
+
tabRefs.current.set(product.slug, element);
|
|
4483
|
+
} else {
|
|
4484
|
+
tabRefs.current.delete(product.slug);
|
|
3716
4485
|
}
|
|
4486
|
+
},
|
|
4487
|
+
role: "tab",
|
|
4488
|
+
tabIndex: product === selected ? 0 : -1,
|
|
4489
|
+
type: "button"
|
|
4490
|
+
}, /*#__PURE__*/React__default.createElement(ProductIcon, {
|
|
4491
|
+
slug: product.slug
|
|
4492
|
+
}), product === selected ? product.appName : (/*#__PURE__*/React__default.createElement("span", {
|
|
4493
|
+
className: styles$i.visuallyHidden
|
|
4494
|
+
}, product.appName)))))), /*#__PURE__*/React__default.createElement("div", {
|
|
4495
|
+
"aria-labelledby": tabId(selected.slug),
|
|
4496
|
+
className: styles$i.panel,
|
|
4497
|
+
id: `${id}-panel`,
|
|
4498
|
+
role: "tabpanel"
|
|
4499
|
+
}, children));
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
function EndOfResultsObserver({
|
|
4503
|
+
onVisible
|
|
4504
|
+
}) {
|
|
4505
|
+
const ref = React__default.useRef(null);
|
|
4506
|
+
React__default.useEffect(() => {
|
|
4507
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
4508
|
+
if (entry.isIntersecting) onVisible();
|
|
3717
4509
|
});
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
products: settled.products,
|
|
3725
|
-
status: "done"
|
|
3726
|
-
};
|
|
3727
|
-
} else {
|
|
3728
|
-
return {
|
|
3729
|
-
status: "loading"
|
|
3730
|
-
};
|
|
3731
|
-
}
|
|
4510
|
+
if (ref.current) observer.observe(ref.current);
|
|
4511
|
+
return () => observer.disconnect();
|
|
4512
|
+
}, [onVisible]);
|
|
4513
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
4514
|
+
ref: ref
|
|
4515
|
+
});
|
|
3732
4516
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
4517
|
+
|
|
4518
|
+
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
4519
|
+
|
|
4520
|
+
var styles$d = {"result":"result-Thq2C","title":"title-KYUEn","subtitle":"subtitle-R2haq","image":"image-KVDor","avatar":"avatar-6Gh62","photo":"photo-FZR3Q","badge":"badge-trUKt"};
|
|
4521
|
+
|
|
4522
|
+
function SearchResult({
|
|
4523
|
+
category,
|
|
4524
|
+
highlighted,
|
|
4525
|
+
id,
|
|
4526
|
+
imageUrl,
|
|
4527
|
+
onHighlight,
|
|
4528
|
+
product,
|
|
4529
|
+
subtitle,
|
|
4530
|
+
title,
|
|
4531
|
+
url
|
|
4532
|
+
}) {
|
|
4533
|
+
const ref = React__default.useRef(null);
|
|
4534
|
+
// A hovered result is already under the pointer, and scrolling it clear of
|
|
4535
|
+
// the panel's scroll padding would slide the list out from under the mouse.
|
|
4536
|
+
const highlightedByPointer = React__default.useRef(false);
|
|
4537
|
+
React__default.useEffect(() => {
|
|
4538
|
+
var _a;
|
|
4539
|
+
if (!highlighted) return;
|
|
4540
|
+
if (highlightedByPointer.current) {
|
|
4541
|
+
highlightedByPointer.current = false;
|
|
4542
|
+
} else {
|
|
4543
|
+
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
4544
|
+
block: "nearest"
|
|
4545
|
+
});
|
|
4546
|
+
}
|
|
4547
|
+
}, [highlighted]);
|
|
4548
|
+
const highlightFromPointer = () => {
|
|
4549
|
+
if (highlighted) return;
|
|
4550
|
+
highlightedByPointer.current = true;
|
|
4551
|
+
onHighlight();
|
|
4552
|
+
};
|
|
4553
|
+
// Focus stays in the search input, which points at the highlighted result
|
|
4554
|
+
// with aria-activedescendant, so results are kept out of the tab order.
|
|
4555
|
+
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
4556
|
+
// pointer, and that shouldn't steal the highlight.
|
|
4557
|
+
return /*#__PURE__*/React__default.createElement("a", {
|
|
4558
|
+
"aria-selected": highlighted,
|
|
4559
|
+
className: styles$d.result,
|
|
4560
|
+
href: url,
|
|
4561
|
+
id: id,
|
|
4562
|
+
onMouseMove: highlightFromPointer,
|
|
4563
|
+
ref: ref,
|
|
4564
|
+
role: "option",
|
|
4565
|
+
tabIndex: -1
|
|
4566
|
+
}, /*#__PURE__*/React__default.createElement(ResultImage, {
|
|
4567
|
+
category: category,
|
|
4568
|
+
imageUrl: imageUrl,
|
|
4569
|
+
product: product
|
|
4570
|
+
}), /*#__PURE__*/React__default.createElement("span", {
|
|
4571
|
+
className: styles$d.title
|
|
4572
|
+
}, title), /*#__PURE__*/React__default.createElement("span", {
|
|
4573
|
+
className: styles$d.subtitle
|
|
4574
|
+
}, subtitle));
|
|
4575
|
+
}
|
|
4576
|
+
// A person's photo or the result's own image, badged with its product, or
|
|
4577
|
+
// just the product's logomark when there's no image.
|
|
4578
|
+
function ResultImage({
|
|
4579
|
+
category,
|
|
4580
|
+
imageUrl,
|
|
4581
|
+
product
|
|
4582
|
+
}) {
|
|
4583
|
+
const icon = size => isProductIconSlug(product) ? (/*#__PURE__*/React__default.createElement(ProductIcon, {
|
|
4584
|
+
size: size,
|
|
4585
|
+
slug: product
|
|
4586
|
+
})) : null;
|
|
4587
|
+
return /*#__PURE__*/React__default.createElement("span", {
|
|
4588
|
+
"aria-hidden": true,
|
|
4589
|
+
className: styles$d.image
|
|
4590
|
+
}, imageUrl ? (/*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("img", {
|
|
4591
|
+
alt: "",
|
|
4592
|
+
className: category === "profile" ? styles$d.avatar : styles$d.photo,
|
|
4593
|
+
loading: "lazy",
|
|
4594
|
+
src: imageUrl
|
|
4595
|
+
}), /*#__PURE__*/React__default.createElement("span", {
|
|
4596
|
+
className: styles$d.badge
|
|
4597
|
+
}, icon(16)))) : icon("100%"));
|
|
3735
4598
|
}
|
|
3736
4599
|
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
4600
|
+
var styles$c = {"message":"message-1-PP-","heading":"heading-LioUF"};
|
|
4601
|
+
|
|
4602
|
+
// Sections with a heading render as labelled groups; ones without render
|
|
4603
|
+
// their items straight into the listbox. Indexes run across every section.
|
|
4604
|
+
function ResultListbox({
|
|
4605
|
+
busy,
|
|
4606
|
+
highlightedIndex,
|
|
4607
|
+
id,
|
|
4608
|
+
label,
|
|
4609
|
+
labelledBy,
|
|
4610
|
+
onHighlight,
|
|
4611
|
+
sections
|
|
3744
4612
|
}) {
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
4613
|
+
let index = 0;
|
|
4614
|
+
const renderItem = item => {
|
|
4615
|
+
const itemIndex = index++;
|
|
4616
|
+
return /*#__PURE__*/React__default.createElement(SearchResult, {
|
|
4617
|
+
category: item.category,
|
|
4618
|
+
highlighted: itemIndex === highlightedIndex,
|
|
4619
|
+
id: resultId(id, itemIndex),
|
|
4620
|
+
imageUrl: item.imageUrl,
|
|
4621
|
+
key: item.key,
|
|
4622
|
+
onHighlight: () => onHighlight(itemIndex),
|
|
4623
|
+
product: item.product,
|
|
4624
|
+
subtitle: item.subtitle,
|
|
4625
|
+
title: item.title,
|
|
4626
|
+
url: item.url
|
|
3749
4627
|
});
|
|
4628
|
+
};
|
|
4629
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
4630
|
+
"aria-busy": busy,
|
|
4631
|
+
"aria-label": label,
|
|
4632
|
+
"aria-labelledby": labelledBy,
|
|
4633
|
+
id: id,
|
|
4634
|
+
role: "listbox"
|
|
4635
|
+
}, sections.map((section, sectionIndex) => {
|
|
4636
|
+
if (section.heading === undefined) {
|
|
4637
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, {
|
|
4638
|
+
key: section.key
|
|
4639
|
+
}, section.items.map(renderItem));
|
|
4640
|
+
}
|
|
4641
|
+
const headingId = `${id}-group-${sectionIndex}`;
|
|
4642
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
4643
|
+
"aria-labelledby": headingId,
|
|
4644
|
+
key: section.key,
|
|
4645
|
+
role: "group"
|
|
4646
|
+
}, /*#__PURE__*/React__default.createElement("div", {
|
|
4647
|
+
className: styles$c.heading,
|
|
4648
|
+
id: headingId
|
|
4649
|
+
}, section.heading), section.items.map(renderItem));
|
|
4650
|
+
}));
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
function RecentlyVisited({
|
|
4654
|
+
count,
|
|
4655
|
+
hasMore,
|
|
4656
|
+
items,
|
|
4657
|
+
loading,
|
|
4658
|
+
showMore,
|
|
4659
|
+
...navigation
|
|
4660
|
+
}) {
|
|
4661
|
+
const headingId = `${navigation.id}-heading`;
|
|
4662
|
+
if (items.length === 0 && !hasMore && !loading) {
|
|
4663
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4664
|
+
className: styles$c.message
|
|
4665
|
+
}, "Pages you visit will show up here.");
|
|
3750
4666
|
} else {
|
|
3751
|
-
return null
|
|
4667
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
4668
|
+
className: styles$c.heading,
|
|
4669
|
+
id: headingId
|
|
4670
|
+
}, "Recently visited"), /*#__PURE__*/React__default.createElement(ResultListbox, {
|
|
4671
|
+
labelledBy: headingId,
|
|
4672
|
+
sections: [{
|
|
4673
|
+
items,
|
|
4674
|
+
key: "recents"
|
|
4675
|
+
}],
|
|
4676
|
+
...navigation
|
|
4677
|
+
}), hasMore && /*#__PURE__*/React__default.createElement(EndOfResultsObserver, {
|
|
4678
|
+
key: count,
|
|
4679
|
+
onVisible: showMore
|
|
4680
|
+
}));
|
|
3752
4681
|
}
|
|
3753
4682
|
}
|
|
3754
|
-
|
|
3755
|
-
|
|
4683
|
+
|
|
4684
|
+
var styles$b = {"header":"header-sXEyG","bordered":"bordered-mWkCy","input":"input-YQkTC","clearButton":"clearButton-9Yh9w"};
|
|
4685
|
+
|
|
4686
|
+
// A combobox: focus stays here while aria-activedescendant points screen
|
|
4687
|
+
// readers at the highlighted result in the listbox it controls.
|
|
4688
|
+
function SearchInput({
|
|
4689
|
+
bordered = false,
|
|
4690
|
+
expanded,
|
|
4691
|
+
highlightedIndex,
|
|
4692
|
+
inputRef,
|
|
4693
|
+
listboxId,
|
|
4694
|
+
onChange,
|
|
4695
|
+
onFocus,
|
|
4696
|
+
onKeyDown,
|
|
4697
|
+
value
|
|
3756
4698
|
}) {
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
}
|
|
3771
|
-
};
|
|
3772
|
-
window.addEventListener("keydown", handleKeyDown, {
|
|
3773
|
-
capture: true
|
|
3774
|
-
});
|
|
3775
|
-
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
3776
|
-
capture: true
|
|
3777
|
-
});
|
|
3778
|
-
}, []);
|
|
3779
|
-
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("button", {
|
|
3780
|
-
"aria-keyshortcuts": `${isMac() ? "Meta+K" : "Control+K"} /`,
|
|
4699
|
+
return /*#__PURE__*/React__default.createElement("label", {
|
|
4700
|
+
className: classnames(styles$b.header, {
|
|
4701
|
+
[styles$b.bordered]: bordered
|
|
4702
|
+
})
|
|
4703
|
+
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
4704
|
+
name: "general.search",
|
|
4705
|
+
size: 20,
|
|
4706
|
+
theme: "--t-icon-color-dim"
|
|
4707
|
+
}), /*#__PURE__*/React__default.createElement("input", {
|
|
4708
|
+
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
4709
|
+
"aria-autocomplete": "list",
|
|
4710
|
+
"aria-controls": expanded ? listboxId : undefined,
|
|
4711
|
+
"aria-expanded": expanded,
|
|
3781
4712
|
"aria-label": "Search",
|
|
3782
|
-
|
|
4713
|
+
autoFocus: true,
|
|
4714
|
+
className: styles$b.input,
|
|
4715
|
+
onChange: event => onChange(event.target.value),
|
|
4716
|
+
onFocus: onFocus,
|
|
4717
|
+
onKeyDown: onKeyDown,
|
|
4718
|
+
placeholder: "Search",
|
|
4719
|
+
ref: inputRef,
|
|
4720
|
+
role: "combobox",
|
|
4721
|
+
type: "search",
|
|
4722
|
+
value: value
|
|
4723
|
+
}), value !== "" && (/*#__PURE__*/React__default.createElement("button", {
|
|
4724
|
+
"aria-label": "Clear search",
|
|
4725
|
+
className: styles$b.clearButton,
|
|
4726
|
+
onClick: () => {
|
|
4727
|
+
var _a;
|
|
4728
|
+
onChange("");
|
|
4729
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4730
|
+
},
|
|
4731
|
+
tabIndex: -1,
|
|
3783
4732
|
type: "button"
|
|
3784
|
-
}, /*#__PURE__*/React__default.createElement(
|
|
3785
|
-
|
|
3786
|
-
}))
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
4733
|
+
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
4734
|
+
name: "general.smallX"
|
|
4735
|
+
}))));
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
function SearchResults({
|
|
4739
|
+
label,
|
|
4740
|
+
sections,
|
|
4741
|
+
status,
|
|
4742
|
+
term,
|
|
4743
|
+
...navigation
|
|
4744
|
+
}) {
|
|
4745
|
+
switch (status) {
|
|
4746
|
+
case "idle":
|
|
4747
|
+
return null;
|
|
4748
|
+
case "loading":
|
|
4749
|
+
if (sections.length > 0) {
|
|
4750
|
+
return /*#__PURE__*/React__default.createElement(ResultListbox, {
|
|
4751
|
+
busy: true,
|
|
4752
|
+
label: label,
|
|
4753
|
+
sections: sections,
|
|
4754
|
+
...navigation
|
|
4755
|
+
});
|
|
4756
|
+
}
|
|
4757
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4758
|
+
className: styles$c.message,
|
|
4759
|
+
role: "status"
|
|
4760
|
+
}, "Searching\u2026");
|
|
4761
|
+
case "error":
|
|
4762
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4763
|
+
className: styles$c.message,
|
|
4764
|
+
role: "alert"
|
|
4765
|
+
}, "Search isn\u2019t working right now. Try again.");
|
|
4766
|
+
case "done":
|
|
4767
|
+
if (sections.length === 0) {
|
|
4768
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4769
|
+
className: styles$c.message,
|
|
4770
|
+
role: "status"
|
|
4771
|
+
}, "No results for \u201C", term.trim(), "\u201D");
|
|
4772
|
+
}
|
|
4773
|
+
return /*#__PURE__*/React__default.createElement(ResultListbox, {
|
|
4774
|
+
busy: false,
|
|
4775
|
+
label: label,
|
|
4776
|
+
sections: sections,
|
|
4777
|
+
...navigation
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
3794
4780
|
}
|
|
4781
|
+
|
|
4782
|
+
// Keys the recents list for navigation, so the highlight
|
|
4783
|
+
// stays put as more recents load and as their lookups answer.
|
|
4784
|
+
const RECENTS = "recents";
|
|
3795
4785
|
function SearchPanel({
|
|
4786
|
+
mobile = false,
|
|
4787
|
+
env,
|
|
3796
4788
|
products
|
|
3797
4789
|
}) {
|
|
3798
4790
|
var _a, _b;
|
|
@@ -3805,11 +4797,17 @@ function SearchPanel({
|
|
|
3805
4797
|
const hasTerm = term.trim() !== "";
|
|
3806
4798
|
const search = useProductSearch(product.slug, term);
|
|
3807
4799
|
const groups = "groups" in search ? search.groups : undefined;
|
|
3808
|
-
const
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
4800
|
+
const sections = React__default.useMemo(() => groupSections(groups), [groups]);
|
|
4801
|
+
const {
|
|
4802
|
+
visits,
|
|
4803
|
+
...recents
|
|
4804
|
+
} = useRecentVisits(env);
|
|
4805
|
+
const recentItems = visits.map(visitItem);
|
|
4806
|
+
const items = hasTerm ? sections.flatMap(section => section.items) : recentItems;
|
|
4807
|
+
// Touch-only devices open results by tapping them, with no highlight to
|
|
4808
|
+
// arrow through. A narrow window with a keyboard and mouse keeps both.
|
|
4809
|
+
const touchOnly = useMediaQuery("(hover: none) and (pointer: coarse)");
|
|
4810
|
+
const navigation = useResultNavigation(items, hasTerm ? sections : RECENTS, !touchOnly);
|
|
3813
4811
|
const {
|
|
3814
4812
|
highlightedIndex
|
|
3815
4813
|
} = navigation;
|
|
@@ -3832,296 +4830,273 @@ function SearchPanel({
|
|
|
3832
4830
|
React__default.useEffect(() => {
|
|
3833
4831
|
if (hasTerm) saveLastProduct(product.slug);
|
|
3834
4832
|
}, [hasTerm, product.slug]);
|
|
3835
|
-
|
|
3836
|
-
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
3837
|
-
"aria-autocomplete": "list",
|
|
3838
|
-
"aria-controls": results.length > 0 ? listboxId : undefined,
|
|
3839
|
-
"aria-expanded": results.length > 0,
|
|
3840
|
-
"aria-label": "Search",
|
|
3841
|
-
autoFocus: true,
|
|
3842
|
-
onChange: event => setTerm(event.target.value),
|
|
3843
|
-
onFocus: () => setAnnounceHighlight(false),
|
|
3844
|
-
onKeyDown: navigation.handleKeyDown,
|
|
3845
|
-
placeholder: "Search",
|
|
3846
|
-
ref: inputRef,
|
|
3847
|
-
role: "combobox",
|
|
3848
|
-
type: "search",
|
|
3849
|
-
value: term
|
|
3850
|
-
}), /*#__PURE__*/React__default.createElement(ProductTabs, {
|
|
3851
|
-
onKeyDown: handleTabsKeyDown,
|
|
3852
|
-
onSelect: setSelectedSlug,
|
|
3853
|
-
products: products,
|
|
3854
|
-
selected: product
|
|
3855
|
-
}, /*#__PURE__*/React__default.createElement(SearchResults, {
|
|
4833
|
+
const results = hasTerm ? (/*#__PURE__*/React__default.createElement(SearchResults, {
|
|
3856
4834
|
highlightedIndex: highlightedIndex,
|
|
3857
4835
|
id: listboxId,
|
|
3858
4836
|
label: `${product.appName} results`,
|
|
3859
4837
|
onHighlight: navigation.highlightIndex,
|
|
3860
|
-
|
|
4838
|
+
sections: sections,
|
|
4839
|
+
status: search.status,
|
|
3861
4840
|
term: term
|
|
3862
|
-
}))
|
|
4841
|
+
})) : (/*#__PURE__*/React__default.createElement(RecentlyVisited, {
|
|
4842
|
+
highlightedIndex: highlightedIndex,
|
|
4843
|
+
id: listboxId,
|
|
4844
|
+
items: recentItems,
|
|
4845
|
+
onHighlight: navigation.highlightIndex,
|
|
4846
|
+
...recents
|
|
4847
|
+
}));
|
|
4848
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(SearchInput, {
|
|
4849
|
+
bordered: mobile,
|
|
4850
|
+
expanded: items.length > 0,
|
|
4851
|
+
highlightedIndex: highlightedIndex,
|
|
4852
|
+
inputRef: inputRef,
|
|
4853
|
+
listboxId: listboxId,
|
|
4854
|
+
onChange: setTerm,
|
|
4855
|
+
onFocus: () => setAnnounceHighlight(false),
|
|
4856
|
+
onKeyDown: navigation.handleKeyDown,
|
|
4857
|
+
value: term
|
|
4858
|
+
}), mobile ? (/*#__PURE__*/React__default.createElement(ProductSelect, {
|
|
4859
|
+
onSelect: setSelectedSlug,
|
|
4860
|
+
products: products,
|
|
4861
|
+
selected: product
|
|
4862
|
+
}, results)) : (/*#__PURE__*/React__default.createElement(ProductTabs, {
|
|
4863
|
+
onKeyDown: handleTabsKeyDown,
|
|
4864
|
+
onSelect: setSelectedSlug,
|
|
4865
|
+
products: products,
|
|
4866
|
+
selected: product
|
|
4867
|
+
}, results)), /*#__PURE__*/React__default.createElement("div", {
|
|
3863
4868
|
"aria-live": "polite",
|
|
3864
|
-
className: styles$
|
|
3865
|
-
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.
|
|
4869
|
+
className: styles$i.visuallyHidden
|
|
4870
|
+
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.title : null), mobile ? null : /*#__PURE__*/React__default.createElement(KeyboardHints, {
|
|
3866
4871
|
copied: navigation.copied
|
|
3867
4872
|
}));
|
|
3868
4873
|
}
|
|
3869
|
-
function KeyboardHints({
|
|
3870
|
-
copied
|
|
3871
|
-
}) {
|
|
3872
|
-
const command = commandKeyLabel();
|
|
3873
|
-
return /*#__PURE__*/React__default.createElement("p", null, "\u2191\u2193 Select \u00B7 \u21B5 Open \u00B7 ", command, "\u21B5 Open in new tab \u00B7 ", command, "C", " ", /*#__PURE__*/React__default.createElement("span", {
|
|
3874
|
-
role: "status"
|
|
3875
|
-
}, copied ? "Copied!" : "Copy link"), " \u00B7 ", command, "K Open search");
|
|
3876
|
-
}
|
|
3877
|
-
function initialProduct(products) {
|
|
3878
|
-
var _a, _b, _c;
|
|
3879
|
-
const lastProduct = readLastProduct();
|
|
3880
|
-
const bySlug = slug => products.find(product => product.slug === slug);
|
|
3881
|
-
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
|
-
}
|
|
3883
4874
|
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
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;
|
|
4875
|
+
// Products open modals from many libraries, not all of which mark themselves
|
|
4876
|
+
// with roles, so a modal is recognized by what it does: cover the page.
|
|
4877
|
+
function isModalOpen() {
|
|
4878
|
+
const {
|
|
4879
|
+
clientHeight,
|
|
4880
|
+
clientWidth
|
|
4881
|
+
} = document.documentElement;
|
|
4882
|
+
const element = document.elementFromPoint(clientWidth / 2, clientHeight / 2);
|
|
4883
|
+
return element !== null && (isInNativeDialog(element) || hasBackdrop(element, clientWidth, clientHeight));
|
|
3901
4884
|
}
|
|
3902
|
-
//
|
|
3903
|
-
//
|
|
3904
|
-
|
|
3905
|
-
|
|
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;
|
|
4885
|
+
// A native modal's backdrop is drawn by the browser rather than being an
|
|
4886
|
+
// element; hit-testing it lands on the dialog itself.
|
|
4887
|
+
function isInNativeDialog(element) {
|
|
4888
|
+
return element.closest("dialog[open]") !== null;
|
|
3908
4889
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
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
|
-
});
|
|
4890
|
+
function hasBackdrop(element, width, height) {
|
|
4891
|
+
let current = element;
|
|
4892
|
+
while (current && !isBackdrop(current, width, height)) {
|
|
4893
|
+
current = current.parentElement;
|
|
3921
4894
|
}
|
|
4895
|
+
return current !== null;
|
|
3922
4896
|
}
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
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());
|
|
4897
|
+
// Some backdrops start below the topbar rather than at the top of the screen,
|
|
4898
|
+
// so the layer only has to span the width and reach from the center to the
|
|
4899
|
+
// bottom. Its own box has to reach the center: a bar along the bottom can
|
|
4900
|
+
// hold a panel that pops up over the center, like Services' media player.
|
|
4901
|
+
// react-aria leaves a closed modal's backdrop up while it fades out, search's
|
|
4902
|
+
// own included, so one that's on its way out doesn't count.
|
|
4903
|
+
function isBackdrop(element, width, height) {
|
|
4904
|
+
const rect = element.getBoundingClientRect();
|
|
4905
|
+
return !element.matches("[data-exiting]") && getComputedStyle(element).position === "fixed" && rect.width >= width - SCROLLBAR_SLACK && rect.top <= height / 2 && rect.bottom >= height - ROUNDING_SLACK;
|
|
3944
4906
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
4907
|
+
// react-aria keeps a classic scrollbar's gutter reserved while its modals lock
|
|
4908
|
+
// scrolling, so their backdrops stop short of the right edge by its width.
|
|
4909
|
+
const SCROLLBAR_SLACK = 24;
|
|
4910
|
+
// Rects are fractional at non-100% zoom, but clientWidth and clientHeight are
|
|
4911
|
+
// rounded to whole pixels.
|
|
4912
|
+
const ROUNDING_SLACK = 1;
|
|
4913
|
+
|
|
4914
|
+
function useSearchShortcut(enabled, open, setOpen) {
|
|
4915
|
+
React__default.useEffect(() => {
|
|
4916
|
+
if (!enabled) return undefined;
|
|
4917
|
+
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
4918
|
+
// window and stopping propagation keeps them from ever seeing it. Under a
|
|
4919
|
+
// product's modal, search stays shut rather than stacking a second modal,
|
|
4920
|
+
// and the keystroke goes on to the product.
|
|
4921
|
+
const handleKeyDown = event => {
|
|
4922
|
+
if (open && isSearchShortcut(event)) {
|
|
4923
|
+
event.preventDefault();
|
|
4924
|
+
event.stopPropagation();
|
|
4925
|
+
setOpen(false);
|
|
4926
|
+
} else if (!open && (isSearchShortcut(event) || isLegacySearchShortcut(event)) && !isModalOpen()) {
|
|
4927
|
+
event.preventDefault();
|
|
4928
|
+
event.stopPropagation();
|
|
4929
|
+
setOpen(true);
|
|
4930
|
+
}
|
|
4931
|
+
};
|
|
4932
|
+
window.addEventListener("keydown", handleKeyDown, {
|
|
4933
|
+
capture: true
|
|
4934
|
+
});
|
|
4935
|
+
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
4936
|
+
capture: true
|
|
4937
|
+
});
|
|
4938
|
+
}, [enabled, open, setOpen]);
|
|
3948
4939
|
}
|
|
3949
4940
|
|
|
3950
|
-
const
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
}
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
}
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
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("");
|
|
4941
|
+
const PERMISSION_CHECKS = {
|
|
4942
|
+
"check-ins": () => fetchMe("check-ins", "permission").then(({
|
|
4943
|
+
permission
|
|
4944
|
+
}) => permission !== null),
|
|
4945
|
+
giving: personId => {
|
|
4946
|
+
if (personId === undefined) {
|
|
4947
|
+
return Promise.reject(new Error("No person to check Giving access for"));
|
|
4948
|
+
} else {
|
|
4949
|
+
return fetchAttributes(`giving/v2/person_permissions/${personId}`, {
|
|
4950
|
+
"fields[PersonPermissions]": "can_search"
|
|
4951
|
+
}).then(({
|
|
4952
|
+
can_search
|
|
4953
|
+
}) => can_search);
|
|
4954
|
+
}
|
|
4955
|
+
},
|
|
4956
|
+
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
4957
|
+
people_permissions,
|
|
4958
|
+
site_administrator
|
|
4959
|
+
}) => site_administrator || people_permissions !== null),
|
|
4960
|
+
registrations: () => fetchMe("registrations", "can_search").then(({
|
|
4961
|
+
can_search
|
|
4962
|
+
}) => can_search),
|
|
4963
|
+
services: () => fetchMe("services", "can_search").then(({
|
|
4964
|
+
can_search
|
|
4965
|
+
}) => can_search)
|
|
4966
|
+
};
|
|
4967
|
+
function fetchMe(product, fields) {
|
|
4968
|
+
return fetchAttributes(`${product}/v2/me`, {
|
|
4969
|
+
"fields[Person]": fields
|
|
4970
|
+
});
|
|
3989
4971
|
}
|
|
3990
|
-
function
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4972
|
+
function fetchAttributes(path, params) {
|
|
4973
|
+
const url = `${window.location.origin}/~api/${path}?${new URLSearchParams(params)}`;
|
|
4974
|
+
return apiRequest(defaultFetch, url, {
|
|
4975
|
+
headers: apiVersionHeaders(url)
|
|
4976
|
+
}).then(({
|
|
4977
|
+
json,
|
|
4978
|
+
ok
|
|
4979
|
+
}) => {
|
|
4980
|
+
if (!ok || json === null) throw new Error("Permission check failed");
|
|
4981
|
+
return json.data.attributes;
|
|
4982
|
+
});
|
|
4000
4983
|
}
|
|
4001
|
-
//
|
|
4002
|
-
//
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
}
|
|
4010
|
-
|
|
4011
|
-
return parsed.entries.filter(isEntry);
|
|
4984
|
+
// Permissions rarely change mid-session, so a successful check is kept until
|
|
4985
|
+
// the page reloads. A failed one is dropped so a remount tries again.
|
|
4986
|
+
const permissionCache = new Map();
|
|
4987
|
+
function canSearch(slug, personId) {
|
|
4988
|
+
const check = PERMISSION_CHECKS[slug];
|
|
4989
|
+
const cached = permissionCache.get(slug);
|
|
4990
|
+
if (!check) {
|
|
4991
|
+
return Promise.resolve(true);
|
|
4992
|
+
} else if (cached) {
|
|
4993
|
+
return cached;
|
|
4012
4994
|
} else {
|
|
4013
|
-
|
|
4995
|
+
const promise = check(personId).catch(() => {
|
|
4996
|
+
permissionCache.delete(slug);
|
|
4997
|
+
return false;
|
|
4998
|
+
});
|
|
4999
|
+
permissionCache.set(slug, promise);
|
|
5000
|
+
return promise;
|
|
4014
5001
|
}
|
|
4015
5002
|
}
|
|
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
5003
|
|
|
4028
|
-
|
|
4029
|
-
//
|
|
4030
|
-
//
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
const
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
if (resource && isPcrl(resource)) {
|
|
4053
|
-
return {
|
|
4054
|
-
pcrl: resource,
|
|
4055
|
-
type: "resource"
|
|
5004
|
+
function useSearchableProducts(apps) {
|
|
5005
|
+
// Keyed by name because providers hand over a fresh `apps` array on renders
|
|
5006
|
+
// where nothing changed.
|
|
5007
|
+
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
5008
|
+
slug
|
|
5009
|
+
}) => slug).join(",");
|
|
5010
|
+
const personId = useCurrentPersonId();
|
|
5011
|
+
const [settled, setSettled] = React__default.useState();
|
|
5012
|
+
React__default.useEffect(() => {
|
|
5013
|
+
let current = true;
|
|
5014
|
+
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
5015
|
+
Promise.all(accessible.map(({
|
|
5016
|
+
slug
|
|
5017
|
+
}) => canSearch(slug, personId))).then(allowed => {
|
|
5018
|
+
if (current) {
|
|
5019
|
+
setSettled({
|
|
5020
|
+
accessibleKey,
|
|
5021
|
+
personId,
|
|
5022
|
+
products: accessible.filter((_, index) => allowed[index])
|
|
5023
|
+
});
|
|
5024
|
+
}
|
|
5025
|
+
});
|
|
5026
|
+
return () => {
|
|
5027
|
+
current = false;
|
|
4056
5028
|
};
|
|
4057
|
-
}
|
|
5029
|
+
}, [accessibleKey, personId]);
|
|
5030
|
+
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey && settled.personId === personId) {
|
|
4058
5031
|
return {
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
product,
|
|
4062
|
-
title,
|
|
4063
|
-
type: "page"
|
|
5032
|
+
products: settled.products,
|
|
5033
|
+
status: "done"
|
|
4064
5034
|
};
|
|
4065
5035
|
} else {
|
|
4066
|
-
return
|
|
5036
|
+
return {
|
|
5037
|
+
status: "loading"
|
|
5038
|
+
};
|
|
4067
5039
|
}
|
|
4068
5040
|
}
|
|
4069
|
-
|
|
4070
|
-
|
|
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]);
|
|
5041
|
+
function inSwitcherOrder(apps) {
|
|
5042
|
+
return groupApps(apps).restProducts.flatMap(app => SEARCHABLE_PRODUCTS.filter(product => product.appName === app.name));
|
|
4083
5043
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
5044
|
+
|
|
5045
|
+
/**
|
|
5046
|
+
* Writes the height of the visible part of the page to the parent of `ref`'s
|
|
5047
|
+
* element as `--visual-viewport-height`. It shrinks when an on-screen keyboard
|
|
5048
|
+
* opens; `100dvh` doesn't, since mobile Safari draws the keyboard over it.
|
|
5049
|
+
*/
|
|
5050
|
+
function useVisualViewportHeight(ref, enabled) {
|
|
5051
|
+
React__default.useEffect(() => {
|
|
5052
|
+
var _a;
|
|
5053
|
+
const viewport = window.visualViewport;
|
|
5054
|
+
const parent = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
5055
|
+
if (!enabled || !viewport || !parent) return undefined;
|
|
5056
|
+
const update = () => parent.style.setProperty("--visual-viewport-height", `${viewport.height}px`);
|
|
5057
|
+
update();
|
|
5058
|
+
viewport.addEventListener("resize", update);
|
|
5059
|
+
return () => {
|
|
5060
|
+
viewport.removeEventListener("resize", update);
|
|
5061
|
+
parent.style.removeProperty("--visual-viewport-height");
|
|
5062
|
+
};
|
|
5063
|
+
}, [enabled, ref]);
|
|
4091
5064
|
}
|
|
4092
5065
|
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
env,
|
|
4098
|
-
personId
|
|
5066
|
+
function ProductSearch({
|
|
5067
|
+
apps,
|
|
5068
|
+
mobile = false,
|
|
5069
|
+
env
|
|
4099
5070
|
}) {
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
//
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
}
|
|
5071
|
+
const searchable = useSearchableProducts(apps);
|
|
5072
|
+
const [open, setOpen] = React__default.useState(false);
|
|
5073
|
+
const modalRef = React__default.useRef(null);
|
|
5074
|
+
// Sizes Tapestry's modal frame, the dialog's parent, to fit above the keyboard.
|
|
5075
|
+
useVisualViewportHeight(modalRef, mobile && open);
|
|
5076
|
+
const available = searchable.status === "done" && searchable.products.length > 0;
|
|
5077
|
+
useSearchShortcut(available, open, setOpen);
|
|
5078
|
+
if (!available) return null;
|
|
5079
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(SearchButton, {
|
|
5080
|
+
mobile: mobile,
|
|
5081
|
+
onClick: () => setOpen(true)
|
|
5082
|
+
}), /*#__PURE__*/React__default.createElement(Modal, {
|
|
5083
|
+
"aria-label": "Search",
|
|
5084
|
+
className: classnames(styles$i.modal, {
|
|
5085
|
+
[styles$i.mobile]: mobile
|
|
5086
|
+
}),
|
|
5087
|
+
dismissible: true,
|
|
5088
|
+
onClose: () => setOpen(false),
|
|
5089
|
+
open: open,
|
|
5090
|
+
ref: modalRef,
|
|
5091
|
+
size: "lg"
|
|
5092
|
+
}, mobile && /*#__PURE__*/React__default.createElement(ModalHeader, {
|
|
5093
|
+
className: styles$i.header,
|
|
5094
|
+
title: "Search"
|
|
5095
|
+
}), /*#__PURE__*/React__default.createElement(SearchPanel, {
|
|
5096
|
+
mobile: mobile,
|
|
5097
|
+
env: env,
|
|
5098
|
+
products: searchable.products
|
|
5099
|
+
})));
|
|
4125
5100
|
}
|
|
4126
5101
|
|
|
4127
5102
|
function useRecordRecentlyVisited({
|
|
@@ -4166,9 +5141,9 @@ function StyledRoot({
|
|
|
4166
5141
|
`), children);
|
|
4167
5142
|
}
|
|
4168
5143
|
|
|
4169
|
-
var styles$
|
|
5144
|
+
var styles$a = {"route":"route--8BWe","is-active":"is-active-FsU9Y","apps-routes-separator":"apps-routes-separator-Nlm-N"};
|
|
4170
5145
|
|
|
4171
|
-
var styles$
|
|
5146
|
+
var styles$9 = {"popup":"popup-mDNKn","trigger-wrap":"trigger-wrap-Jd-KR","trigger":"trigger-WgRT5","is-visible":"is-visible-GZC0-","trigger-orgname":"trigger-orgname-TDHCc"};
|
|
4172
5147
|
|
|
4173
5148
|
const UserMenu = ({
|
|
4174
5149
|
appName,
|
|
@@ -4187,7 +5162,7 @@ const UserMenu = ({
|
|
|
4187
5162
|
component: Outsider,
|
|
4188
5163
|
onOutsideClick: () => setUserMenuVisible(false),
|
|
4189
5164
|
cleanup: !userMenuVisible,
|
|
4190
|
-
className: styles$
|
|
5165
|
+
className: styles$9.popup,
|
|
4191
5166
|
isVisible: userMenuVisible
|
|
4192
5167
|
}, /*#__PURE__*/React__default.createElement("div", null, hasConnectedPeople ? (/*#__PURE__*/React__default.createElement("div", {
|
|
4193
5168
|
style: {
|
|
@@ -4295,7 +5270,7 @@ const UserMenu = ({
|
|
|
4295
5270
|
}, /*#__PURE__*/React__default.createElement(Text, {
|
|
4296
5271
|
color: "var(--t-text-color-secondary)",
|
|
4297
5272
|
lineHeight: "24px"
|
|
4298
|
-
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon, {
|
|
5273
|
+
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
4299
5274
|
name: "general.intoBox",
|
|
4300
5275
|
fill: "var(--t-icon-color-secondary)"
|
|
4301
5276
|
})))))));
|
|
@@ -4331,11 +5306,11 @@ const UserMenuTrigger = ({
|
|
|
4331
5306
|
userMenuVisible
|
|
4332
5307
|
}) => {
|
|
4333
5308
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
4334
|
-
className: styles$
|
|
5309
|
+
className: styles$9["trigger-wrap"],
|
|
4335
5310
|
"data-pendo": "topbar-org-switcher"
|
|
4336
5311
|
}, /*#__PURE__*/React__default.createElement(Hoverable, {
|
|
4337
|
-
className: classnames(styles$
|
|
4338
|
-
[styles$
|
|
5312
|
+
className: classnames(styles$9["trigger"], {
|
|
5313
|
+
[styles$9["is-visible"]]: userMenuVisible
|
|
4339
5314
|
}),
|
|
4340
5315
|
component: "button",
|
|
4341
5316
|
type: "button",
|
|
@@ -4347,7 +5322,7 @@ const UserMenuTrigger = ({
|
|
|
4347
5322
|
requestConnectedPeopleFetch();
|
|
4348
5323
|
}
|
|
4349
5324
|
}, showOrgName && (/*#__PURE__*/React__default.createElement("span", {
|
|
4350
|
-
className: styles$
|
|
5325
|
+
className: styles$9["trigger-orgname"]
|
|
4351
5326
|
}, orgName)), /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)));
|
|
4352
5327
|
};
|
|
4353
5328
|
|
|
@@ -4407,13 +5382,14 @@ const NotSmallTopbar = ({
|
|
|
4407
5382
|
colors: props.colors,
|
|
4408
5383
|
requestAppsFetch: props.requestAppsFetch
|
|
4409
5384
|
})), /*#__PURE__*/React__default.createElement("div", {
|
|
4410
|
-
className: styles$
|
|
5385
|
+
className: styles$a["apps-routes-separator"]
|
|
4411
5386
|
}), routesVisible && props.routes, /*#__PURE__*/React__default.createElement("div", {
|
|
4412
5387
|
style: {
|
|
4413
5388
|
margin: "auto"
|
|
4414
5389
|
}
|
|
4415
5390
|
}), !notificationsEnabled && notifications(), productSearchEnabled ? (/*#__PURE__*/React__default.createElement(ProductSearch, {
|
|
4416
|
-
apps: props.apps
|
|
5391
|
+
apps: props.apps,
|
|
5392
|
+
env: props.env
|
|
4417
5393
|
})) : search({
|
|
4418
5394
|
hideRoutes: () => setRoutesVisible(false),
|
|
4419
5395
|
showRoutes: () => setRoutesVisible(true)
|
|
@@ -4447,12 +5423,12 @@ const NotSmallRoute = ({
|
|
|
4447
5423
|
...nativeProps
|
|
4448
5424
|
}) => {
|
|
4449
5425
|
return /*#__PURE__*/React__default.createElement("a", {
|
|
4450
|
-
className: classnames(styles$
|
|
5426
|
+
className: classnames(styles$a["route"], className, active && styles$a["is-active"]),
|
|
4451
5427
|
...nativeProps
|
|
4452
5428
|
});
|
|
4453
5429
|
};
|
|
4454
5430
|
|
|
4455
|
-
var styles$
|
|
5431
|
+
var styles$8 = {"avatar":"avatar-tW-wn"};
|
|
4456
5432
|
|
|
4457
5433
|
function Avatar({
|
|
4458
5434
|
env,
|
|
@@ -4462,7 +5438,7 @@ function Avatar({
|
|
|
4462
5438
|
}) {
|
|
4463
5439
|
const url = incomingUrl || `${pcoUrl$1(env)("people")}/static/no_photo_thumbnail_gray.svg`;
|
|
4464
5440
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
4465
|
-
className: styles$
|
|
5441
|
+
className: styles$8.avatar,
|
|
4466
5442
|
style: {
|
|
4467
5443
|
backgroundImage: `url(${url})`,
|
|
4468
5444
|
...style
|
|
@@ -32221,7 +33197,7 @@ function requireReactDom () {
|
|
|
32221
33197
|
|
|
32222
33198
|
var reactDomExports = requireReactDom();
|
|
32223
33199
|
|
|
32224
|
-
var styles$
|
|
33200
|
+
var styles$7 = {"trigger":"trigger-Th-Up","layer":"layer-ck6BD","layer--visible":"layer--visible-4E2p1","panel":"panel-vobWE","header":"header-xndfZ","heading":"heading-uXyQf","close":"close-pK0Lk","list":"list-zWvrn","link":"link-Kf3qj","current":"current-g3Ey-","accounts-item":"accounts-item-eQqDD","accounts-link":"accounts-link-SeIbD"};
|
|
32225
33201
|
|
|
32226
33202
|
const AppsSwitcher = ({
|
|
32227
33203
|
appName,
|
|
@@ -32249,8 +33225,8 @@ const AppsSwitcher = ({
|
|
|
32249
33225
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32250
33226
|
"aria-current": name === appName ? "page" : undefined,
|
|
32251
33227
|
"aria-label": name,
|
|
32252
|
-
className: classnames(styles$
|
|
32253
|
-
[styles$
|
|
33228
|
+
className: classnames(styles$7.link, {
|
|
33229
|
+
[styles$7.current]: name === appName
|
|
32254
33230
|
}),
|
|
32255
33231
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32256
33232
|
"data-turbolinks": false,
|
|
@@ -32262,48 +33238,48 @@ const AppsSwitcher = ({
|
|
|
32262
33238
|
"aria-expanded": visible,
|
|
32263
33239
|
"aria-haspopup": "true",
|
|
32264
33240
|
"aria-label": `App switcher: ${appName}`,
|
|
32265
|
-
className: styles$
|
|
33241
|
+
className: styles$7.trigger,
|
|
32266
33242
|
onClick: onTriggerClick,
|
|
32267
33243
|
type: "button"
|
|
32268
33244
|
}, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
32269
33245
|
appName: appName.replace(/[\s-]/, "")
|
|
32270
33246
|
})), /*#__PURE__*/reactDomExports.createPortal(/*#__PURE__*/React__default.createElement("div", {
|
|
32271
33247
|
ref: containerRef,
|
|
32272
|
-
className: classnames(styles$
|
|
32273
|
-
[styles$
|
|
33248
|
+
className: classnames(styles$7.layer, {
|
|
33249
|
+
[styles$7["layer--visible"]]: visible
|
|
32274
33250
|
}),
|
|
32275
33251
|
onClick: toggle,
|
|
32276
33252
|
onKeyDown: handleKeyDown
|
|
32277
33253
|
}, /*#__PURE__*/React__default.createElement("nav", {
|
|
32278
33254
|
"aria-label": "Switch app",
|
|
32279
33255
|
"aria-modal": visible,
|
|
32280
|
-
className: styles$
|
|
33256
|
+
className: styles$7.panel,
|
|
32281
33257
|
"data-pendo": "app-switcher",
|
|
32282
33258
|
onClick: event => event.stopPropagation(),
|
|
32283
33259
|
role: "dialog"
|
|
32284
33260
|
}, /*#__PURE__*/React__default.createElement("div", {
|
|
32285
|
-
className: styles$
|
|
33261
|
+
className: styles$7.header
|
|
32286
33262
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
32287
|
-
className: styles$
|
|
33263
|
+
className: styles$7.heading
|
|
32288
33264
|
}, "Switch to"), /*#__PURE__*/React__default.createElement("button", {
|
|
32289
33265
|
"aria-label": "Close",
|
|
32290
|
-
className: styles$
|
|
33266
|
+
className: styles$7.close,
|
|
32291
33267
|
onClick: toggle,
|
|
32292
33268
|
type: "button"
|
|
32293
33269
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
32294
33270
|
"aria-hidden": "true",
|
|
32295
33271
|
fill: "var(--t-icon-color-dim)"
|
|
32296
33272
|
}))), /*#__PURE__*/React__default.createElement("ul", {
|
|
32297
|
-
className: styles$
|
|
33273
|
+
className: styles$7.list,
|
|
32298
33274
|
role: "list"
|
|
32299
33275
|
}, homeProduct && productLink(homeProduct.name), restProducts.map(({
|
|
32300
33276
|
name
|
|
32301
33277
|
}) => productLink(name)), accountsProduct && (/*#__PURE__*/React__default.createElement("li", {
|
|
32302
|
-
className: styles$
|
|
33278
|
+
className: styles$7["accounts-item"]
|
|
32303
33279
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32304
33280
|
"aria-current": appName === "Accounts" ? "page" : undefined,
|
|
32305
33281
|
"aria-label": "Account settings",
|
|
32306
|
-
className: classnames(styles$
|
|
33282
|
+
className: classnames(styles$7.link, styles$7["accounts-link"]),
|
|
32307
33283
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
32308
33284
|
"data-turbolinks": false,
|
|
32309
33285
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -32326,14 +33302,14 @@ ClientStorage.defaultProps = {
|
|
|
32326
33302
|
method: "localStorage"
|
|
32327
33303
|
};
|
|
32328
33304
|
|
|
32329
|
-
var styles$
|
|
33305
|
+
var styles$6 = {"appmenu-button":"appmenu-button-b0UvT","appmenu-layer":"appmenu-layer-0IQpN","appmenu-container":"appmenu-container-gackd","appmenu-scroll":"appmenu-scroll-mPSx0","appmenu-list":"appmenu-list-h7clX","appmenu-list-a":"appmenu-list-a-rGIQN","appmenu-list-a--top":"appmenu-list-a--top-RT-2F","appmenu-list-a--bottom":"appmenu-list-a--bottom-YDeBC","appmenu-close-button":"appmenu-close-button-vv9bV","current-route":"current-route-wpJgz","is-active":"is-active-wy8Bg","routes":"routes-Nmt1S","route":"route-kkknQ","actions":"actions-INQ7-","profile-button":"profile-button-a2WO2"};
|
|
32330
33306
|
|
|
32331
33307
|
function AccountSettingsLink({
|
|
32332
33308
|
env
|
|
32333
33309
|
}) {
|
|
32334
33310
|
const accountsName = "accounts";
|
|
32335
33311
|
return /*#__PURE__*/React__default.createElement("a", {
|
|
32336
|
-
className: classnames(styles$
|
|
33312
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--bottom"]),
|
|
32337
33313
|
"data-pendo": `app-switcher--change-app--${accountsName}`,
|
|
32338
33314
|
"data-turbolinks": false,
|
|
32339
33315
|
href: pcoUrl$1(env)(accountsName) + getAppsSwitchProductPath(accountsName)
|
|
@@ -32356,7 +33332,7 @@ const CloseMenuButton = ({
|
|
|
32356
33332
|
}) => (/*#__PURE__*/React__default.createElement("button", {
|
|
32357
33333
|
"aria-label": "Close menu",
|
|
32358
33334
|
type: "button",
|
|
32359
|
-
className: styles$
|
|
33335
|
+
className: styles$6["appmenu-close-button"],
|
|
32360
33336
|
style: style
|
|
32361
33337
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
32362
33338
|
fill: color,
|
|
@@ -32417,7 +33393,7 @@ const LegacyAppsSwitcher = ({
|
|
|
32417
33393
|
"aria-haspopup": "true",
|
|
32418
33394
|
"aria-label": `App switcher: ${appName}`,
|
|
32419
33395
|
type: "button",
|
|
32420
|
-
className: styles$
|
|
33396
|
+
className: styles$6["appmenu-button"],
|
|
32421
33397
|
onClick: onTriggerClick
|
|
32422
33398
|
}, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
32423
33399
|
appName: appName.replace(/[\s-]/, "")
|
|
@@ -32425,7 +33401,7 @@ const LegacyAppsSwitcher = ({
|
|
|
32425
33401
|
item: "Topbar:Small:Menus:Products:visibility",
|
|
32426
33402
|
render: () => (/*#__PURE__*/React__default.createElement("div", {
|
|
32427
33403
|
ref: appMenuRef,
|
|
32428
|
-
className: styles$
|
|
33404
|
+
className: styles$6["appmenu-layer"],
|
|
32429
33405
|
style: {
|
|
32430
33406
|
...(visible && {
|
|
32431
33407
|
display: "block"
|
|
@@ -32442,37 +33418,37 @@ const LegacyAppsSwitcher = ({
|
|
|
32442
33418
|
e.stopPropagation();
|
|
32443
33419
|
}
|
|
32444
33420
|
},
|
|
32445
|
-
className: styles$
|
|
33421
|
+
className: styles$6["appmenu-container"]
|
|
32446
33422
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32447
33423
|
"aria-current": appName === (homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) ? "page" : undefined,
|
|
32448
33424
|
"aria-label": homeProduct ? homeProduct.name : appName,
|
|
32449
33425
|
"data-pendo": `app-switcher--change-app--${homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name}`,
|
|
32450
33426
|
"data-turbolinks": false,
|
|
32451
|
-
className: classnames(styles$
|
|
33427
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--top"]),
|
|
32452
33428
|
href: homeProduct ? pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) : undefined
|
|
32453
33429
|
}, /*#__PURE__*/React__default.createElement(UserMenuAppLockup, {
|
|
32454
33430
|
appName: homeProduct ? homeProduct.name : appName
|
|
32455
33431
|
})), /*#__PURE__*/React__default.createElement("div", {
|
|
32456
|
-
className: styles$
|
|
33432
|
+
className: styles$6["appmenu-scroll"]
|
|
32457
33433
|
}, /*#__PURE__*/React__default.createElement("ul", {
|
|
32458
33434
|
"data-pendo": "app-switcher",
|
|
32459
|
-
className: styles$
|
|
33435
|
+
className: styles$6["appmenu-list"]
|
|
32460
33436
|
}, appsMenuFormatter(restProducts).map(({
|
|
32461
33437
|
name
|
|
32462
33438
|
}) => (/*#__PURE__*/React__default.createElement("li", {
|
|
32463
33439
|
key: name,
|
|
32464
|
-
className: styles$
|
|
33440
|
+
className: styles$6["appmenu-list-li"]
|
|
32465
33441
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32466
33442
|
"aria-current": name === appName ? "page" : undefined,
|
|
32467
33443
|
"aria-label": name,
|
|
32468
33444
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32469
33445
|
"data-turbolinks": false,
|
|
32470
|
-
className: styles$
|
|
33446
|
+
className: styles$6["appmenu-list-a"],
|
|
32471
33447
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(name)
|
|
32472
33448
|
}, /*#__PURE__*/React__default.createElement(UserMenuAppLockup, {
|
|
32473
33449
|
appName: name
|
|
32474
33450
|
}))))), accountsProduct && (/*#__PURE__*/React__default.createElement("li", {
|
|
32475
|
-
className: styles$
|
|
33451
|
+
className: styles$6["appmenu-list-li"]
|
|
32476
33452
|
}, /*#__PURE__*/React__default.createElement(AccountSettingsLink, {
|
|
32477
33453
|
env: env
|
|
32478
33454
|
}))))))))
|
|
@@ -32536,19 +33512,25 @@ const SmallTopbar = ({
|
|
|
32536
33512
|
toggle: toggleProductSwitcher,
|
|
32537
33513
|
visible: productSwitcherVisible
|
|
32538
33514
|
})), routesMenuVisible && (/*#__PURE__*/React__default.createElement("div", {
|
|
32539
|
-
className: styles$
|
|
33515
|
+
className: styles$6["routes"]
|
|
32540
33516
|
}, props.routes)), /*#__PURE__*/React__default.createElement("button", {
|
|
32541
33517
|
type: "button",
|
|
32542
|
-
className: classnames(styles$
|
|
32543
|
-
[styles$
|
|
33518
|
+
className: classnames(styles$6["current-route"], {
|
|
33519
|
+
[styles$6["is-active"]]: routesMenuVisible
|
|
32544
33520
|
}),
|
|
32545
33521
|
onClick: props.routes.length > 0 ? () => {
|
|
32546
33522
|
setProductSwitcherVisible(false);
|
|
32547
33523
|
setRoutesMenuVisible(visible => !visible);
|
|
32548
33524
|
} : null
|
|
32549
|
-
}, /*#__PURE__*/React__default.createElement(CurrentRouteComponent, null, capitalize(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React__default.createElement("
|
|
33525
|
+
}, /*#__PURE__*/React__default.createElement(CurrentRouteComponent, null, capitalize$1(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React__default.createElement("div", {
|
|
33526
|
+
className: styles$6["actions"]
|
|
33527
|
+
}, productSearchEnabled && (/*#__PURE__*/React__default.createElement(ProductSearch, {
|
|
33528
|
+
apps: props.apps,
|
|
33529
|
+
mobile: true,
|
|
33530
|
+
env: props.env
|
|
33531
|
+
})), /*#__PURE__*/React__default.createElement("button", {
|
|
32550
33532
|
type: "button",
|
|
32551
|
-
className: styles$
|
|
33533
|
+
className: styles$6["profile-button"],
|
|
32552
33534
|
onClick: () => {
|
|
32553
33535
|
var _a, _b;
|
|
32554
33536
|
props.requestConnectedPeopleFetch();
|
|
@@ -32557,14 +33539,14 @@ const SmallTopbar = ({
|
|
|
32557
33539
|
}, /*#__PURE__*/React__default.createElement(Avatar, {
|
|
32558
33540
|
env: props.env,
|
|
32559
33541
|
url: props.userAvatarPath
|
|
32560
|
-
})));
|
|
33542
|
+
}))));
|
|
32561
33543
|
};
|
|
32562
33544
|
const SmallRoute = ({
|
|
32563
33545
|
active,
|
|
32564
33546
|
className,
|
|
32565
33547
|
...props
|
|
32566
33548
|
}) => (/*#__PURE__*/React__default.createElement("a", {
|
|
32567
|
-
className: classnames(styles$
|
|
33549
|
+
className: classnames(styles$6["route"], className, active && styles$6["is-active"]),
|
|
32568
33550
|
...props
|
|
32569
33551
|
}));
|
|
32570
33552
|
|
|
@@ -32703,7 +33685,7 @@ const topbarTheme = {
|
|
|
32703
33685
|
id: "topbar"
|
|
32704
33686
|
};
|
|
32705
33687
|
|
|
32706
|
-
var styles$
|
|
33688
|
+
var styles$5 = {"debug-pco-resource":"debug-pco-resource-sLteM","debug-pco-resource-header":"debug-pco-resource-header-5fEbe","debug-pco-resource-badge":"debug-pco-resource-badge-J9v1F","debug-pco-resource-title":"debug-pco-resource-title-volWV","debug-pco-resource-field":"debug-pco-resource-field-7Gitp","debug-pco-resource-field-label":"debug-pco-resource-field-label--f1xX","debug-pco-resource-field-value":"debug-pco-resource-field-value-YoX-C","debug-pco-resource-note":"debug-pco-resource-note-N5xyf","debug-pco-resource-json":"debug-pco-resource-json-Rx-JI"};
|
|
32707
33689
|
|
|
32708
33690
|
function isPresent(value) {
|
|
32709
33691
|
return value !== undefined && value.trim() !== "";
|
|
@@ -32732,15 +33714,31 @@ function Field({
|
|
|
32732
33714
|
value
|
|
32733
33715
|
}) {
|
|
32734
33716
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32735
|
-
className: styles$
|
|
33717
|
+
className: styles$5["debug-pco-resource-field"]
|
|
32736
33718
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
32737
|
-
className: styles$
|
|
33719
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
32738
33720
|
}, label), /*#__PURE__*/React__default.createElement("span", {
|
|
32739
|
-
className: styles$
|
|
33721
|
+
className: styles$5["debug-pco-resource-field-value"]
|
|
32740
33722
|
}, value));
|
|
32741
33723
|
}
|
|
33724
|
+
// Only staff ghosting or impersonating a person get pco:staff-access-level,
|
|
33725
|
+
// which keeps the panel away from regular users on staging.
|
|
32742
33726
|
function DebugPcoResource({
|
|
32743
33727
|
env
|
|
33728
|
+
}) {
|
|
33729
|
+
const {
|
|
33730
|
+
"staff-access-level": staffAccessLevel
|
|
33731
|
+
} = usePcoMeta();
|
|
33732
|
+
if (isPresent(staffAccessLevel)) {
|
|
33733
|
+
return /*#__PURE__*/React__default.createElement(DebugPanel, {
|
|
33734
|
+
env: env
|
|
33735
|
+
});
|
|
33736
|
+
} else {
|
|
33737
|
+
return null;
|
|
33738
|
+
}
|
|
33739
|
+
}
|
|
33740
|
+
function DebugPanel({
|
|
33741
|
+
env
|
|
32744
33742
|
}) {
|
|
32745
33743
|
const {
|
|
32746
33744
|
recently_visitable: recentlyVisitable,
|
|
@@ -32749,12 +33747,17 @@ function DebugPcoResource({
|
|
|
32749
33747
|
const [lookup, setLookup] = useState({
|
|
32750
33748
|
status: "idle"
|
|
32751
33749
|
});
|
|
32752
|
-
const
|
|
33750
|
+
const visit = useCurrentVisit();
|
|
33751
|
+
const personId = useCurrentPersonId();
|
|
33752
|
+
const recentlyVisited = useRecentlyVisited({
|
|
33753
|
+
env,
|
|
33754
|
+
personId
|
|
33755
|
+
});
|
|
33756
|
+
const pcrl = (visit === null || visit === void 0 ? void 0 : visit.type) === "resource" ? visit.pcrl : null;
|
|
32753
33757
|
const hasRecentlyVisitable = isPresent(recentlyVisitable);
|
|
33758
|
+
const hasInvalidResource = isPresent(resource) && !isPcrl(resource);
|
|
32754
33759
|
useEffect(() => {
|
|
32755
|
-
|
|
32756
|
-
// tags are present.
|
|
32757
|
-
const url = hasResource ? pcrlLookupUrl(resource, env) : null;
|
|
33760
|
+
const url = pcrl ? pcrlLookupUrl(pcrl, env) : null;
|
|
32758
33761
|
if (!url) {
|
|
32759
33762
|
setLookup({
|
|
32760
33763
|
status: "idle"
|
|
@@ -32786,37 +33789,45 @@ function DebugPcoResource({
|
|
|
32786
33789
|
return () => {
|
|
32787
33790
|
cancelled = true;
|
|
32788
33791
|
};
|
|
32789
|
-
}, [
|
|
33792
|
+
}, [pcrl, env]);
|
|
32790
33793
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32791
|
-
className: classnames("staff-only", styles$
|
|
33794
|
+
className: classnames("staff-only", styles$5["debug-pco-resource"])
|
|
32792
33795
|
}, /*#__PURE__*/React__default.createElement("div", {
|
|
32793
|
-
className: styles$
|
|
33796
|
+
className: styles$5["debug-pco-resource-header"]
|
|
32794
33797
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
32795
|
-
className: styles$
|
|
33798
|
+
className: styles$5["debug-pco-resource-badge"]
|
|
32796
33799
|
}, "Staff"), /*#__PURE__*/React__default.createElement("span", {
|
|
32797
|
-
className: styles$
|
|
33800
|
+
className: styles$5["debug-pco-resource-title"]
|
|
32798
33801
|
}, "Recently Visited debug")), /*#__PURE__*/React__default.createElement(Field, {
|
|
32799
33802
|
label: "pco:resource",
|
|
32800
33803
|
value: describeTag("resource", resource)
|
|
32801
33804
|
}), /*#__PURE__*/React__default.createElement(Field, {
|
|
32802
33805
|
label: "pco:recently_visitable",
|
|
32803
33806
|
value: describeTag("recently_visitable", recentlyVisitable)
|
|
32804
|
-
}),
|
|
32805
|
-
className: styles$
|
|
32806
|
-
}, "
|
|
32807
|
-
className: styles$
|
|
32808
|
-
}, "
|
|
32809
|
-
className: styles$
|
|
32810
|
-
}, "
|
|
32811
|
-
className: styles$
|
|
32812
|
-
},
|
|
32813
|
-
className: styles$
|
|
32814
|
-
},
|
|
32815
|
-
className: styles$
|
|
32816
|
-
},
|
|
32817
|
-
|
|
32818
|
-
|
|
32819
|
-
|
|
33807
|
+
}), hasInvalidResource && (/*#__PURE__*/React__default.createElement("p", {
|
|
33808
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33809
|
+
}, "pco:resource isn't a PCRL (product/v2/type/id), so Recently Visited ignores it.")), visit === null && (/*#__PURE__*/React__default.createElement("p", {
|
|
33810
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33811
|
+
}, "This page is not eligible for Recently Visited")), pcrl && hasRecentlyVisitable && (/*#__PURE__*/React__default.createElement("p", {
|
|
33812
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33813
|
+
}, "Both tags are present \u2014 pco:resource takes precedence for the Recently Visited entry.")), pcrl && lookup.status === "loading" && (/*#__PURE__*/React__default.createElement("p", {
|
|
33814
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33815
|
+
}, "Looking up resource\u2026")), pcrl && lookup.status === "error" && (/*#__PURE__*/React__default.createElement("p", {
|
|
33816
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33817
|
+
}, lookup.message)), pcrl && lookup.status === "loaded" && (/*#__PURE__*/React__default.createElement("pre", {
|
|
33818
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33819
|
+
}, JSON.stringify(lookup.json, null, 2))), (visit === null || visit === void 0 ? void 0 : visit.type) === "page" && (/*#__PURE__*/React__default.createElement("p", {
|
|
33820
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33821
|
+
}, "Recently Visited title: ", visit.title)), /*#__PURE__*/React__default.createElement("span", {
|
|
33822
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
33823
|
+
}, "Recently Visited"), personId ? (/*#__PURE__*/React__default.createElement("pre", {
|
|
33824
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33825
|
+
}, JSON.stringify(recentlyVisited, null, 2))) : (/*#__PURE__*/React__default.createElement("p", {
|
|
33826
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33827
|
+
}, "No current person, so nothing is recorded.")));
|
|
33828
|
+
}
|
|
33829
|
+
|
|
33830
|
+
var styles$4 = {"drawer-wrap":"drawer-wrap-J0rSL","is-visible":"is-visible-KTME8","drawer":"drawer-j7f4l"};
|
|
32820
33831
|
|
|
32821
33832
|
function Drawer({
|
|
32822
33833
|
children,
|
|
@@ -32824,7 +33835,7 @@ function Drawer({
|
|
|
32824
33835
|
}) {
|
|
32825
33836
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32826
33837
|
id: id,
|
|
32827
|
-
className: styles$
|
|
33838
|
+
className: styles$4["drawer"]
|
|
32828
33839
|
}, children);
|
|
32829
33840
|
}
|
|
32830
33841
|
function DrawerWrap({
|
|
@@ -32834,7 +33845,7 @@ function DrawerWrap({
|
|
|
32834
33845
|
...props
|
|
32835
33846
|
}) {
|
|
32836
33847
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32837
|
-
className: classnames(styles$
|
|
33848
|
+
className: classnames(styles$4["drawer-wrap"], className, isVisible && styles$4["is-visible"]),
|
|
32838
33849
|
...props
|
|
32839
33850
|
}, children);
|
|
32840
33851
|
}
|
|
@@ -33018,15 +34029,18 @@ const SwitchView = ({
|
|
|
33018
34029
|
}));
|
|
33019
34030
|
};
|
|
33020
34031
|
|
|
33021
|
-
var styles$
|
|
34032
|
+
var styles$3 = {"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
34033
|
|
|
33023
34034
|
const COLOR_MODES = [{
|
|
34035
|
+
icon: "toolbar.sun",
|
|
33024
34036
|
label: "Light",
|
|
33025
34037
|
value: "light"
|
|
33026
34038
|
}, {
|
|
34039
|
+
icon: "toolbar.moon",
|
|
33027
34040
|
label: "Dark",
|
|
33028
34041
|
value: "dark"
|
|
33029
34042
|
}, {
|
|
34043
|
+
icon: "toolbar.display",
|
|
33030
34044
|
label: "System",
|
|
33031
34045
|
value: "system"
|
|
33032
34046
|
}];
|
|
@@ -33038,14 +34052,15 @@ function ColorModeSwitcher() {
|
|
|
33038
34052
|
const updateStore = useUpdateStore();
|
|
33039
34053
|
const [counter] = useState(() => COLOR_MODE_INSTANCE_COUNTER++);
|
|
33040
34054
|
return /*#__PURE__*/React__default.createElement("fieldset", {
|
|
33041
|
-
className: styles$
|
|
34055
|
+
className: styles$3["color-mode-switcher"]
|
|
33042
34056
|
}, /*#__PURE__*/React__default.createElement("legend", {
|
|
33043
|
-
className: styles$
|
|
34057
|
+
className: styles$3["color-mode-switcher-label"]
|
|
33044
34058
|
}, "Appearance", " "), /*#__PURE__*/React__default.createElement("div", {
|
|
33045
|
-
className: styles$
|
|
34059
|
+
className: styles$3["color-mode-switcher-modes"]
|
|
33046
34060
|
}, COLOR_MODES.map(({
|
|
33047
|
-
|
|
33048
|
-
label
|
|
34061
|
+
icon,
|
|
34062
|
+
label,
|
|
34063
|
+
value
|
|
33049
34064
|
}) => (/*#__PURE__*/React__default.createElement("label", {
|
|
33050
34065
|
key: value
|
|
33051
34066
|
}, /*#__PURE__*/React__default.createElement("input", {
|
|
@@ -33062,8 +34077,9 @@ function ColorModeSwitcher() {
|
|
|
33062
34077
|
document.documentElement.setAttribute(RESOLVED_COLOR_MODE_ATTRIBUTE, resolveColorMode(colorMode));
|
|
33063
34078
|
updateColorMode(colorMode);
|
|
33064
34079
|
}
|
|
33065
|
-
}), /*#__PURE__*/React__default.createElement(
|
|
33066
|
-
|
|
34080
|
+
}), /*#__PURE__*/React__default.createElement(Icon, {
|
|
34081
|
+
name: icon,
|
|
34082
|
+
size: 15
|
|
33067
34083
|
}), label)))));
|
|
33068
34084
|
}
|
|
33069
34085
|
function updateColorMode(mode) {
|
|
@@ -33078,28 +34094,6 @@ function updateColorMode(mode) {
|
|
|
33078
34094
|
method: "POST"
|
|
33079
34095
|
});
|
|
33080
34096
|
}
|
|
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
34097
|
|
|
33104
34098
|
const requestClearConnectedPeopleCache = () => remove();
|
|
33105
34099
|
function UserProfile({
|
|
@@ -33164,8 +34158,8 @@ function UserProfile({
|
|
|
33164
34158
|
width: "100%"
|
|
33165
34159
|
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
33166
34160
|
name: mfaConfigured ? "general.lock" : "general.unlock",
|
|
33167
|
-
size:
|
|
33168
|
-
|
|
34161
|
+
size: 12,
|
|
34162
|
+
theme: mfaConfigured ? "--t-icon-color-status-success" : "--t-icon-color-status-error"
|
|
33169
34163
|
}), /*#__PURE__*/React__default.createElement(Text, {
|
|
33170
34164
|
size: 4,
|
|
33171
34165
|
weight: 400,
|
|
@@ -33215,7 +34209,8 @@ function UserProfile({
|
|
|
33215
34209
|
size: 3
|
|
33216
34210
|
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33217
34211
|
name: "general.intoBox",
|
|
33218
|
-
|
|
34212
|
+
size: 14,
|
|
34213
|
+
theme: "--t-icon-color-secondary"
|
|
33219
34214
|
})))), env !== "production" && /*#__PURE__*/React__default.createElement(DebugPcoResource, {
|
|
33220
34215
|
env: env
|
|
33221
34216
|
}), hasConnectedPeople && (/*#__PURE__*/React__default.createElement(StackView$2, {
|
|
@@ -33277,26 +34272,42 @@ function UserProfile({
|
|
|
33277
34272
|
|
|
33278
34273
|
var ActionType;
|
|
33279
34274
|
(function (ActionType) {
|
|
34275
|
+
ActionType["clearPending"] = "clearPending";
|
|
33280
34276
|
ActionType["close"] = "close";
|
|
33281
34277
|
ActionType["mount"] = "mount";
|
|
33282
34278
|
ActionType["open"] = "open";
|
|
34279
|
+
ActionType["setPending"] = "setPending";
|
|
33283
34280
|
ActionType["unmount"] = "unmount";
|
|
33284
34281
|
})(ActionType || (ActionType = {}));
|
|
33285
34282
|
const initialState = {
|
|
33286
34283
|
currentContent: null,
|
|
33287
|
-
open: false
|
|
34284
|
+
open: false,
|
|
34285
|
+
pending: null
|
|
33288
34286
|
};
|
|
33289
34287
|
const reducer = (state, action) => {
|
|
34288
|
+
var _a, _b;
|
|
33290
34289
|
switch (action.type) {
|
|
33291
34290
|
case ActionType.mount:
|
|
33292
34291
|
return {
|
|
33293
34292
|
...state,
|
|
33294
|
-
currentContent: action.payload.content
|
|
34293
|
+
currentContent: action.payload.content,
|
|
34294
|
+
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
34295
|
};
|
|
33296
34296
|
case ActionType.unmount:
|
|
33297
34297
|
return {
|
|
33298
34298
|
...state,
|
|
33299
|
-
currentContent: null
|
|
34299
|
+
currentContent: null,
|
|
34300
|
+
pending: null
|
|
34301
|
+
};
|
|
34302
|
+
case ActionType.setPending:
|
|
34303
|
+
return {
|
|
34304
|
+
...state,
|
|
34305
|
+
pending: action.payload
|
|
34306
|
+
};
|
|
34307
|
+
case ActionType.clearPending:
|
|
34308
|
+
return {
|
|
34309
|
+
...state,
|
|
34310
|
+
pending: null
|
|
33300
34311
|
};
|
|
33301
34312
|
case ActionType.open:
|
|
33302
34313
|
return {
|
|
@@ -33315,13 +34326,181 @@ const useToolbarReducer = overrides => React.useReducer(reducer, {
|
|
|
33315
34326
|
...overrides
|
|
33316
34327
|
});
|
|
33317
34328
|
|
|
33318
|
-
var styles$
|
|
34329
|
+
var styles$2 = {"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
34330
|
|
|
33320
|
-
|
|
34331
|
+
// PCO Box serves TLS by default, and a bare http:// frame inside an https
|
|
34332
|
+
// product page is blocked as mixed content, so follow the page's scheme.
|
|
34333
|
+
const iframeSrc = () => `${window.location.protocol}//pco.test/iframe`;
|
|
34334
|
+
const STAFF_PINK = "var(--t-fill-color-product-staff)";
|
|
34335
|
+
// The pco.test domain is only ever served by a local PCO Box, so the tool
|
|
34336
|
+
// stays hidden in staging and production — and on Topbar's own dev server,
|
|
34337
|
+
// which runs on localhost and has no pco.test to frame.
|
|
34338
|
+
function isPcoTestHost() {
|
|
34339
|
+
const {
|
|
34340
|
+
hostname
|
|
34341
|
+
} = window.location;
|
|
34342
|
+
return hostname === "pco.test" || hostname.endsWith(".pco.test");
|
|
34343
|
+
}
|
|
34344
|
+
// Flattened from the source artwork's two outline paths — its gradients,
|
|
34345
|
+
// filters and clip paths carry nothing at icon size. Those paths are authored
|
|
34346
|
+
// on a 128px canvas, hence the transform: it fits the pair into the same ~24px
|
|
34347
|
+
// optical box the sibling toolbar icons occupy. Filled explicitly rather than
|
|
34348
|
+
// with `currentColor` so the mark stays staff pink instead of inheriting the
|
|
34349
|
+
// toolbar's text color and its hover/active states.
|
|
34350
|
+
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";
|
|
34351
|
+
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";
|
|
34352
|
+
function ToolsIcon() {
|
|
34353
|
+
return /*#__PURE__*/React__default.createElement("svg", {
|
|
34354
|
+
"aria-hidden": "true",
|
|
34355
|
+
width: "32",
|
|
34356
|
+
height: "32",
|
|
34357
|
+
viewBox: "0 0 32 32",
|
|
34358
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
34359
|
+
}, /*#__PURE__*/React__default.createElement("g", {
|
|
34360
|
+
fill: STAFF_PINK,
|
|
34361
|
+
transform: "translate(2.92 2.66) scale(.204)"
|
|
34362
|
+
}, /*#__PURE__*/React__default.createElement("path", {
|
|
34363
|
+
d: WRENCH
|
|
34364
|
+
}), /*#__PURE__*/React__default.createElement("path", {
|
|
34365
|
+
d: SCREWDRIVER
|
|
34366
|
+
})));
|
|
34367
|
+
}
|
|
34368
|
+
// A cross-origin frame's own history object is unreachable (touching it throws
|
|
34369
|
+
// a SecurityError), but the frame's navigations do land in the top-level joint
|
|
34370
|
+
// session history, so the *parent's* back/forward traverse them while leaving
|
|
34371
|
+
// the host page in place. Counting frame loads tracks how deep we are, which is
|
|
34372
|
+
// what keeps a click on Back from stepping past the frame's first page and
|
|
34373
|
+
// navigating the host app instead. Loads we caused are attributed to the
|
|
34374
|
+
// traversal that requested them rather than counted as new navigations.
|
|
34375
|
+
function useFrameHistory() {
|
|
34376
|
+
const [steps, setSteps] = useState({
|
|
34377
|
+
back: 0,
|
|
34378
|
+
forward: 0
|
|
34379
|
+
});
|
|
34380
|
+
// Traversal has to read the current counts outside of render, so the ref is
|
|
34381
|
+
// the source of truth and state exists to paint the buttons.
|
|
34382
|
+
const stepsRef = useRef(steps);
|
|
34383
|
+
const pendingTraversal = useRef(null);
|
|
34384
|
+
const frameIsLoaded = useRef(false);
|
|
34385
|
+
const moveTo = useCallback(next => {
|
|
34386
|
+
stepsRef.current = next;
|
|
34387
|
+
setSteps(next);
|
|
34388
|
+
}, []);
|
|
34389
|
+
const onFrameLoad = useCallback(() => {
|
|
34390
|
+
if (!frameIsLoaded.current) {
|
|
34391
|
+
// The frame's first page is the floor, not a step.
|
|
34392
|
+
frameIsLoaded.current = true;
|
|
34393
|
+
} else {
|
|
34394
|
+
const traversal = pendingTraversal.current;
|
|
34395
|
+
pendingTraversal.current = null;
|
|
34396
|
+
const {
|
|
34397
|
+
back,
|
|
34398
|
+
forward
|
|
34399
|
+
} = stepsRef.current;
|
|
34400
|
+
if (traversal === "back") {
|
|
34401
|
+
moveTo({
|
|
34402
|
+
back: back - 1,
|
|
34403
|
+
forward: forward + 1
|
|
34404
|
+
});
|
|
34405
|
+
} else if (traversal === "forward") {
|
|
34406
|
+
moveTo({
|
|
34407
|
+
back: back + 1,
|
|
34408
|
+
forward: forward - 1
|
|
34409
|
+
});
|
|
34410
|
+
} else {
|
|
34411
|
+
moveTo({
|
|
34412
|
+
back: back + 1,
|
|
34413
|
+
forward: 0
|
|
34414
|
+
});
|
|
34415
|
+
}
|
|
34416
|
+
}
|
|
34417
|
+
}, [moveTo]);
|
|
34418
|
+
// Guarded here rather than trusting the button's disabled state: traversing
|
|
34419
|
+
// with no frame step left would navigate the host app out from under us.
|
|
34420
|
+
const traverse = useCallback(direction => {
|
|
34421
|
+
if (stepsRef.current[direction] > 0) {
|
|
34422
|
+
pendingTraversal.current = direction;
|
|
34423
|
+
window.history[direction]();
|
|
34424
|
+
}
|
|
34425
|
+
}, []);
|
|
34426
|
+
return {
|
|
34427
|
+
onFrameLoad,
|
|
34428
|
+
steps,
|
|
34429
|
+
traverse
|
|
34430
|
+
};
|
|
34431
|
+
}
|
|
34432
|
+
function NavButton({
|
|
34433
|
+
direction,
|
|
34434
|
+
disabled,
|
|
34435
|
+
onClick
|
|
34436
|
+
}) {
|
|
34437
|
+
return /*#__PURE__*/React__default.createElement(Button$1
|
|
34438
|
+
// Tapestry's `disabled` suppresses the click but emits no ARIA state.
|
|
34439
|
+
, {
|
|
34440
|
+
"aria-disabled": disabled,
|
|
34441
|
+
disabled: disabled,
|
|
34442
|
+
hover: {
|
|
34443
|
+
backgroundColor: "var(--t-fill-color-neutral-070)"
|
|
34444
|
+
},
|
|
34445
|
+
icon: {
|
|
34446
|
+
fill: "var(--t-icon-color-dim)",
|
|
34447
|
+
name: direction === "back" ? "general.leftChevron" : "general.rightChevron",
|
|
34448
|
+
size: "md"
|
|
34449
|
+
},
|
|
34450
|
+
onClick: onClick,
|
|
34451
|
+
opacity: disabled ? 0.35 : 1,
|
|
34452
|
+
theme: false,
|
|
34453
|
+
title: direction === "back" ? "Back" : "Forward"
|
|
34454
|
+
});
|
|
34455
|
+
}
|
|
34456
|
+
function DeveloperToolsComponent({
|
|
34457
|
+
closeDrawer
|
|
34458
|
+
}) {
|
|
34459
|
+
const {
|
|
34460
|
+
onFrameLoad,
|
|
34461
|
+
steps,
|
|
34462
|
+
traverse
|
|
34463
|
+
} = useFrameHistory();
|
|
34464
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
34465
|
+
closeDrawer: closeDrawer
|
|
34466
|
+
}, /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
34467
|
+
alignment: "center",
|
|
34468
|
+
axis: "horizontal",
|
|
34469
|
+
spacing: 0.25
|
|
34470
|
+
}, /*#__PURE__*/React__default.createElement(NavButton, {
|
|
34471
|
+
direction: "back",
|
|
34472
|
+
disabled: steps.back === 0,
|
|
34473
|
+
onClick: () => traverse("back")
|
|
34474
|
+
}), /*#__PURE__*/React__default.createElement(NavButton, {
|
|
34475
|
+
direction: "forward",
|
|
34476
|
+
disabled: steps.forward === 0,
|
|
34477
|
+
onClick: () => traverse("forward")
|
|
34478
|
+
}), /*#__PURE__*/React__default.createElement(DrawerHeading, {
|
|
34479
|
+
paddingLeft: 0.5
|
|
34480
|
+
}, "Developer Tools"))), /*#__PURE__*/React__default.createElement(Box, {
|
|
34481
|
+
as: "iframe",
|
|
34482
|
+
border: "none",
|
|
34483
|
+
flex: "1",
|
|
34484
|
+
minHeight: 0,
|
|
34485
|
+
onLoad: onFrameLoad,
|
|
34486
|
+
src: iframeSrc(),
|
|
34487
|
+
title: "Developer Tools",
|
|
34488
|
+
width: "100%"
|
|
34489
|
+
}));
|
|
34490
|
+
}
|
|
34491
|
+
const DeveloperTools = {
|
|
34492
|
+
component: DeveloperToolsComponent,
|
|
34493
|
+
icons: {
|
|
34494
|
+
component: ToolsIcon
|
|
34495
|
+
},
|
|
34496
|
+
label: "Developer Tools",
|
|
34497
|
+
// No space: `name` becomes an element id and a pendo attribute.
|
|
34498
|
+
name: "DeveloperTools"
|
|
34499
|
+
};
|
|
33321
34500
|
|
|
33322
|
-
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"};
|
|
34501
|
+
var styles$1 = {"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
34502
|
|
|
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";
|
|
34503
|
+
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
34504
|
// Same stops as the AskAIBanner border gradient, so the two stay in sync.
|
|
33326
34505
|
const GRADIENT_STOPS = [{
|
|
33327
34506
|
color: "var(--t-fill-color-tag-teal-040)",
|
|
@@ -33370,7 +34549,7 @@ function SparkleIcon({
|
|
|
33370
34549
|
stopColor: color
|
|
33371
34550
|
}
|
|
33372
34551
|
})))))), /*#__PURE__*/React__default.createElement("path", {
|
|
33373
|
-
d: SPARKLE_PATH,
|
|
34552
|
+
d: SPARKLE_PATH$1,
|
|
33374
34553
|
fill: isGradient ? `url(#${gradientId})` : "currentColor"
|
|
33375
34554
|
}));
|
|
33376
34555
|
}
|
|
@@ -33379,8 +34558,8 @@ function AskAIBanner(props) {
|
|
|
33379
34558
|
var _a, _b;
|
|
33380
34559
|
const isStacked = props.variant !== "inline";
|
|
33381
34560
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
33382
|
-
className: classnames(styles.banner, {
|
|
33383
|
-
[styles.stacked]: isStacked
|
|
34561
|
+
className: classnames(styles$1.banner, {
|
|
34562
|
+
[styles$1.stacked]: isStacked
|
|
33384
34563
|
}),
|
|
33385
34564
|
onClick: () => props.onClick(),
|
|
33386
34565
|
type: "button"
|
|
@@ -33395,15 +34574,15 @@ function StackedContent({
|
|
|
33395
34574
|
title
|
|
33396
34575
|
}) {
|
|
33397
34576
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
|
|
33398
|
-
className: styles.content
|
|
34577
|
+
className: styles$1.content
|
|
33399
34578
|
}, /*#__PURE__*/React__default.createElement(AskAIIcon, null), /*#__PURE__*/React__default.createElement("span", {
|
|
33400
|
-
className: styles.text
|
|
34579
|
+
className: styles$1.text
|
|
33401
34580
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
33402
|
-
className: styles.title
|
|
34581
|
+
className: styles$1.title
|
|
33403
34582
|
}, title), /*#__PURE__*/React__default.createElement("span", {
|
|
33404
|
-
className: styles.subtitle
|
|
33405
|
-
}, "Support answers tailored to you"))), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33406
|
-
className: styles.chevron,
|
|
34583
|
+
className: styles$1.subtitle
|
|
34584
|
+
}, "Support answers tailored to you"))), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
34585
|
+
className: styles$1.chevron,
|
|
33407
34586
|
name: "general.rightChevron",
|
|
33408
34587
|
size: "16px"
|
|
33409
34588
|
}));
|
|
@@ -33413,18 +34592,18 @@ function InlineContent({
|
|
|
33413
34592
|
title
|
|
33414
34593
|
}) {
|
|
33415
34594
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
|
|
33416
|
-
className: styles.inlineContent
|
|
34595
|
+
className: styles$1.inlineContent
|
|
33417
34596
|
}, /*#__PURE__*/React__default.createElement(SparkleIcon, {
|
|
33418
|
-
className: styles.sparkle,
|
|
34597
|
+
className: styles$1.sparkle,
|
|
33419
34598
|
fill: "currentColor"
|
|
33420
34599
|
}), /*#__PURE__*/React__default.createElement("span", {
|
|
33421
|
-
className: styles.inlineTitle
|
|
34600
|
+
className: styles$1.inlineTitle
|
|
33422
34601
|
}, title)), /*#__PURE__*/React__default.createElement("span", {
|
|
33423
|
-
className: styles.action
|
|
34602
|
+
className: styles$1.action
|
|
33424
34603
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
33425
|
-
className: styles.actionLabel
|
|
33426
|
-
}, action), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33427
|
-
className: styles.actionChevron,
|
|
34604
|
+
className: styles$1.actionLabel
|
|
34605
|
+
}, action), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
34606
|
+
className: styles$1.actionChevron,
|
|
33428
34607
|
name: "general.rightChevron",
|
|
33429
34608
|
size: "14px"
|
|
33430
34609
|
})));
|
|
@@ -33441,9 +34620,9 @@ function AskAIIcon() {
|
|
|
33441
34620
|
const borderA = `${uid}-border-a`;
|
|
33442
34621
|
const borderB = `${uid}-border-b`;
|
|
33443
34622
|
return /*#__PURE__*/React__default.createElement("span", {
|
|
33444
|
-
className: styles.icon
|
|
34623
|
+
className: styles$1.icon
|
|
33445
34624
|
}, /*#__PURE__*/React__default.createElement("svg", {
|
|
33446
|
-
className: styles.iconShape,
|
|
34625
|
+
className: styles$1.iconShape,
|
|
33447
34626
|
viewBox: "0 0 32 32",
|
|
33448
34627
|
fill: "none",
|
|
33449
34628
|
"aria-hidden": "true"
|
|
@@ -33614,7 +34793,7 @@ function AskAIIcon() {
|
|
|
33614
34793
|
stopColor: "#D48B91",
|
|
33615
34794
|
stopOpacity: "0"
|
|
33616
34795
|
})))), /*#__PURE__*/React__default.createElement("svg", {
|
|
33617
|
-
className: styles.iconSparkle,
|
|
34796
|
+
className: styles$1.iconSparkle,
|
|
33618
34797
|
viewBox: "0 0 19.4995 17.9999",
|
|
33619
34798
|
"aria-hidden": "true"
|
|
33620
34799
|
}, /*#__PURE__*/React__default.createElement("path", {
|
|
@@ -33623,6 +34802,23 @@ function AskAIIcon() {
|
|
|
33623
34802
|
})));
|
|
33624
34803
|
}
|
|
33625
34804
|
|
|
34805
|
+
var styles = {"button":"button-2NVI1","sparkle":"sparkle-ruJGP","label":"label-4NUGB"};
|
|
34806
|
+
|
|
34807
|
+
function AskAIButton({
|
|
34808
|
+
onClick,
|
|
34809
|
+
text = "Ask AI"
|
|
34810
|
+
}) {
|
|
34811
|
+
return /*#__PURE__*/React__default.createElement("button", {
|
|
34812
|
+
className: styles.button,
|
|
34813
|
+
onClick: () => onClick(),
|
|
34814
|
+
type: "button"
|
|
34815
|
+
}, /*#__PURE__*/React__default.createElement(SparkleIcon, {
|
|
34816
|
+
className: styles.sparkle
|
|
34817
|
+
}), /*#__PURE__*/React__default.createElement("span", {
|
|
34818
|
+
className: styles.label
|
|
34819
|
+
}, text));
|
|
34820
|
+
}
|
|
34821
|
+
|
|
33626
34822
|
// The article heading lives in the deepest populated hierarchy level (lvl0 is
|
|
33627
34823
|
// always "Planning Center").
|
|
33628
34824
|
const hitTitle = hierarchy => (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl6) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl5) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl4) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl3) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl2) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl1) || (hierarchy === null || hierarchy === void 0 ? void 0 : hierarchy.lvl0) || "";
|
|
@@ -33646,7 +34842,7 @@ const HeadingIcon = ({
|
|
|
33646
34842
|
const logoName = appName.toLowerCase().replace(/\s+/g, "-");
|
|
33647
34843
|
// Accounts has no tapestry Logo, so use the cog icon
|
|
33648
34844
|
if (logoName === "accounts") {
|
|
33649
|
-
return /*#__PURE__*/React__default.createElement(Icon, {
|
|
34845
|
+
return /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33650
34846
|
name: "general.cog",
|
|
33651
34847
|
color: "foregroundSecondary"
|
|
33652
34848
|
});
|
|
@@ -33710,7 +34906,7 @@ const CustomSearchBox = ({
|
|
|
33710
34906
|
},
|
|
33711
34907
|
placeholder: "Search our documentation",
|
|
33712
34908
|
value: query,
|
|
33713
|
-
renderRight: /*#__PURE__*/React__default.createElement(Icon, {
|
|
34909
|
+
renderRight: /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33714
34910
|
name: "general.search"
|
|
33715
34911
|
}),
|
|
33716
34912
|
onChange: e => setQuery(e.target.value),
|
|
@@ -33778,16 +34974,24 @@ const DocumentationSearch = ({
|
|
|
33778
34974
|
// not the path, so updating it is a same-document navigation that doesn't
|
|
33779
34975
|
// reload the iframe, but also lets the route survive a forced reload (e.g.
|
|
33780
34976
|
// Turbo re-parenting the topbar).
|
|
33781
|
-
|
|
34977
|
+
//
|
|
34978
|
+
// `panel` loads picobot as its own toolbar panel rather than inside Helpdesk.
|
|
34979
|
+
function buildPicobotSrc(origin, path, {
|
|
34980
|
+
panel = false
|
|
34981
|
+
} = {}) {
|
|
33782
34982
|
const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
|
|
33783
34983
|
const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
|
|
33784
34984
|
const fragment = queryPart !== undefined ? `${safePath}?${queryPart}` : safePath;
|
|
33785
|
-
const
|
|
34985
|
+
const url = new URL("topbar", origin);
|
|
34986
|
+
if (panel) url.searchParams.set("panel", "1");
|
|
34987
|
+
const base = url.toString();
|
|
33786
34988
|
return fragment ? `${base}#${fragment}` : base;
|
|
33787
34989
|
}
|
|
33788
34990
|
|
|
33789
34991
|
const LOAD_TIMEOUT_MS = 3000;
|
|
33790
|
-
function createPicobotIframe(picobotOrigin
|
|
34992
|
+
function createPicobotIframe(picobotOrigin, {
|
|
34993
|
+
panel = false
|
|
34994
|
+
} = {}) {
|
|
33791
34995
|
return /*#__PURE__*/React__default.forwardRef(function PicobotIframe({
|
|
33792
34996
|
currentPath: iframePath,
|
|
33793
34997
|
onChangePath: onIframeChangePath,
|
|
@@ -33843,7 +35047,9 @@ function createPicobotIframe(picobotOrigin) {
|
|
|
33843
35047
|
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, picobotOrigin);
|
|
33844
35048
|
}
|
|
33845
35049
|
}), []);
|
|
33846
|
-
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath
|
|
35050
|
+
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath, {
|
|
35051
|
+
panel
|
|
35052
|
+
});
|
|
33847
35053
|
return /*#__PURE__*/React__default.createElement("iframe", {
|
|
33848
35054
|
ref: iframeRef,
|
|
33849
35055
|
src: iframeSrc,
|
|
@@ -33882,22 +35088,28 @@ function HelpdeskComponent({
|
|
|
33882
35088
|
currentPath,
|
|
33883
35089
|
env,
|
|
33884
35090
|
helpdeskConfig,
|
|
33885
|
-
|
|
35091
|
+
isPicobotLoading,
|
|
35092
|
+
onChangePath,
|
|
35093
|
+
openPicobot
|
|
33886
35094
|
}) {
|
|
33887
35095
|
const PicobotIframe = React__default.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot")), [env]);
|
|
35096
|
+
const loadingFallback = /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
35097
|
+
flex: 1,
|
|
35098
|
+
axis: "vertical"
|
|
35099
|
+
}, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
35100
|
+
closeDrawer: closeDrawer
|
|
35101
|
+
}, /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
35102
|
+
axis: "horizontal",
|
|
35103
|
+
spacing: 1
|
|
35104
|
+
}, /*#__PURE__*/React__default.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React__default.createElement(LoadingIndicator$1, null))));
|
|
35105
|
+
// The add-on reads its args once, so mounting before the gate answers
|
|
35106
|
+
// would leave out openPicobot for good.
|
|
35107
|
+
if (isPicobotLoading) return loadingFallback;
|
|
33888
35108
|
return /*#__PURE__*/React__default.createElement(UiExtensionFirstPartyComponent, {
|
|
33889
35109
|
grow: 1,
|
|
33890
35110
|
display: "flex",
|
|
33891
35111
|
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)))),
|
|
35112
|
+
loadingFallback: loadingFallback,
|
|
33901
35113
|
additionalComponents: {
|
|
33902
35114
|
AskAIBanner: {
|
|
33903
35115
|
component: AskAIBanner,
|
|
@@ -33917,6 +35129,18 @@ function HelpdeskComponent({
|
|
|
33917
35129
|
}
|
|
33918
35130
|
}
|
|
33919
35131
|
},
|
|
35132
|
+
AskAIButton: {
|
|
35133
|
+
component: AskAIButton,
|
|
35134
|
+
properties: {
|
|
35135
|
+
onClick: {
|
|
35136
|
+
event: false,
|
|
35137
|
+
type: Function
|
|
35138
|
+
},
|
|
35139
|
+
text: {
|
|
35140
|
+
type: String
|
|
35141
|
+
}
|
|
35142
|
+
}
|
|
35143
|
+
},
|
|
33920
35144
|
CalendlyWidget: {
|
|
33921
35145
|
component: CalendlyWidget,
|
|
33922
35146
|
properties: {
|
|
@@ -33995,7 +35219,11 @@ function HelpdeskComponent({
|
|
|
33995
35219
|
currentPageParams: location.search,
|
|
33996
35220
|
currentPath,
|
|
33997
35221
|
helpdeskConfig,
|
|
33998
|
-
onChangePath
|
|
35222
|
+
onChangePath,
|
|
35223
|
+
// Its presence tells helpdesk that Picobot has its own panel.
|
|
35224
|
+
...(openPicobot && {
|
|
35225
|
+
openPicobot
|
|
35226
|
+
})
|
|
33999
35227
|
}
|
|
34000
35228
|
});
|
|
34001
35229
|
}
|
|
@@ -34542,6 +35770,142 @@ const Notifications = {
|
|
|
34542
35770
|
name: "Notifications"
|
|
34543
35771
|
};
|
|
34544
35772
|
|
|
35773
|
+
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";
|
|
35774
|
+
// Helpdesk reads this context off the email path to open its email form the
|
|
35775
|
+
// same way a handoff inside its own drawer does.
|
|
35776
|
+
function emailPathWithContext(context = {}) {
|
|
35777
|
+
const params = new URLSearchParams();
|
|
35778
|
+
for (const key of ["conversationId", "source", "turnIndex"]) {
|
|
35779
|
+
const value = context[key];
|
|
35780
|
+
if (value !== null && value !== undefined) params.set(key, String(value));
|
|
35781
|
+
}
|
|
35782
|
+
return `email?${params}`;
|
|
35783
|
+
}
|
|
35784
|
+
function PicobotComponent({
|
|
35785
|
+
clearPending,
|
|
35786
|
+
closeDrawer,
|
|
35787
|
+
currentPath,
|
|
35788
|
+
env,
|
|
35789
|
+
onChangePath,
|
|
35790
|
+
openTool,
|
|
35791
|
+
pending
|
|
35792
|
+
}) {
|
|
35793
|
+
const PicobotIframe = React__default.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot"), {
|
|
35794
|
+
panel: true
|
|
35795
|
+
}), [env]);
|
|
35796
|
+
const picobotRef = React__default.useRef(null);
|
|
35797
|
+
const [isPicobotReady, setIsPicobotReady] = React__default.useState(false);
|
|
35798
|
+
// Picobot has no listener until it sends READY, so openPicobot's draft or
|
|
35799
|
+
// conversation waits for it.
|
|
35800
|
+
React__default.useEffect(function handOverPendingOncePicobotIsReady() {
|
|
35801
|
+
var _a, _b;
|
|
35802
|
+
if (!pending || !isPicobotReady) return;
|
|
35803
|
+
const {
|
|
35804
|
+
conversationId,
|
|
35805
|
+
draft,
|
|
35806
|
+
ticketExternalId
|
|
35807
|
+
} = pending;
|
|
35808
|
+
if (conversationId) {
|
|
35809
|
+
(_a = picobotRef.current) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
35810
|
+
payload: {
|
|
35811
|
+
conversation_id: conversationId,
|
|
35812
|
+
// Picobot asks whether this resolved the ticket, and closes it.
|
|
35813
|
+
...(ticketExternalId && {
|
|
35814
|
+
external_id: ticketExternalId,
|
|
35815
|
+
source: "ticket_submitted"
|
|
35816
|
+
})
|
|
35817
|
+
},
|
|
35818
|
+
type: "LOAD_CONVERSATION"
|
|
35819
|
+
});
|
|
35820
|
+
} else if (draft) {
|
|
35821
|
+
// Not both: Picobot cancels a conversation load when a prefill
|
|
35822
|
+
// starts a new conversation.
|
|
35823
|
+
(_b = picobotRef.current) === null || _b === void 0 ? void 0 : _b.postMessage({
|
|
35824
|
+
payload: {
|
|
35825
|
+
source: "email_form",
|
|
35826
|
+
text: draft
|
|
35827
|
+
},
|
|
35828
|
+
type: "PREFILL_COMPOSER"
|
|
35829
|
+
});
|
|
35830
|
+
}
|
|
35831
|
+
clearPending();
|
|
35832
|
+
}, [pending, isPicobotReady, clearPending]);
|
|
35833
|
+
const [iframeErrored, setIframeErrored] = React__default.useState(false);
|
|
35834
|
+
const [mountKey, setMountKey] = React__default.useState(0);
|
|
35835
|
+
const handleRetry = React__default.useCallback(() => {
|
|
35836
|
+
setIframeErrored(false);
|
|
35837
|
+
setIsPicobotReady(false);
|
|
35838
|
+
setMountKey(key => key + 1);
|
|
35839
|
+
}, []);
|
|
35840
|
+
const handleMessage = React__default.useCallback(data => {
|
|
35841
|
+
var _a;
|
|
35842
|
+
const message = data;
|
|
35843
|
+
switch (message === null || message === void 0 ? void 0 : message.type) {
|
|
35844
|
+
case "CLOSE_DRAWER":
|
|
35845
|
+
return closeDrawer();
|
|
35846
|
+
case "READY":
|
|
35847
|
+
return setIsPicobotReady(true);
|
|
35848
|
+
case "PICOBOT_SUPPORT_HANDOFF":
|
|
35849
|
+
return openTool("helpdesk", ((_a = message.payload) === null || _a === void 0 ? void 0 : _a.method) === "zoom" ? "phone" : emailPathWithContext(message.payload));
|
|
35850
|
+
case "IFRAME_LOAD_ERROR":
|
|
35851
|
+
console.warn("topbar: Picobot iframe failed to load");
|
|
35852
|
+
return setIframeErrored(true);
|
|
35853
|
+
}
|
|
35854
|
+
}, [closeDrawer, openTool]);
|
|
35855
|
+
if (iframeErrored) {
|
|
35856
|
+
return /*#__PURE__*/React__default.createElement(LoadError, {
|
|
35857
|
+
onContactSupport: () => openTool("helpdesk", "email"),
|
|
35858
|
+
onRetry: handleRetry
|
|
35859
|
+
});
|
|
35860
|
+
}
|
|
35861
|
+
return /*#__PURE__*/React__default.createElement(PicobotIframe, {
|
|
35862
|
+
key: mountKey,
|
|
35863
|
+
ref: picobotRef,
|
|
35864
|
+
currentPath: currentPath,
|
|
35865
|
+
onChangePath: onChangePath,
|
|
35866
|
+
onMessage: handleMessage
|
|
35867
|
+
});
|
|
35868
|
+
}
|
|
35869
|
+
function LoadError({
|
|
35870
|
+
onContactSupport,
|
|
35871
|
+
onRetry
|
|
35872
|
+
}) {
|
|
35873
|
+
return /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
35874
|
+
axis: "vertical",
|
|
35875
|
+
flex: 1,
|
|
35876
|
+
alignment: "center",
|
|
35877
|
+
distribution: "center",
|
|
35878
|
+
padding: 3,
|
|
35879
|
+
spacing: 1
|
|
35880
|
+
}, /*#__PURE__*/React__default.createElement(Heading, {
|
|
35881
|
+
level: 2
|
|
35882
|
+
}, "Picobot is temporarily unavailable"), /*#__PURE__*/React__default.createElement(Text, {
|
|
35883
|
+
size: 5,
|
|
35884
|
+
color: "var(--t-text-color-secondary)"
|
|
35885
|
+
}, "Try again or contact support."), /*#__PURE__*/React__default.createElement(Box, {
|
|
35886
|
+
paddingTop: 2
|
|
35887
|
+
}, /*#__PURE__*/React__default.createElement(Button$1, {
|
|
35888
|
+
onClick: onRetry,
|
|
35889
|
+
iconLeft: "general.refresh",
|
|
35890
|
+
theme: "primary",
|
|
35891
|
+
variant: "fill",
|
|
35892
|
+
title: "Retry"
|
|
35893
|
+
})), /*#__PURE__*/React__default.createElement(Button$1, {
|
|
35894
|
+
onClick: onContactSupport,
|
|
35895
|
+
variant: "naked",
|
|
35896
|
+
theme: "primary",
|
|
35897
|
+
title: "Contact support"
|
|
35898
|
+
}));
|
|
35899
|
+
}
|
|
35900
|
+
const Picobot = {
|
|
35901
|
+
component: PicobotComponent,
|
|
35902
|
+
icons: {
|
|
35903
|
+
staticDotPath: SPARKLE_PATH,
|
|
35904
|
+
staticPath: SPARKLE_PATH
|
|
35905
|
+
},
|
|
35906
|
+
name: "Picobot"
|
|
35907
|
+
};
|
|
35908
|
+
|
|
34545
35909
|
const {
|
|
34546
35910
|
LoadingIndicator,
|
|
34547
35911
|
StackView
|
|
@@ -34937,6 +36301,41 @@ const useChatConfig = ({
|
|
|
34937
36301
|
};
|
|
34938
36302
|
};
|
|
34939
36303
|
|
|
36304
|
+
// Picobot's API rejects requests without a version header, and apiRequest
|
|
36305
|
+
// sets its own headers, so the version rides in on the fetch.
|
|
36306
|
+
const picobotFetch = (url, opts) => defaultFetch(url, {
|
|
36307
|
+
...opts,
|
|
36308
|
+
headers: {
|
|
36309
|
+
...(opts === null || opts === void 0 ? void 0 : opts.headers),
|
|
36310
|
+
"X-PCO-API-Version": "2026-08-11"
|
|
36311
|
+
}
|
|
36312
|
+
});
|
|
36313
|
+
// Routes wait on the gate, so a slow Picobot can't hold them up for long.
|
|
36314
|
+
const GATE_TIMEOUT_MS = 3000;
|
|
36315
|
+
const usePicobotConfig = ({
|
|
36316
|
+
isEnabled
|
|
36317
|
+
}) => {
|
|
36318
|
+
// Undefined until Picobot answers.
|
|
36319
|
+
const [panelEnabled, setPanelEnabled] = useState();
|
|
36320
|
+
useEffect(() => {
|
|
36321
|
+
if (!isEnabled) return undefined;
|
|
36322
|
+
// Whatever settles first wins, so a late answer can't reopen the gate.
|
|
36323
|
+
const settle = enabled => setPanelEnabled(current => current !== null && current !== void 0 ? current : enabled);
|
|
36324
|
+
const timeout = setTimeout(() => settle(false), GATE_TIMEOUT_MS);
|
|
36325
|
+
apiRequest(picobotFetch, `${window.location.origin}/~api/picobot/v2/me`).then(({
|
|
36326
|
+
json
|
|
36327
|
+
}) => {
|
|
36328
|
+
var _a, _b;
|
|
36329
|
+
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);
|
|
36330
|
+
}).catch(() => settle(false)).finally(() => clearTimeout(timeout));
|
|
36331
|
+
return () => clearTimeout(timeout);
|
|
36332
|
+
}, [isEnabled]);
|
|
36333
|
+
return {
|
|
36334
|
+
displayPicobotIcon: panelEnabled === true,
|
|
36335
|
+
isLoading: isEnabled && panelEnabled === undefined
|
|
36336
|
+
};
|
|
36337
|
+
};
|
|
36338
|
+
|
|
34940
36339
|
const Profile = {
|
|
34941
36340
|
component: UserProfile,
|
|
34942
36341
|
icons: {
|
|
@@ -34947,10 +36346,16 @@ const Profile = {
|
|
|
34947
36346
|
};
|
|
34948
36347
|
|
|
34949
36348
|
const DURATION = 100;
|
|
36349
|
+
// Exempt paths match regardless of query, so `email?conversationId=…` is
|
|
36350
|
+
// exempt along with `email`.
|
|
34950
36351
|
function isToolPathExemptFromStorage(toolName, path) {
|
|
34951
36352
|
var _a, _b;
|
|
34952
36353
|
const tool = toolsRouteMap[toolName];
|
|
34953
|
-
|
|
36354
|
+
const [pathWithoutQuery] = path.split("?");
|
|
36355
|
+
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;
|
|
36356
|
+
}
|
|
36357
|
+
function isConversationPath(path) {
|
|
36358
|
+
return Boolean(path === null || path === void 0 ? void 0 : path.startsWith("conversations/"));
|
|
34954
36359
|
}
|
|
34955
36360
|
function scrollToolDrawerToTop(toolName) {
|
|
34956
36361
|
var _a;
|
|
@@ -34963,6 +36368,7 @@ const TOOLS_MAP = {
|
|
|
34963
36368
|
Chat,
|
|
34964
36369
|
Helpdesk,
|
|
34965
36370
|
Notifications,
|
|
36371
|
+
Picobot,
|
|
34966
36372
|
Profile,
|
|
34967
36373
|
Tasks
|
|
34968
36374
|
};
|
|
@@ -34990,7 +36396,8 @@ function Toolbar({
|
|
|
34990
36396
|
});
|
|
34991
36397
|
const {
|
|
34992
36398
|
currentContent,
|
|
34993
|
-
open
|
|
36399
|
+
open,
|
|
36400
|
+
pending
|
|
34994
36401
|
} = state;
|
|
34995
36402
|
const mounted = !!currentContent;
|
|
34996
36403
|
const storeData = useStore();
|
|
@@ -35003,6 +36410,15 @@ function Toolbar({
|
|
|
35003
36410
|
...(env === "development" ? _storeDataOverride !== null && _storeDataOverride !== void 0 ? _storeDataOverride : {} : {})
|
|
35004
36411
|
};
|
|
35005
36412
|
const updateStore = useUpdateStore();
|
|
36413
|
+
const {
|
|
36414
|
+
displayPicobotIcon,
|
|
36415
|
+
isLoading: isPicobotGateLoading
|
|
36416
|
+
} = usePicobotConfig({
|
|
36417
|
+
isEnabled: Boolean(userId)
|
|
36418
|
+
});
|
|
36419
|
+
// Picobot is a kill-switched tool: until its gate says the panel is
|
|
36420
|
+
// enabled, its routes are held, and if it never does, they're ignored.
|
|
36421
|
+
const routableTool = useCallback(name => name === Picobot.name.toLowerCase() && !displayPicobotIcon ? undefined : toolsRouteMap[name], [displayPicobotIcon]);
|
|
35006
36422
|
React__default.useEffect(() => {
|
|
35007
36423
|
if (open) {
|
|
35008
36424
|
document.body.style.overflow = "hidden";
|
|
@@ -35090,7 +36506,9 @@ function Toolbar({
|
|
|
35090
36506
|
});
|
|
35091
36507
|
}, [clearRoute]);
|
|
35092
36508
|
React__default.useEffect(() => {
|
|
35093
|
-
if (!toolbarFragmentRoute)
|
|
36509
|
+
if (!toolbarFragmentRoute || !routableTool(toolbarFragmentRoute.tool)) {
|
|
36510
|
+
return;
|
|
36511
|
+
}
|
|
35094
36512
|
const isExempt = isToolPathExemptFromStorage(toolbarFragmentRoute.tool, toolbarFragmentRoute.path);
|
|
35095
36513
|
if (isExempt) return;
|
|
35096
36514
|
const nextState = {
|
|
@@ -35100,12 +36518,32 @@ function Toolbar({
|
|
|
35100
36518
|
if (!shallowCompare(nextState, toolStateCookie)) {
|
|
35101
36519
|
setToolStateCookie(nextState);
|
|
35102
36520
|
}
|
|
35103
|
-
}, [toolbarFragmentRoute, toolStateCookie]);
|
|
36521
|
+
}, [toolbarFragmentRoute, toolStateCookie, routableTool]);
|
|
35104
36522
|
React__default.useEffect(() => {
|
|
35105
|
-
|
|
35106
|
-
|
|
36523
|
+
// Helpdesk conversations may belong to Picobot, so they wait for its gate
|
|
36524
|
+
// rather than flashing open in Helpdesk first.
|
|
36525
|
+
const mayRedirectToPicobot = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path) && (isPicobotGateLoading || displayPicobotIcon);
|
|
36526
|
+
const tool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && !mayRedirectToPicobot && routableTool(toolbarFragmentRoute.tool);
|
|
36527
|
+
if (tool && (currentContent !== tool || !open)) {
|
|
36528
|
+
setToolAndOpenDrawer(tool);
|
|
35107
36529
|
}
|
|
35108
|
-
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, currentContent, open]);
|
|
36530
|
+
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path, currentContent, open, routableTool, isPicobotGateLoading, displayPicobotIcon]);
|
|
36531
|
+
const openTool = useCallback((name, path, pendingValue) => {
|
|
36532
|
+
const tool = routableTool(name.toLowerCase());
|
|
36533
|
+
if (!tool) return;
|
|
36534
|
+
dispatch({
|
|
36535
|
+
payload: pendingValue === undefined ? null : {
|
|
36536
|
+
tool: tool.name,
|
|
36537
|
+
value: pendingValue
|
|
36538
|
+
},
|
|
36539
|
+
type: ActionType.setPending
|
|
36540
|
+
});
|
|
36541
|
+
onChangeToolbarFragmentRoute(tool.name.toLowerCase(), path);
|
|
36542
|
+
scrollToolDrawerToTop(tool.name);
|
|
36543
|
+
}, [onChangeToolbarFragmentRoute, routableTool]);
|
|
36544
|
+
const clearPending = useCallback(() => dispatch({
|
|
36545
|
+
type: ActionType.clearPending
|
|
36546
|
+
}), []);
|
|
35109
36547
|
const toggleDrawer = content => {
|
|
35110
36548
|
if (!open) {
|
|
35111
36549
|
return openDrawer(content);
|
|
@@ -35122,12 +36560,52 @@ function Toolbar({
|
|
|
35122
36560
|
joltPromise: storeData === null || storeData === void 0 ? void 0 : storeData.joltPromise,
|
|
35123
36561
|
userId
|
|
35124
36562
|
});
|
|
35125
|
-
|
|
36563
|
+
// Picobot conversations moved from Helpdesk to the Picobot panel, so
|
|
36564
|
+
// send old links and saved paths there once the panel is enabled.
|
|
36565
|
+
React__default.useEffect(() => {
|
|
36566
|
+
if (!displayPicobotIcon) return;
|
|
36567
|
+
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path)) {
|
|
36568
|
+
onChangeToolbarFragmentRoute(Picobot.name.toLowerCase(), toolbarFragmentRoute.path);
|
|
36569
|
+
}
|
|
36570
|
+
}, [displayPicobotIcon, toolbarFragmentRoute, onChangeToolbarFragmentRoute]);
|
|
36571
|
+
React__default.useEffect(() => {
|
|
36572
|
+
if (!displayPicobotIcon) return;
|
|
36573
|
+
const {
|
|
36574
|
+
helpdesk,
|
|
36575
|
+
picobot
|
|
36576
|
+
} = toolStateCookie;
|
|
36577
|
+
if (!isConversationPath(helpdesk)) return;
|
|
36578
|
+
setToolStateCookie({
|
|
36579
|
+
...toolStateCookie,
|
|
36580
|
+
helpdesk: "",
|
|
36581
|
+
picobot: picobot || helpdesk
|
|
36582
|
+
});
|
|
36583
|
+
}, [displayPicobotIcon, toolStateCookie]);
|
|
36584
|
+
const openPicobot = useCallback(options => {
|
|
36585
|
+
var _a;
|
|
36586
|
+
// Moving an open Picobot off its path reloads or navigates the iframe,
|
|
36587
|
+
// which would race the options it's being handed.
|
|
36588
|
+
const path = currentContent === Picobot ? (_a = toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path) !== null && _a !== void 0 ? _a : "" : "";
|
|
36589
|
+
openTool(Picobot.name, path, options);
|
|
36590
|
+
}, [openTool, currentContent, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path]);
|
|
36591
|
+
React__default.useEffect(() => {
|
|
36592
|
+
if (!displayPicobotIcon) return undefined;
|
|
36593
|
+
window.topbar = window.topbar || {};
|
|
36594
|
+
window.topbar.openPicobot = openPicobot;
|
|
36595
|
+
return () => {
|
|
36596
|
+
delete window.topbar.openPicobot;
|
|
36597
|
+
};
|
|
36598
|
+
}, [displayPicobotIcon, openPicobot]);
|
|
36599
|
+
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat, displayPicobotIcon && Picobot, isPcoTestHost() && DeveloperTools].filter(Boolean);
|
|
35126
36600
|
const handleToolOnClick = tool => {
|
|
35127
36601
|
toggleDrawer(tool);
|
|
35128
36602
|
scrollToolDrawerToTop(tool.name);
|
|
35129
36603
|
};
|
|
35130
36604
|
const toolSpecificProps = {
|
|
36605
|
+
Helpdesk: {
|
|
36606
|
+
isPicobotLoading: isPicobotGateLoading,
|
|
36607
|
+
openPicobot: displayPicobotIcon ? openPicobot : undefined
|
|
36608
|
+
},
|
|
35131
36609
|
Profile: {
|
|
35132
36610
|
appName,
|
|
35133
36611
|
closeDrawer,
|
|
@@ -35150,6 +36628,7 @@ function Toolbar({
|
|
|
35150
36628
|
const routeIsForCurrentTool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && toolsRouteMap[toolbarFragmentRoute.tool] === currentContent;
|
|
35151
36629
|
const currentToolProps = {
|
|
35152
36630
|
appName,
|
|
36631
|
+
clearPending,
|
|
35153
36632
|
closeDrawer,
|
|
35154
36633
|
currentPath: routeIsForCurrentTool ? toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path : null,
|
|
35155
36634
|
env,
|
|
@@ -35158,6 +36637,8 @@ function Toolbar({
|
|
|
35158
36637
|
joltPromise,
|
|
35159
36638
|
onChangePath,
|
|
35160
36639
|
openDrawer: () => currentContent && openDrawer(currentContent),
|
|
36640
|
+
openTool,
|
|
36641
|
+
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
36642
|
userId,
|
|
35162
36643
|
...(currentContent ? toolSpecificProps[currentContent.name] : {})
|
|
35163
36644
|
};
|
|
@@ -35168,38 +36649,37 @@ function Toolbar({
|
|
|
35168
36649
|
return /*#__PURE__*/React__default.createElement(ThemeProvider, {
|
|
35169
36650
|
theme: topbarTheme
|
|
35170
36651
|
}, /*#__PURE__*/React__default.createElement(UiExtensionDataContext, null, /*#__PURE__*/React__default.createElement("div", {
|
|
35171
|
-
className: styles$
|
|
36652
|
+
className: styles$2["toolbar"],
|
|
35172
36653
|
"data-pendo": "toolbar-profile-support-wrapper"
|
|
35173
36654
|
}, /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35174
36655
|
"aria-label": `Open ${userName}’s profile drawer`,
|
|
35175
36656
|
onClick: () => toggleDrawer(Profile),
|
|
35176
36657
|
"data-pendo": "toolbar-profile",
|
|
35177
|
-
className: styles$
|
|
36658
|
+
className: styles$2["toolbar-button--avatar"]
|
|
35178
36659
|
}, /*#__PURE__*/React__default.createElement(Avatar, {
|
|
35179
36660
|
env: env,
|
|
35180
36661
|
url: userAvatarPath
|
|
35181
36662
|
})), /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35182
|
-
className: styles$
|
|
36663
|
+
className: styles$2["toolbar-button--home"],
|
|
35183
36664
|
onClick: () => closeDrawer()
|
|
35184
|
-
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
36665
|
+
}, /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
35185
36666
|
name: "toolbar.planningCenter",
|
|
35186
36667
|
size: "24px"
|
|
35187
36668
|
})), [HelpDeskTool, ...TOOLS].map(tool => {
|
|
36669
|
+
var _a;
|
|
35188
36670
|
const current = open && currentContent === tool;
|
|
35189
36671
|
const indicator = indicatorState[tool.name];
|
|
35190
36672
|
return /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35191
36673
|
current: current,
|
|
35192
36674
|
onClick: () => handleToolOnClick(tool),
|
|
35193
36675
|
id: getDataPendoAttribute(tool.name),
|
|
35194
|
-
"aria-label": `Open ${tool.name} drawer`,
|
|
36676
|
+
"aria-label": `Open ${(_a = tool.label) !== null && _a !== void 0 ? _a : tool.name} drawer`,
|
|
35195
36677
|
"data-pendo": getDataPendoAttribute(tool.name),
|
|
35196
36678
|
key: tool.name
|
|
35197
|
-
},
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
dot: true
|
|
35202
|
-
})));
|
|
36679
|
+
}, /*#__PURE__*/React__default.createElement(ToolbarToolIcon, {
|
|
36680
|
+
icons: tool.icons,
|
|
36681
|
+
dot: !!indicator
|
|
36682
|
+
}));
|
|
35203
36683
|
})), /*#__PURE__*/React__default.createElement(DrawerWrap, {
|
|
35204
36684
|
isVisible: open
|
|
35205
36685
|
}, mounted && (/*#__PURE__*/React__default.createElement(Drawer, {
|
|
@@ -35217,12 +36697,25 @@ const ToolbarButton = ({
|
|
|
35217
36697
|
}) => {
|
|
35218
36698
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
35219
36699
|
type: "button",
|
|
35220
|
-
className: classnames(styles$
|
|
36700
|
+
className: classnames(styles$2["toolbar-button"], className, current && styles$2["is-current"]),
|
|
35221
36701
|
onClick: onClick,
|
|
35222
36702
|
"data-toolbar-link": "true",
|
|
35223
36703
|
...restProps
|
|
35224
36704
|
}, children);
|
|
35225
36705
|
};
|
|
36706
|
+
function ToolbarToolIcon({
|
|
36707
|
+
dot,
|
|
36708
|
+
icons
|
|
36709
|
+
}) {
|
|
36710
|
+
if ("component" in icons) {
|
|
36711
|
+
return /*#__PURE__*/React__default.createElement(icons.component, null);
|
|
36712
|
+
} else {
|
|
36713
|
+
return /*#__PURE__*/React__default.createElement(ToolbarButtonIcon, {
|
|
36714
|
+
path: dot ? icons.staticDotPath : icons.staticPath,
|
|
36715
|
+
dot: dot
|
|
36716
|
+
});
|
|
36717
|
+
}
|
|
36718
|
+
}
|
|
35226
36719
|
function ToolbarButtonIcon({
|
|
35227
36720
|
dot,
|
|
35228
36721
|
path
|