@planningcenter/topbar 12.3.0 → 12.4.0-rc.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 +84 -18
- package/dist/topbar.css.map +1 -1
- package/dist/topbar.js +1980 -873
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +1985 -878
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +1985 -878
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +1980 -873
- package/dist/topbar.umd.js.map +1 -1
- package/package.json +4 -3
package/dist/topbar.modern.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as general from '@planningcenter/icons/paths/general';
|
|
|
6
6
|
import Jolt from '@planningcenter/jolt-client';
|
|
7
7
|
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
8
8
|
import 'react/jsx-runtime';
|
|
9
|
-
import { ThemeProvider, Divider, Button as Button$1, StackView as StackView$2, Text, Icon, designSystem, Box, Heading, GridView, Avatar as Avatar$1, Link as Link$1, HeadingUppercase, List, Combobox, Logo, Dropdown, Spinner, Badge } from '@planningcenter/tapestry-react';
|
|
9
|
+
import { ThemeProvider, Divider, Button as Button$1, StackView as StackView$2, Text, Icon as Icon$1, designSystem, Box, Heading, GridView, Avatar as Avatar$1, Link as Link$1, HeadingUppercase, List, Combobox, Logo, Dropdown, Spinner, Badge } from '@planningcenter/tapestry-react';
|
|
10
10
|
import { components, UiExtensionFirstPartyComponent, UiExtensionDataContext } from '@planningcenter/add-ons';
|
|
11
11
|
import * as churchCenter from '@planningcenter/icons/paths/church-center';
|
|
12
12
|
import * as people from '@planningcenter/icons/paths/people';
|
|
@@ -17,7 +17,7 @@ import { useSearchBox, useHits, InstantSearch, Configure } from 'react-instantse
|
|
|
17
17
|
import { TransitionGroup, Transition } from 'react-transition-group';
|
|
18
18
|
import datetimeFmt from '@planningcenter/datetime-fmt';
|
|
19
19
|
|
|
20
|
-
var styles$
|
|
20
|
+
var styles$m = {"accessibility-toolbar":"accessibility-toolbar-jgjUG"};
|
|
21
21
|
|
|
22
22
|
const AccessibilityToolbar = ({
|
|
23
23
|
children
|
|
@@ -29,7 +29,7 @@ const AccessibilityToolbar = ({
|
|
|
29
29
|
(_a = document.getElementById("main-content")) === null || _a === void 0 ? void 0 : _a.focus();
|
|
30
30
|
};
|
|
31
31
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32
|
-
className: styles$
|
|
32
|
+
className: styles$m["accessibility-toolbar"]
|
|
33
33
|
}, /*#__PURE__*/React__default.createElement(Link, {
|
|
34
34
|
href: "#main-content",
|
|
35
35
|
kind: "primary",
|
|
@@ -156,12 +156,14 @@ function inferRailsCsrfToken() {
|
|
|
156
156
|
}
|
|
157
157
|
function apiRequest(fetch, url, {
|
|
158
158
|
method = "GET",
|
|
159
|
-
data = null
|
|
159
|
+
data = null,
|
|
160
|
+
headers = {}
|
|
160
161
|
} = {}) {
|
|
161
162
|
const options = {
|
|
162
163
|
headers: {
|
|
163
164
|
Accept: "application/json",
|
|
164
|
-
"Content-Type": "application/json"
|
|
165
|
+
"Content-Type": "application/json",
|
|
166
|
+
...headers
|
|
165
167
|
},
|
|
166
168
|
method,
|
|
167
169
|
redirect: "follow"
|
|
@@ -346,11 +348,11 @@ const X = ({
|
|
|
346
348
|
fill: fill
|
|
347
349
|
})));
|
|
348
350
|
|
|
349
|
-
var styles$
|
|
351
|
+
var styles$l = {"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
352
|
|
|
351
353
|
function AnnouncementIcon() {
|
|
352
354
|
return /*#__PURE__*/React__default.createElement("svg", {
|
|
353
|
-
className: styles$
|
|
355
|
+
className: styles$l["announcement-icon"],
|
|
354
356
|
width: "16",
|
|
355
357
|
height: "16",
|
|
356
358
|
viewBox: "0 0 16 16",
|
|
@@ -475,7 +477,7 @@ let StyledAnnouncement$1 = class StyledAnnouncement extends React__default.Compo
|
|
|
475
477
|
} = this.props;
|
|
476
478
|
const isPromo = variant === "promo";
|
|
477
479
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
478
|
-
className: classnames(styles$
|
|
480
|
+
className: classnames(styles$l["announcement"], styles$l[`announcement--${variant !== null && variant !== void 0 ? variant : "platform"}`]),
|
|
479
481
|
"data-announcement-variant": variant !== null && variant !== void 0 ? variant : "platform",
|
|
480
482
|
style: isPromo ? {
|
|
481
483
|
backgroundImage: promoBackgroundUrl,
|
|
@@ -484,7 +486,7 @@ let StyledAnnouncement$1 = class StyledAnnouncement extends React__default.Compo
|
|
|
484
486
|
backgroundSize: "cover"
|
|
485
487
|
} : undefined
|
|
486
488
|
}, !isPromo && /*#__PURE__*/React__default.createElement(AnnouncementIcon, null), /*#__PURE__*/React__default.createElement("div", {
|
|
487
|
-
className: styles$
|
|
489
|
+
className: styles$l["announcement-content"],
|
|
488
490
|
style: style,
|
|
489
491
|
...platformProps
|
|
490
492
|
}));
|
|
@@ -533,7 +535,7 @@ function StyledDismissButton$1({
|
|
|
533
535
|
const isPromo = variant === "promo";
|
|
534
536
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
535
537
|
type: "button",
|
|
536
|
-
className: classnames(styles$
|
|
538
|
+
className: classnames(styles$l.dismiss, isPromo && styles$l["dismiss--promo"]),
|
|
537
539
|
...props
|
|
538
540
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
539
541
|
fill: isPromo ? "currentColor" : "white",
|
|
@@ -613,9 +615,9 @@ class StyledAnnouncement extends React__default.Component {
|
|
|
613
615
|
...platformProps
|
|
614
616
|
} = this.props;
|
|
615
617
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
616
|
-
className: styles$
|
|
618
|
+
className: styles$l["announcement"]
|
|
617
619
|
}, /*#__PURE__*/React__default.createElement(AnnouncementIcon, null), /*#__PURE__*/React__default.createElement("div", {
|
|
618
|
-
className: styles$
|
|
620
|
+
className: styles$l["announcement-content"],
|
|
619
621
|
style: style,
|
|
620
622
|
...platformProps
|
|
621
623
|
}));
|
|
@@ -626,7 +628,7 @@ function StyledDismissButton({
|
|
|
626
628
|
}) {
|
|
627
629
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
628
630
|
type: "button",
|
|
629
|
-
className: styles$
|
|
631
|
+
className: styles$l.dismiss,
|
|
630
632
|
...props
|
|
631
633
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
632
634
|
fill: "white",
|
|
@@ -781,7 +783,7 @@ function useMediaQuery(query) {
|
|
|
781
783
|
return matches;
|
|
782
784
|
}
|
|
783
785
|
|
|
784
|
-
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
786
|
+
const capitalize$1 = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
785
787
|
const RESOLVED_COLOR_MODE_ATTRIBUTE = "data-resolved-color-mode";
|
|
786
788
|
function resolveColorMode(colorMode) {
|
|
787
789
|
if (colorMode === "system") {
|
|
@@ -1748,7 +1750,7 @@ function MonoAppIcon({
|
|
|
1748
1750
|
})) : (/*#__PURE__*/React__default.createElement("div", null, "appName not supported")));
|
|
1749
1751
|
}
|
|
1750
1752
|
|
|
1751
|
-
var styles$
|
|
1753
|
+
var styles$k = {"icon-slot":"icon-slot-m3ZIk","name":"name-UNqbW"};
|
|
1752
1754
|
|
|
1753
1755
|
const AppRow = ({
|
|
1754
1756
|
appName,
|
|
@@ -1757,24 +1759,24 @@ const AppRow = ({
|
|
|
1757
1759
|
}) => {
|
|
1758
1760
|
if (appName === "Accounts") {
|
|
1759
1761
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
|
|
1760
|
-
className: styles$
|
|
1762
|
+
className: styles$k["icon-slot"]
|
|
1761
1763
|
}, /*#__PURE__*/React__default.createElement(MonoAppIcon, {
|
|
1762
1764
|
appName: "Accounts",
|
|
1763
1765
|
colors: colors,
|
|
1764
1766
|
size: iconSize
|
|
1765
1767
|
})), /*#__PURE__*/React__default.createElement("span", {
|
|
1766
|
-
className: styles$
|
|
1768
|
+
className: styles$k.name
|
|
1767
1769
|
}, "Account settings"));
|
|
1768
1770
|
}
|
|
1769
1771
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
1770
1772
|
appName: appName.replace(/[\s-]/, ""),
|
|
1771
1773
|
size: iconSize
|
|
1772
1774
|
}), /*#__PURE__*/React__default.createElement("span", {
|
|
1773
|
-
className: styles$
|
|
1775
|
+
className: styles$k.name
|
|
1774
1776
|
}, appName));
|
|
1775
1777
|
};
|
|
1776
1778
|
|
|
1777
|
-
var styles$
|
|
1779
|
+
var styles$j = {"button":"button-XMbeQ","label":"label-884of","chevron":"chevron-RRg9k"};
|
|
1778
1780
|
|
|
1779
1781
|
const AppsButton = /*#__PURE__*/React__default.forwardRef(({
|
|
1780
1782
|
appName,
|
|
@@ -1786,10 +1788,10 @@ const AppsButton = /*#__PURE__*/React__default.forwardRef(({
|
|
|
1786
1788
|
...restProps,
|
|
1787
1789
|
ref: ref,
|
|
1788
1790
|
"aria-label": `App switcher: ${appName}`,
|
|
1789
|
-
className: styles$
|
|
1791
|
+
className: styles$j.button,
|
|
1790
1792
|
kind: "neutral-inline",
|
|
1791
1793
|
label: /*#__PURE__*/React__default.createElement("span", {
|
|
1792
|
-
className: styles$
|
|
1794
|
+
className: styles$j.label
|
|
1793
1795
|
}, isAccounts ? "Account settings" : appName),
|
|
1794
1796
|
prefix: isAccounts ? (/*#__PURE__*/React__default.createElement(MonoAppIcon, {
|
|
1795
1797
|
appName: "Accounts",
|
|
@@ -1800,13 +1802,13 @@ const AppsButton = /*#__PURE__*/React__default.forwardRef(({
|
|
|
1800
1802
|
size: 22
|
|
1801
1803
|
})),
|
|
1802
1804
|
suffix: /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, {
|
|
1803
|
-
className: `${styles$
|
|
1805
|
+
className: `${styles$j.chevron} symbol`
|
|
1804
1806
|
})
|
|
1805
1807
|
});
|
|
1806
1808
|
});
|
|
1807
1809
|
AppsButton.displayName = "AppsButton";
|
|
1808
1810
|
|
|
1809
|
-
var styles$
|
|
1811
|
+
var styles$i = {"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
1812
|
|
|
1811
1813
|
const AppsMenu = ({
|
|
1812
1814
|
appName,
|
|
@@ -1827,8 +1829,8 @@ const AppsMenu = ({
|
|
|
1827
1829
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
1828
1830
|
"aria-current": isCurrent ? "page" : undefined,
|
|
1829
1831
|
"aria-label": name,
|
|
1830
|
-
className: classnames(styles$
|
|
1831
|
-
[styles$
|
|
1832
|
+
className: classnames(styles$i.link, {
|
|
1833
|
+
[styles$i.current]: isCurrent
|
|
1832
1834
|
}),
|
|
1833
1835
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
1834
1836
|
"data-turbolinks": false,
|
|
@@ -1844,22 +1846,22 @@ const AppsMenu = ({
|
|
|
1844
1846
|
colors: colors
|
|
1845
1847
|
})), /*#__PURE__*/React__default.createElement(PopoverContent, {
|
|
1846
1848
|
"aria-label": "Switch app",
|
|
1847
|
-
className: styles$
|
|
1849
|
+
className: styles$i["popover-content"],
|
|
1848
1850
|
placement: "bottom start"
|
|
1849
1851
|
}, /*#__PURE__*/React__default.createElement("nav", {
|
|
1850
|
-
className: styles$
|
|
1852
|
+
className: styles$i.nav,
|
|
1851
1853
|
"data-pendo": "app-switcher"
|
|
1852
1854
|
}, /*#__PURE__*/React__default.createElement("ul", {
|
|
1853
|
-
className: styles$
|
|
1855
|
+
className: styles$i.list,
|
|
1854
1856
|
role: "list"
|
|
1855
1857
|
}, homeProduct && renderProductLink(homeProduct.name), restProducts.map(({
|
|
1856
1858
|
name
|
|
1857
1859
|
}) => renderProductLink(name)), accountsProduct && (/*#__PURE__*/React__default.createElement("li", {
|
|
1858
|
-
className: styles$
|
|
1860
|
+
className: styles$i["accounts-item"]
|
|
1859
1861
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
1860
1862
|
"aria-current": appName === accountsProduct.name ? "page" : undefined,
|
|
1861
1863
|
"aria-label": "Account settings",
|
|
1862
|
-
className: classnames(styles$
|
|
1864
|
+
className: classnames(styles$i.link, styles$i["accounts-link"]),
|
|
1863
1865
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
1864
1866
|
"data-turbolinks": false,
|
|
1865
1867
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -2067,7 +2069,7 @@ function MonoAppName({
|
|
|
2067
2069
|
})) : (/*#__PURE__*/React__default.createElement("div", null, "appName not supported"));
|
|
2068
2070
|
}
|
|
2069
2071
|
|
|
2070
|
-
var styles$
|
|
2072
|
+
var styles$h = {"unbutton":"unbutton-lEbTR"};
|
|
2071
2073
|
|
|
2072
2074
|
function Unbutton({
|
|
2073
2075
|
className,
|
|
@@ -2076,7 +2078,7 @@ function Unbutton({
|
|
|
2076
2078
|
...nativeProps
|
|
2077
2079
|
}) {
|
|
2078
2080
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
2079
|
-
className: classnames(styles$
|
|
2081
|
+
className: classnames(styles$h.unbutton, className),
|
|
2080
2082
|
style: {
|
|
2081
2083
|
...style
|
|
2082
2084
|
},
|
|
@@ -2085,7 +2087,7 @@ function Unbutton({
|
|
|
2085
2087
|
});
|
|
2086
2088
|
}
|
|
2087
2089
|
|
|
2088
|
-
var styles$
|
|
2090
|
+
var styles$g = {"button":"button-wsipc","button-accounts":"button-accounts-zQmBM","chevron":"chevron-obyBF","is-expanded":"is-expanded-PoCQ9"};
|
|
2089
2091
|
|
|
2090
2092
|
const LegacyAppsButton = ({
|
|
2091
2093
|
appName,
|
|
@@ -2100,8 +2102,8 @@ const LegacyAppsButton = ({
|
|
|
2100
2102
|
"aria-haspopup": "true",
|
|
2101
2103
|
"aria-label": `App switcher: ${appName}`,
|
|
2102
2104
|
onClick: onClick,
|
|
2103
|
-
className: classnames(styles$
|
|
2104
|
-
[styles$
|
|
2105
|
+
className: classnames(styles$g.button, {
|
|
2106
|
+
[styles$g["is-expanded"]]: expanded
|
|
2105
2107
|
}),
|
|
2106
2108
|
type: "button",
|
|
2107
2109
|
style: style,
|
|
@@ -2110,7 +2112,7 @@ const LegacyAppsButton = ({
|
|
|
2110
2112
|
appName: "Accounts",
|
|
2111
2113
|
colors: colors
|
|
2112
2114
|
}), /*#__PURE__*/React__default.createElement("div", {
|
|
2113
|
-
className: styles$
|
|
2115
|
+
className: styles$g["button-accounts"]
|
|
2114
2116
|
}, "Account settings"))) : (/*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
2115
2117
|
appName: appName.replace(/[\s-]/, "")
|
|
2116
2118
|
}), /*#__PURE__*/React__default.createElement(MonoAppName, {
|
|
@@ -2118,7 +2120,7 @@ const LegacyAppsButton = ({
|
|
|
2118
2120
|
key: "mono-app-name",
|
|
2119
2121
|
size: 24
|
|
2120
2122
|
}))), /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, {
|
|
2121
|
-
className: styles$
|
|
2123
|
+
className: styles$g.chevron,
|
|
2122
2124
|
key: "disclosure-chevron-icon"
|
|
2123
2125
|
}));
|
|
2124
2126
|
};
|
|
@@ -2251,7 +2253,7 @@ class Outsider extends React__default.Component {
|
|
|
2251
2253
|
}
|
|
2252
2254
|
}
|
|
2253
2255
|
|
|
2254
|
-
var styles$
|
|
2256
|
+
var styles$f = {"popup-root":"popup-root-SeL5u","popup":"popup-nQXYW","is-visible":"is-visible-3DAUP"};
|
|
2255
2257
|
|
|
2256
2258
|
const Popup = ({
|
|
2257
2259
|
className,
|
|
@@ -2259,8 +2261,8 @@ const Popup = ({
|
|
|
2259
2261
|
isVisible,
|
|
2260
2262
|
...nativeProps
|
|
2261
2263
|
}) => (/*#__PURE__*/React__default.createElement(C, {
|
|
2262
|
-
className: classnames(styles$
|
|
2263
|
-
[styles$
|
|
2264
|
+
className: classnames(styles$f.popup, {
|
|
2265
|
+
[styles$f["is-visible"]]: isVisible
|
|
2264
2266
|
}, className),
|
|
2265
2267
|
...nativeProps
|
|
2266
2268
|
}));
|
|
@@ -2268,11 +2270,11 @@ const Popup = ({
|
|
|
2268
2270
|
const PopupRoot = ({
|
|
2269
2271
|
...nativeProps
|
|
2270
2272
|
}) => (/*#__PURE__*/React__default.createElement("div", {
|
|
2271
|
-
className: styles$
|
|
2273
|
+
className: styles$f["popup-root"],
|
|
2272
2274
|
...nativeProps
|
|
2273
2275
|
}));
|
|
2274
2276
|
|
|
2275
|
-
var styles$
|
|
2277
|
+
var styles$e = {"popup":"popup-bVhSa"};
|
|
2276
2278
|
|
|
2277
2279
|
const LegacyAppsMenu = ({
|
|
2278
2280
|
appName,
|
|
@@ -2310,7 +2312,7 @@ const LegacyAppsMenu = ({
|
|
|
2310
2312
|
cleanup: !visible,
|
|
2311
2313
|
component: Outsider,
|
|
2312
2314
|
onOutsideClick: toggle,
|
|
2313
|
-
className: styles$
|
|
2315
|
+
className: styles$e.popup,
|
|
2314
2316
|
isVisible: visible
|
|
2315
2317
|
}, /*#__PURE__*/React__default.createElement("menu", {
|
|
2316
2318
|
ref: menuRef,
|
|
@@ -3223,27 +3225,34 @@ const useUpdateStore = () => useCallback(updates => {
|
|
|
3223
3225
|
const isMac = () => /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
3224
3226
|
const hasCommandKey = event => isMac() ? event.metaKey : event.ctrlKey;
|
|
3225
3227
|
const commandKeyLabel = () => isMac() ? "⌘" : "Ctrl+";
|
|
3228
|
+
const isSearchShortcut = event => event.key.toLowerCase() === "k" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3229
|
+
// Products opened their own search with `/` before this one replaced it.
|
|
3230
|
+
const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !event.ctrlKey && !event.metaKey && !isTypingIn(event.target);
|
|
3231
|
+
const isCopyShortcut = event => event.key.toLowerCase() === "c" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3232
|
+
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3233
|
+
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3234
|
+
const verticalStep = key => ({
|
|
3235
|
+
ArrowDown: 1,
|
|
3236
|
+
ArrowUp: -1
|
|
3237
|
+
})[key];
|
|
3238
|
+
const horizontalStep = key => ({
|
|
3239
|
+
ArrowLeft: -1,
|
|
3240
|
+
ArrowRight: 1
|
|
3241
|
+
})[key];
|
|
3226
3242
|
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
} catch {
|
|
3239
|
-
// Storage can be full or blocked; search still works without it.
|
|
3240
|
-
}
|
|
3243
|
+
function SearchButton({
|
|
3244
|
+
onClick
|
|
3245
|
+
}) {
|
|
3246
|
+
return /*#__PURE__*/React__default.createElement("button", {
|
|
3247
|
+
"aria-keyshortcuts": `${isMac() ? "Meta+K" : "Control+K"} /`,
|
|
3248
|
+
"aria-label": "Search",
|
|
3249
|
+
onClick: onClick,
|
|
3250
|
+
type: "button"
|
|
3251
|
+
}, /*#__PURE__*/React__default.createElement(Spyglass, {
|
|
3252
|
+
fill: "currentColor"
|
|
3253
|
+
}));
|
|
3241
3254
|
}
|
|
3242
3255
|
|
|
3243
|
-
var styles$c = {"visuallyHidden":"visuallyHidden-C28ju"};
|
|
3244
|
-
|
|
3245
|
-
var styles$b = {"tab":"tab-lQLDx"};
|
|
3246
|
-
|
|
3247
3256
|
// useId isn't available on React 17, which our peer range still supports.
|
|
3248
3257
|
let instanceCount$1 = 0;
|
|
3249
3258
|
function useInstanceId(prefix) {
|
|
@@ -3251,173 +3260,33 @@ function useInstanceId(prefix) {
|
|
|
3251
3260
|
return id;
|
|
3252
3261
|
}
|
|
3253
3262
|
|
|
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
|
-
});
|
|
3263
|
+
// The versions home-mobile pins, so web and mobile read the same response
|
|
3264
|
+
// shapes. Some endpoints only exist from a version on: Registrations' me,
|
|
3265
|
+
// product_search_results, and pcrl_lookups need 2025-05-01.
|
|
3266
|
+
const API_VERSIONS = {
|
|
3267
|
+
calendar: "2022-07-07",
|
|
3268
|
+
"check-ins": "2025-05-28",
|
|
3269
|
+
giving: "2019-10-18",
|
|
3270
|
+
groups: "2023-07-10",
|
|
3271
|
+
home: "2018-08-01",
|
|
3272
|
+
people: "2023-03-21",
|
|
3273
|
+
publishing: "2024-03-25",
|
|
3274
|
+
registrations: "2025-05-01",
|
|
3275
|
+
services: "2018-11-01"
|
|
3276
|
+
};
|
|
3277
|
+
// For a `/~api/{product}/v2/...` URL, the header that pins its product's
|
|
3278
|
+
// version.
|
|
3279
|
+
function apiVersionHeaders(url) {
|
|
3280
|
+
const [,, product] = new URL(url).pathname.split("/");
|
|
3281
|
+
const version = API_VERSIONS[product];
|
|
3282
|
+
if (version) {
|
|
3283
|
+
return {
|
|
3284
|
+
"X-PCO-API-Version": version
|
|
3285
|
+
};
|
|
3286
|
+
} else {
|
|
3287
|
+
return {};
|
|
3382
3288
|
}
|
|
3383
3289
|
}
|
|
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
3290
|
|
|
3422
3291
|
const CACHE_TTL_MS = 60000;
|
|
3423
3292
|
const MAX_ENTRIES$1 = 50;
|
|
@@ -3434,7 +3303,9 @@ function cachedRequest(url) {
|
|
|
3434
3303
|
}
|
|
3435
3304
|
}
|
|
3436
3305
|
function fetchIntoCache(url) {
|
|
3437
|
-
const promise = apiRequest(defaultFetch, url
|
|
3306
|
+
const promise = apiRequest(defaultFetch, url, {
|
|
3307
|
+
headers: apiVersionHeaders(url)
|
|
3308
|
+
}).then(({
|
|
3438
3309
|
json,
|
|
3439
3310
|
ok
|
|
3440
3311
|
}) => {
|
|
@@ -3490,44 +3361,52 @@ function useCachedRequest(url) {
|
|
|
3490
3361
|
};
|
|
3491
3362
|
}
|
|
3492
3363
|
}
|
|
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"
|
|
3364
|
+
// One request per URL, for callers that fan out to a varying number of
|
|
3365
|
+
// endpoints. Unlike `useCachedRequest`, a URL that hasn't settled yet has no
|
|
3366
|
+
// earlier data to fall back on.
|
|
3367
|
+
function useCachedRequests(urls) {
|
|
3368
|
+
const [settled, setSettled] = React__default.useState({});
|
|
3369
|
+
// Keyed by value because each settle re-renders the caller, and a caller
|
|
3370
|
+
// that builds a fresh `urls` array on render would otherwise loop forever.
|
|
3371
|
+
const key = urls.join("\n");
|
|
3372
|
+
React__default.useEffect(() => {
|
|
3373
|
+
let current = true;
|
|
3374
|
+
const settle = result => current && setSettled(prev => ({
|
|
3375
|
+
...prev,
|
|
3376
|
+
[result.url]: result
|
|
3377
|
+
}));
|
|
3378
|
+
for (const url of key.split("\n").filter(Boolean)) {
|
|
3379
|
+
cachedRequest(url).then(data => settle({
|
|
3380
|
+
data,
|
|
3381
|
+
url
|
|
3382
|
+
}), () => settle({
|
|
3383
|
+
failed: true,
|
|
3384
|
+
url
|
|
3385
|
+
}));
|
|
3386
|
+
}
|
|
3387
|
+
return () => {
|
|
3388
|
+
current = false;
|
|
3528
3389
|
};
|
|
3529
|
-
}
|
|
3390
|
+
}, [key]);
|
|
3391
|
+
return urls.map(url => {
|
|
3392
|
+
const result = settled[url];
|
|
3393
|
+
if (!result) {
|
|
3394
|
+
return {
|
|
3395
|
+
status: "loading"
|
|
3396
|
+
};
|
|
3397
|
+
} else if ("data" in result) {
|
|
3398
|
+
return {
|
|
3399
|
+
data: result.data,
|
|
3400
|
+
status: "done"
|
|
3401
|
+
};
|
|
3402
|
+
} else {
|
|
3403
|
+
return {
|
|
3404
|
+
status: "error"
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
});
|
|
3530
3408
|
}
|
|
3409
|
+
|
|
3531
3410
|
function productSearchUrl(product, query) {
|
|
3532
3411
|
const params = new URLSearchParams({
|
|
3533
3412
|
per_page: "100",
|
|
@@ -3558,14 +3437,7 @@ function groupByType({
|
|
|
3558
3437
|
};
|
|
3559
3438
|
return [...groups.values()].sort((a, b) => rank(a.type) - rank(b.type));
|
|
3560
3439
|
}
|
|
3561
|
-
|
|
3562
|
-
function useSettledProduct(product, status) {
|
|
3563
|
-
const [settledProduct, setSettledProduct] = React__default.useState();
|
|
3564
|
-
React__default.useEffect(() => {
|
|
3565
|
-
if (status === "done") setSettledProduct(product);
|
|
3566
|
-
}, [product, status]);
|
|
3567
|
-
return settledProduct;
|
|
3568
|
-
}
|
|
3440
|
+
|
|
3569
3441
|
function useDebouncedValue(value, delay) {
|
|
3570
3442
|
const [debounced, setDebounced] = React__default.useState(value);
|
|
3571
3443
|
React__default.useEffect(() => {
|
|
@@ -3575,310 +3447,50 @@ function useDebouncedValue(value, delay) {
|
|
|
3575
3447
|
return debounced;
|
|
3576
3448
|
}
|
|
3577
3449
|
|
|
3578
|
-
const
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
//
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
return undefined;
|
|
3599
|
-
}
|
|
3600
|
-
}, [copiedUrl]);
|
|
3601
|
-
const highlightIndex = React__default.useCallback(index => setHighlight(current => current.results === results && current.index === index ? current : {
|
|
3602
|
-
index,
|
|
3603
|
-
results
|
|
3604
|
-
}), [results]);
|
|
3605
|
-
const handleKeyDown = event => {
|
|
3606
|
-
const step = verticalStep(event.key);
|
|
3607
|
-
if (step && results.length > 0) {
|
|
3608
|
-
event.preventDefault();
|
|
3609
|
-
highlightIndex((highlightedIndex + step + results.length) % results.length);
|
|
3610
|
-
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
3611
|
-
event.preventDefault();
|
|
3612
|
-
open(highlighted.attributes.url, {
|
|
3613
|
-
newTab: hasCommandKey(event)
|
|
3614
|
-
});
|
|
3615
|
-
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
3616
|
-
event.preventDefault();
|
|
3617
|
-
const {
|
|
3618
|
-
url
|
|
3619
|
-
} = highlighted.attributes;
|
|
3620
|
-
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
3621
|
-
}
|
|
3622
|
-
};
|
|
3623
|
-
return {
|
|
3624
|
-
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.attributes.url),
|
|
3625
|
-
handleKeyDown,
|
|
3626
|
-
highlightIndex,
|
|
3627
|
-
highlighted,
|
|
3628
|
-
highlightedIndex: highlighted ? highlightedIndex : null
|
|
3629
|
-
};
|
|
3630
|
-
}
|
|
3631
|
-
function open(url, {
|
|
3632
|
-
newTab
|
|
3633
|
-
}) {
|
|
3634
|
-
if (newTab) {
|
|
3635
|
-
window.open(url, "_blank", "noopener");
|
|
3636
|
-
} else {
|
|
3637
|
-
window.location.assign(url);
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
const isCopyShortcut = event => event.key.toLowerCase() === "c" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3641
|
-
// With text selected, ⌘C should copy that text as usual, not the link.
|
|
3642
|
-
function hasSelectedText(element) {
|
|
3643
|
-
var _a, _b;
|
|
3644
|
-
const selectedInField = element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
3645
|
-
return selectedInField || ((_b = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "") !== "";
|
|
3646
|
-
}
|
|
3647
|
-
|
|
3648
|
-
const SEARCHABLE_PRODUCTS = [{
|
|
3649
|
-
appName: "Groups",
|
|
3650
|
-
slug: "groups"
|
|
3651
|
-
}, {
|
|
3652
|
-
appName: "People",
|
|
3653
|
-
slug: "people"
|
|
3654
|
-
}, {
|
|
3655
|
-
appName: "Services",
|
|
3656
|
-
slug: "services"
|
|
3657
|
-
}];
|
|
3658
|
-
const PERMISSION_CHECKS = {
|
|
3659
|
-
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
3660
|
-
people_permissions,
|
|
3661
|
-
site_administrator
|
|
3662
|
-
}) => site_administrator || people_permissions !== null),
|
|
3663
|
-
services: () => fetchMe("services", "can_search").then(({
|
|
3664
|
-
can_search
|
|
3665
|
-
}) => can_search)
|
|
3666
|
-
};
|
|
3667
|
-
function fetchMe(product, fields) {
|
|
3668
|
-
const params = new URLSearchParams({
|
|
3669
|
-
"fields[Person]": fields
|
|
3670
|
-
});
|
|
3671
|
-
return apiRequest(defaultFetch, `${window.location.origin}/~api/${product}/v2/me?${params}`).then(({
|
|
3672
|
-
json,
|
|
3673
|
-
ok
|
|
3674
|
-
}) => {
|
|
3675
|
-
if (!ok || json === null) throw new Error("Permission check failed");
|
|
3676
|
-
return json.data.attributes;
|
|
3677
|
-
});
|
|
3678
|
-
}
|
|
3679
|
-
// Permissions rarely change mid-session, so a successful check is kept until
|
|
3680
|
-
// the page reloads. A failed one is dropped so a remount tries again.
|
|
3681
|
-
const permissionCache = new Map();
|
|
3682
|
-
function canSearch(slug) {
|
|
3683
|
-
const check = PERMISSION_CHECKS[slug];
|
|
3684
|
-
const cached = permissionCache.get(slug);
|
|
3685
|
-
if (!check) {
|
|
3686
|
-
return Promise.resolve(true);
|
|
3687
|
-
} else if (cached) {
|
|
3688
|
-
return cached;
|
|
3689
|
-
} else {
|
|
3690
|
-
const promise = check().catch(() => {
|
|
3691
|
-
permissionCache.delete(slug);
|
|
3692
|
-
return false;
|
|
3693
|
-
});
|
|
3694
|
-
permissionCache.set(slug, promise);
|
|
3695
|
-
return promise;
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
|
-
function useSearchableProducts(apps) {
|
|
3699
|
-
// Keyed by name because providers hand over a fresh `apps` array on renders
|
|
3700
|
-
// where nothing changed.
|
|
3701
|
-
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
3702
|
-
slug
|
|
3703
|
-
}) => slug).join(",");
|
|
3704
|
-
const [settled, setSettled] = React__default.useState();
|
|
3705
|
-
React__default.useEffect(() => {
|
|
3706
|
-
let current = true;
|
|
3707
|
-
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
3708
|
-
Promise.all(accessible.map(({
|
|
3709
|
-
slug
|
|
3710
|
-
}) => canSearch(slug))).then(allowed => {
|
|
3711
|
-
if (current) {
|
|
3712
|
-
setSettled({
|
|
3713
|
-
accessibleKey,
|
|
3714
|
-
products: accessible.filter((_, index) => allowed[index])
|
|
3715
|
-
});
|
|
3716
|
-
}
|
|
3717
|
-
});
|
|
3718
|
-
return () => {
|
|
3719
|
-
current = false;
|
|
3450
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
3451
|
+
function useProductSearch(product, term) {
|
|
3452
|
+
const query = term.trim();
|
|
3453
|
+
const debouncedQuery = useDebouncedValue(query, SEARCH_DEBOUNCE_MS);
|
|
3454
|
+
const request = useCachedRequest(debouncedQuery ? productSearchUrl(product, debouncedQuery) : null);
|
|
3455
|
+
const response = "data" in request ? request.data : undefined;
|
|
3456
|
+
const settledProduct = useSettledProduct(product, request.status);
|
|
3457
|
+
// While a request loads, the previous response stays on screen, but only
|
|
3458
|
+
// if it was for this product: People results must never show under the
|
|
3459
|
+
// Services tab.
|
|
3460
|
+
const visibleResponse = request.status === "done" || settledProduct === product ? response : undefined;
|
|
3461
|
+
const groups = React__default.useMemo(() => visibleResponse && groupByType(visibleResponse), [visibleResponse]);
|
|
3462
|
+
if (!query) {
|
|
3463
|
+
return {
|
|
3464
|
+
status: "idle"
|
|
3465
|
+
};
|
|
3466
|
+
} else if (query !== debouncedQuery) {
|
|
3467
|
+
return {
|
|
3468
|
+
groups,
|
|
3469
|
+
status: "loading"
|
|
3720
3470
|
};
|
|
3721
|
-
}
|
|
3722
|
-
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey) {
|
|
3471
|
+
} else if (request.status === "done") {
|
|
3723
3472
|
return {
|
|
3724
|
-
|
|
3473
|
+
groups: groups !== null && groups !== void 0 ? groups : [],
|
|
3725
3474
|
status: "done"
|
|
3726
3475
|
};
|
|
3476
|
+
} else if (request.status === "error") {
|
|
3477
|
+
return {
|
|
3478
|
+
status: "error"
|
|
3479
|
+
};
|
|
3727
3480
|
} else {
|
|
3728
3481
|
return {
|
|
3482
|
+
groups,
|
|
3729
3483
|
status: "loading"
|
|
3730
3484
|
};
|
|
3731
3485
|
}
|
|
3732
3486
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
const isSearchShortcut = event => event.key.toLowerCase() === "k" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3738
|
-
// Products opened their own search with `/` before this one replaced it.
|
|
3739
|
-
const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !event.ctrlKey && !event.metaKey && !isTypingIn(event.target);
|
|
3740
|
-
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3741
|
-
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3742
|
-
function ProductSearch({
|
|
3743
|
-
apps
|
|
3744
|
-
}) {
|
|
3745
|
-
const searchable = useSearchableProducts(apps);
|
|
3746
|
-
if (searchable.status === "done" && searchable.products.length > 0) {
|
|
3747
|
-
return /*#__PURE__*/React__default.createElement(SearchButtonAndDialog, {
|
|
3748
|
-
products: searchable.products
|
|
3749
|
-
});
|
|
3750
|
-
} else {
|
|
3751
|
-
return null;
|
|
3752
|
-
}
|
|
3753
|
-
}
|
|
3754
|
-
function SearchButtonAndDialog({
|
|
3755
|
-
products
|
|
3756
|
-
}) {
|
|
3757
|
-
const [open, setOpen] = React__default.useState(false);
|
|
3758
|
-
React__default.useEffect(() => {
|
|
3759
|
-
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
3760
|
-
// window and stopping propagation keeps them from ever seeing it.
|
|
3761
|
-
const handleKeyDown = event => {
|
|
3762
|
-
if (isSearchShortcut(event)) {
|
|
3763
|
-
event.preventDefault();
|
|
3764
|
-
event.stopPropagation();
|
|
3765
|
-
setOpen(wasOpen => !wasOpen);
|
|
3766
|
-
} else if (isLegacySearchShortcut(event)) {
|
|
3767
|
-
event.preventDefault();
|
|
3768
|
-
event.stopPropagation();
|
|
3769
|
-
setOpen(true);
|
|
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"} /`,
|
|
3781
|
-
"aria-label": "Search",
|
|
3782
|
-
onClick: () => setOpen(true),
|
|
3783
|
-
type: "button"
|
|
3784
|
-
}, /*#__PURE__*/React__default.createElement(Spyglass, {
|
|
3785
|
-
fill: "currentColor"
|
|
3786
|
-
})), /*#__PURE__*/React__default.createElement(Modal, {
|
|
3787
|
-
"aria-label": "Search",
|
|
3788
|
-
dismissible: true,
|
|
3789
|
-
onClose: () => setOpen(false),
|
|
3790
|
-
open: open
|
|
3791
|
-
}, /*#__PURE__*/React__default.createElement(SearchPanel, {
|
|
3792
|
-
products: products
|
|
3793
|
-
})));
|
|
3794
|
-
}
|
|
3795
|
-
function SearchPanel({
|
|
3796
|
-
products
|
|
3797
|
-
}) {
|
|
3798
|
-
var _a, _b;
|
|
3799
|
-
const listboxId = useInstanceId("product-search-results");
|
|
3800
|
-
const [term, setTerm] = React__default.useState("");
|
|
3801
|
-
const [selectedSlug, setSelectedSlug] = React__default.useState(() => initialProduct(products).slug);
|
|
3802
|
-
const product = (_a = products.find(({
|
|
3803
|
-
slug
|
|
3804
|
-
}) => slug === selectedSlug)) !== null && _a !== void 0 ? _a : products[0];
|
|
3805
|
-
const hasTerm = term.trim() !== "";
|
|
3806
|
-
const search = useProductSearch(product.slug, term);
|
|
3807
|
-
const groups = "groups" in search ? search.groups : undefined;
|
|
3808
|
-
const results = React__default.useMemo(() => {
|
|
3809
|
-
var _a;
|
|
3810
|
-
return (_a = groups === null || groups === void 0 ? void 0 : groups.flatMap(group => group.results)) !== null && _a !== void 0 ? _a : [];
|
|
3811
|
-
}, [groups]);
|
|
3812
|
-
const navigation = useResultNavigation(results);
|
|
3813
|
-
const {
|
|
3814
|
-
highlightedIndex
|
|
3815
|
-
} = navigation;
|
|
3816
|
-
const inputRef = React__default.useRef(null);
|
|
3817
|
-
const [announceHighlight, setAnnounceHighlight] = React__default.useState(false);
|
|
3818
|
-
// The tabs keep focus while arrowing through results, but only the focused
|
|
3819
|
-
// input can point screen readers at the highlight, so from the tabs it's
|
|
3820
|
-
// announced instead. Typing goes back to the input, keystroke and all.
|
|
3821
|
-
const handleTabsKeyDown = event => {
|
|
3822
|
-
var _a;
|
|
3823
|
-
if (isTypingKey(event)) {
|
|
3824
|
-
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
3825
|
-
} else if (verticalStep(event.key)) {
|
|
3826
|
-
setAnnounceHighlight(true);
|
|
3827
|
-
navigation.handleKeyDown(event);
|
|
3828
|
-
} else {
|
|
3829
|
-
navigation.handleKeyDown(event);
|
|
3830
|
-
}
|
|
3831
|
-
};
|
|
3487
|
+
// The product the most recent finished request was for.
|
|
3488
|
+
function useSettledProduct(product, status) {
|
|
3489
|
+
const [settledProduct, setSettledProduct] = React__default.useState();
|
|
3832
3490
|
React__default.useEffect(() => {
|
|
3833
|
-
if (
|
|
3834
|
-
}, [
|
|
3835
|
-
return
|
|
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, {
|
|
3856
|
-
highlightedIndex: highlightedIndex,
|
|
3857
|
-
id: listboxId,
|
|
3858
|
-
label: `${product.appName} results`,
|
|
3859
|
-
onHighlight: navigation.highlightIndex,
|
|
3860
|
-
search: search,
|
|
3861
|
-
term: term
|
|
3862
|
-
})), /*#__PURE__*/React__default.createElement("div", {
|
|
3863
|
-
"aria-live": "polite",
|
|
3864
|
-
className: styles$c.visuallyHidden
|
|
3865
|
-
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.attributes.title : null), /*#__PURE__*/React__default.createElement(KeyboardHints, {
|
|
3866
|
-
copied: navigation.copied
|
|
3867
|
-
}));
|
|
3868
|
-
}
|
|
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];
|
|
3491
|
+
if (status === "done") setSettledProduct(product);
|
|
3492
|
+
}, [product, status]);
|
|
3493
|
+
return settledProduct;
|
|
3882
3494
|
}
|
|
3883
3495
|
|
|
3884
3496
|
function parseValue(element) {
|
|
@@ -3947,10 +3559,14 @@ function useRecentlyVisitableMetadata() {
|
|
|
3947
3559
|
return useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3948
3560
|
}
|
|
3949
3561
|
|
|
3950
|
-
|
|
3951
|
-
|
|
3562
|
+
// Entries match the ones home-mobile stores, so one list can sync across web
|
|
3563
|
+
// and mobile.
|
|
3564
|
+
const ENTRY_VERSION = 3;
|
|
3565
|
+
const MAX_ENTRIES = 50;
|
|
3952
3566
|
const MAX_TEXT_LENGTH = 200;
|
|
3953
3567
|
const PRODUCTS = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3568
|
+
// Login has no pages worth returning to.
|
|
3569
|
+
const APPS = PRODUCTS.filter(product => product !== "login");
|
|
3954
3570
|
function isId(value) {
|
|
3955
3571
|
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3956
3572
|
}
|
|
@@ -3959,169 +3575,1116 @@ function isPcrl(value) {
|
|
|
3959
3575
|
const segments = value.split("/");
|
|
3960
3576
|
return segments.length === 4 && PRODUCTS.some(product => product === segments[0]) && isId(segments[3]);
|
|
3961
3577
|
}
|
|
3962
|
-
function
|
|
3963
|
-
return
|
|
3578
|
+
function isApp(value) {
|
|
3579
|
+
return APPS.some(app => app === value);
|
|
3580
|
+
}
|
|
3581
|
+
// Links are built by resolving the path against its app's origin, so a stored
|
|
3582
|
+
// path must not be able to resolve anywhere else (`//evil.com`, `/\evil.com`).
|
|
3583
|
+
function isPath(value) {
|
|
3584
|
+
const origin = "https://app.invalid";
|
|
3585
|
+
try {
|
|
3586
|
+
return typeof value === "string" && value.startsWith("/") && new URL(value, origin).origin === origin;
|
|
3587
|
+
} catch {
|
|
3588
|
+
return false;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
function isNonBlankString(value) {
|
|
3592
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3593
|
+
}
|
|
3594
|
+
function isOptionalString(value) {
|
|
3595
|
+
return value === undefined || typeof value === "string";
|
|
3596
|
+
}
|
|
3597
|
+
function isRecord(value) {
|
|
3598
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3599
|
+
}
|
|
3600
|
+
function isPageMetadata(value) {
|
|
3601
|
+
return value === undefined || isRecord(value) && isOptionalString(value.details) && isOptionalString(value.type);
|
|
3602
|
+
}
|
|
3603
|
+
// The query string is part of the link but not of the page's identity, so
|
|
3604
|
+
// revisiting with other filters moves the existing entry instead of adding one.
|
|
3605
|
+
function entryKey(entry) {
|
|
3606
|
+
if (entry.type === "resource") {
|
|
3607
|
+
return `resource:${entry.pcrl}`;
|
|
3608
|
+
} else {
|
|
3609
|
+
return `page:${entry.app}:${entry.path.split("?")[0]}`;
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
function isEntry(value) {
|
|
3613
|
+
if (!isRecord(value) || value.version !== ENTRY_VERSION || typeof value.visitedAt !== "string") {
|
|
3614
|
+
return false;
|
|
3615
|
+
} else if (value.type === "resource") {
|
|
3616
|
+
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3617
|
+
} else if (value.type === "page") {
|
|
3618
|
+
return isApp(value.app) && isPath(value.path) && isNonBlankString(value.title) && isPageMetadata(value.metadata);
|
|
3619
|
+
} else {
|
|
3620
|
+
return false;
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
// Cuts on code points so a surrogate pair is never split.
|
|
3624
|
+
function clampText(value) {
|
|
3625
|
+
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3626
|
+
}
|
|
3627
|
+
function clampMetadata({
|
|
3628
|
+
details,
|
|
3629
|
+
type
|
|
3630
|
+
}) {
|
|
3631
|
+
return {
|
|
3632
|
+
...(details && {
|
|
3633
|
+
details: clampText(details)
|
|
3634
|
+
}),
|
|
3635
|
+
...(type && {
|
|
3636
|
+
type: clampText(type)
|
|
3637
|
+
})
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
function clampVisit(visit) {
|
|
3641
|
+
if (visit.type === "resource") {
|
|
3642
|
+
return visit;
|
|
3643
|
+
} else {
|
|
3644
|
+
return {
|
|
3645
|
+
...visit,
|
|
3646
|
+
...(visit.metadata && {
|
|
3647
|
+
metadata: clampMetadata(visit.metadata)
|
|
3648
|
+
}),
|
|
3649
|
+
title: clampText(visit.title.trim())
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
function toEntry(visit, visitedAt) {
|
|
3654
|
+
return {
|
|
3655
|
+
...clampVisit(visit),
|
|
3656
|
+
version: ENTRY_VERSION,
|
|
3657
|
+
visitedAt: visitedAt.toISOString()
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
3661
|
+
// a single bad entry, a list without it) rather than throwing.
|
|
3662
|
+
function parseRecentlyVisited(value) {
|
|
3663
|
+
let parsed = null;
|
|
3664
|
+
try {
|
|
3665
|
+
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
3666
|
+
} catch {
|
|
3667
|
+
parsed = null;
|
|
3668
|
+
}
|
|
3669
|
+
if (Array.isArray(parsed)) {
|
|
3670
|
+
return parsed.filter(isEntry);
|
|
3671
|
+
} else {
|
|
3672
|
+
return [];
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
function addVisit(entries, entry) {
|
|
3676
|
+
const key = entryKey(entry);
|
|
3677
|
+
return [entry, ...entries.filter(existing => entryKey(existing) !== key)].slice(0, MAX_ENTRIES);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
// The app whose URL, in this environment, has the current page's host.
|
|
3681
|
+
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
3682
|
+
// and guarantees the stored app rebuilds a working link.
|
|
3683
|
+
function currentApp() {
|
|
3684
|
+
var _a;
|
|
3685
|
+
return (_a = APPS.find(app => new URL(pcoUrl$2(app)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
3686
|
+
}
|
|
3687
|
+
function isFilled(value) {
|
|
3688
|
+
return typeof value === "string" && value !== "";
|
|
3689
|
+
}
|
|
3690
|
+
// The tag's metadata is product-supplied JSON; only its string `type` and
|
|
3691
|
+
// `details` carry over, and an entry without either has no metadata at all.
|
|
3692
|
+
function metadataFromPage(metadata) {
|
|
3693
|
+
var _a;
|
|
3694
|
+
try {
|
|
3695
|
+
const {
|
|
3696
|
+
details,
|
|
3697
|
+
type
|
|
3698
|
+
} = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) !== null && _a !== void 0 ? _a : {};
|
|
3699
|
+
if (isFilled(details) || isFilled(type)) {
|
|
3700
|
+
return {
|
|
3701
|
+
...(isFilled(details) && {
|
|
3702
|
+
details
|
|
3703
|
+
}),
|
|
3704
|
+
...(isFilled(type) && {
|
|
3705
|
+
type
|
|
3706
|
+
})
|
|
3707
|
+
};
|
|
3708
|
+
} else {
|
|
3709
|
+
return undefined;
|
|
3710
|
+
}
|
|
3711
|
+
} catch {
|
|
3712
|
+
return undefined;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
// Staff impersonating someone browse under `/O<org id>/P<person id>/…`.
|
|
3716
|
+
function withoutImpersonation(segments) {
|
|
3717
|
+
const [org = "", person = ""] = segments;
|
|
3718
|
+
if (org.startsWith("O") && isId(org.slice(1)) && person.startsWith("P") && isId(person.slice(1))) {
|
|
3719
|
+
return segments.slice(2);
|
|
3720
|
+
} else {
|
|
3721
|
+
return segments;
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
// Normalized the way home-mobile does it, so a page visited on web and on
|
|
3725
|
+
// mobile is one entry once the list syncs.
|
|
3726
|
+
function pathFromLocation({
|
|
3727
|
+
pathname,
|
|
3728
|
+
search
|
|
3729
|
+
}) {
|
|
3730
|
+
const segments = withoutImpersonation(pathname.split("/").filter(Boolean));
|
|
3731
|
+
if (segments.length === 0) {
|
|
3732
|
+
return "/";
|
|
3733
|
+
} else {
|
|
3734
|
+
return `/${segments.join("/")}${search.length > 1 ? search : ""}`;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
// pco:resource is the more specific signal, so it wins when both tags are
|
|
3738
|
+
// present. A resource that isn't a PCRL is ignored rather than recorded.
|
|
3739
|
+
function visitFromPage({
|
|
3740
|
+
metadata,
|
|
3741
|
+
resource,
|
|
3742
|
+
title
|
|
3743
|
+
}) {
|
|
3744
|
+
const app = currentApp();
|
|
3745
|
+
if (resource && isPcrl(resource)) {
|
|
3746
|
+
return {
|
|
3747
|
+
pcrl: resource,
|
|
3748
|
+
type: "resource"
|
|
3749
|
+
};
|
|
3750
|
+
} else if (title && title.trim() !== "" && app) {
|
|
3751
|
+
const pageMetadata = metadataFromPage(metadata);
|
|
3752
|
+
return {
|
|
3753
|
+
app,
|
|
3754
|
+
...(pageMetadata && {
|
|
3755
|
+
metadata: pageMetadata
|
|
3756
|
+
}),
|
|
3757
|
+
path: pathFromLocation(window.location),
|
|
3758
|
+
title,
|
|
3759
|
+
type: "page"
|
|
3760
|
+
};
|
|
3761
|
+
} else {
|
|
3762
|
+
return null;
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
// The entry Recently Visited would record for the current page, or null when
|
|
3766
|
+
// the page isn't eligible. Shared by the recorder and the debug panel so the
|
|
3767
|
+
// panel always shows what actually gets stored.
|
|
3768
|
+
function useCurrentVisit() {
|
|
3769
|
+
const {
|
|
3770
|
+
recently_visitable: title,
|
|
3771
|
+
resource
|
|
3772
|
+
} = usePcoMeta();
|
|
3773
|
+
const metadata = useRecentlyVisitableMetadata();
|
|
3774
|
+
return useMemo(() => visitFromPage({
|
|
3775
|
+
metadata,
|
|
3776
|
+
resource,
|
|
3777
|
+
title
|
|
3778
|
+
}), [metadata, resource, title]);
|
|
3779
|
+
}
|
|
3780
|
+
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
3781
|
+
// and several pass it as a number.
|
|
3782
|
+
function useCurrentPersonId() {
|
|
3783
|
+
const {
|
|
3784
|
+
userId
|
|
3785
|
+
} = useStore();
|
|
3786
|
+
return userId == null ? undefined : String(userId);
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
// Per-origin localStorage is a stopgap: each product keeps its own list until
|
|
3790
|
+
// PCA-409 moves this to storage shared across product subdomains. Only this
|
|
3791
|
+
// file should need to change when it does.
|
|
3792
|
+
function storageKey({
|
|
3793
|
+
env,
|
|
3794
|
+
personId
|
|
3795
|
+
}) {
|
|
3796
|
+
return `topbar.recently_visited.${env}.${personId}`;
|
|
3797
|
+
}
|
|
3798
|
+
// localStorage doesn't notify the tab that wrote it, so readers in this tab
|
|
3799
|
+
// learn about new visits from this counter instead.
|
|
3800
|
+
const changes = createStore({
|
|
3801
|
+
version: 0
|
|
3802
|
+
});
|
|
3803
|
+
function readRecentlyVisited(scope) {
|
|
3804
|
+
try {
|
|
3805
|
+
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)));
|
|
3806
|
+
} catch {
|
|
3807
|
+
return [];
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
function recordVisit(visit, scope) {
|
|
3811
|
+
const entry = toEntry(visit, new Date());
|
|
3812
|
+
if (isEntry(entry)) {
|
|
3813
|
+
try {
|
|
3814
|
+
localStorage.setItem(storageKey(scope), JSON.stringify(addVisit(readRecentlyVisited(scope), entry)));
|
|
3815
|
+
changes.setState({
|
|
3816
|
+
version: changes.getState().version + 1
|
|
3817
|
+
});
|
|
3818
|
+
} catch {
|
|
3819
|
+
// Storage can be full or blocked; recents are disposable.
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
function useRecentlyVisited({
|
|
3824
|
+
env,
|
|
3825
|
+
personId
|
|
3826
|
+
}) {
|
|
3827
|
+
const version = useSyncExternalStore(changes.subscribe, () => changes.getState().version);
|
|
3828
|
+
// `version` isn't read, but listing it re-reads storage after each visit.
|
|
3829
|
+
return useMemo(() => personId ? readRecentlyVisited({
|
|
3830
|
+
env,
|
|
3831
|
+
personId
|
|
3832
|
+
}) : [], [env, personId, version]);
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
const pcoUrl = pcoUrl$1;
|
|
3836
|
+
|
|
3837
|
+
// Resolves resource entries with one pcrl_lookups request per product.
|
|
3838
|
+
// pcrl_lookups reports forbidden and deleted resources the same way, so both
|
|
3839
|
+
// become `unresolved`, as does every entry in a batch whose request failed.
|
|
3840
|
+
function useHydratedRecentlyVisited(entries, env) {
|
|
3841
|
+
const batches = React__default.useMemo(() => batchByProduct(entries), [entries]);
|
|
3842
|
+
const urls = React__default.useMemo(() => batches.map(([product, pcrls]) => pcrlLookupsUrl(product, pcrls)), [batches]);
|
|
3843
|
+
const requests = useCachedRequests(urls);
|
|
3844
|
+
const lookups = new Map();
|
|
3845
|
+
batches.forEach(([, pcrls], index) => {
|
|
3846
|
+
const request = requests[index];
|
|
3847
|
+
for (const pcrl of pcrls) lookups.set(pcrl, lookupFor(pcrl, request));
|
|
3848
|
+
});
|
|
3849
|
+
return entries.map(entry => {
|
|
3850
|
+
var _a;
|
|
3851
|
+
if (entry.type === "page") {
|
|
3852
|
+
return {
|
|
3853
|
+
entry,
|
|
3854
|
+
status: "page",
|
|
3855
|
+
url: pageUrl(entry, env)
|
|
3856
|
+
};
|
|
3857
|
+
} else {
|
|
3858
|
+
const lookup = (_a = lookups.get(entry.pcrl)) !== null && _a !== void 0 ? _a : {
|
|
3859
|
+
status: "unresolved"
|
|
3860
|
+
};
|
|
3861
|
+
if (lookup.status === "resolved") {
|
|
3862
|
+
return {
|
|
3863
|
+
...lookup,
|
|
3864
|
+
entry,
|
|
3865
|
+
url: lookup.result.attributes.url
|
|
3866
|
+
};
|
|
3867
|
+
} else {
|
|
3868
|
+
return {
|
|
3869
|
+
entry,
|
|
3870
|
+
status: lookup.status
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
function lookupFor(pcrl, request) {
|
|
3877
|
+
if (request.status === "done") {
|
|
3878
|
+
const result = request.data.data.find(({
|
|
3879
|
+
attributes
|
|
3880
|
+
}) => attributes.pcrl === pcrl);
|
|
3881
|
+
return result ? {
|
|
3882
|
+
result,
|
|
3883
|
+
status: "resolved"
|
|
3884
|
+
} : {
|
|
3885
|
+
status: "unresolved"
|
|
3886
|
+
};
|
|
3887
|
+
} else if (request.status === "error") {
|
|
3888
|
+
return {
|
|
3889
|
+
status: "unresolved"
|
|
3890
|
+
};
|
|
3891
|
+
} else {
|
|
3892
|
+
return {
|
|
3893
|
+
status: "loading"
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const RECENTLY_VISITED_PAGE_SIZE = 10;
|
|
3898
|
+
// A PCRL can only be looked up through its own product's API, which may not be
|
|
3899
|
+
// the product it was visited in. Each page of entries is batched on its own,
|
|
3900
|
+
// so loading the next page leaves earlier pages' URLs, and so their cache
|
|
3901
|
+
// keys, alone. Sorting keeps them stable as a page reorders.
|
|
3902
|
+
function batchByProduct(entries) {
|
|
3903
|
+
var _a;
|
|
3904
|
+
const batches = [];
|
|
3905
|
+
for (let start = 0; start < entries.length; start += RECENTLY_VISITED_PAGE_SIZE) {
|
|
3906
|
+
const page = new Map();
|
|
3907
|
+
for (const entry of entries.slice(start, start + RECENTLY_VISITED_PAGE_SIZE)) {
|
|
3908
|
+
if (entry.type === "resource") {
|
|
3909
|
+
const [product] = entry.pcrl.split("/");
|
|
3910
|
+
page.set(product, [...((_a = page.get(product)) !== null && _a !== void 0 ? _a : []), entry.pcrl]);
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
for (const [product, pcrls] of page) {
|
|
3914
|
+
batches.push([product, [...pcrls].sort()]);
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
return batches;
|
|
3918
|
+
}
|
|
3919
|
+
function pcrlLookupsUrl(product, pcrls) {
|
|
3920
|
+
const params = new URLSearchParams({
|
|
3921
|
+
"where[pcrl]": pcrls.join(",")
|
|
3922
|
+
});
|
|
3923
|
+
return `${window.location.origin}/~api/${product}/v2/pcrl_lookups?${params}`;
|
|
3924
|
+
}
|
|
3925
|
+
// Built from the app rather than the current origin, so it stays right once
|
|
3926
|
+
// PCA-409 shares one list across product subdomains.
|
|
3927
|
+
function pageUrl(entry, env) {
|
|
3928
|
+
return new URL(entry.path, pcoUrl(env)(entry.app)).href;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
function useRecentVisits(env) {
|
|
3932
|
+
const personId = useCurrentPersonId();
|
|
3933
|
+
const entries = useRecentlyVisited({
|
|
3934
|
+
env,
|
|
3935
|
+
personId
|
|
3936
|
+
});
|
|
3937
|
+
const [count, setCount] = React__default.useState(RECENTLY_VISITED_PAGE_SIZE);
|
|
3938
|
+
const shown = React__default.useMemo(() => entries.slice(0, count), [entries, count]);
|
|
3939
|
+
const hydrated = useHydratedRecentlyVisited(shown, env);
|
|
3940
|
+
// Resources show up once they're found. Ones that can't be found anymore,
|
|
3941
|
+
// or that the person can no longer see, are left out.
|
|
3942
|
+
const visits = hydrated.filter(isShown);
|
|
3943
|
+
const loading = hydrated.some(({
|
|
3944
|
+
status
|
|
3945
|
+
}) => status === "loading");
|
|
3946
|
+
const showMore = React__default.useCallback(() => setCount(current => current + RECENTLY_VISITED_PAGE_SIZE), []);
|
|
3947
|
+
return {
|
|
3948
|
+
count,
|
|
3949
|
+
hasMore: entries.length > count,
|
|
3950
|
+
loading,
|
|
3951
|
+
showMore,
|
|
3952
|
+
visits
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
function isShown(visit) {
|
|
3956
|
+
return visit.status === "page" || visit.status === "resolved";
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
function open(url, {
|
|
3960
|
+
newTab
|
|
3961
|
+
}) {
|
|
3962
|
+
if (newTab) {
|
|
3963
|
+
window.open(url, "_blank", "noopener");
|
|
3964
|
+
} else {
|
|
3965
|
+
window.location.assign(url);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
// With text selected, ⌘C should copy that text as usual, not the link.
|
|
3969
|
+
function hasSelectedText(element) {
|
|
3970
|
+
var _a, _b;
|
|
3971
|
+
const selectedInField = element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
3972
|
+
return selectedInField || ((_b = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "") !== "";
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
const COPIED_FEEDBACK_MS = 1500;
|
|
3976
|
+
// `copiedUrl` stays set briefly after a copy so the UI can confirm it.
|
|
3977
|
+
function useCopyLink() {
|
|
3978
|
+
const [copiedUrl, setCopiedUrl] = React__default.useState(null);
|
|
3979
|
+
React__default.useEffect(() => {
|
|
3980
|
+
if (copiedUrl) {
|
|
3981
|
+
const timeout = setTimeout(() => setCopiedUrl(null), COPIED_FEEDBACK_MS);
|
|
3982
|
+
return () => clearTimeout(timeout);
|
|
3983
|
+
} else {
|
|
3984
|
+
return undefined;
|
|
3985
|
+
}
|
|
3986
|
+
}, [copiedUrl]);
|
|
3987
|
+
const copy = React__default.useCallback(url => {
|
|
3988
|
+
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
3989
|
+
}, []);
|
|
3990
|
+
return {
|
|
3991
|
+
copiedUrl,
|
|
3992
|
+
copy
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
// Remembering which list the index belongs to puts the highlight back on the
|
|
3997
|
+
// first item whenever a new list replaces it, without an effect to reset it.
|
|
3998
|
+
// A list that grows or fills in under the same `list` keeps its highlight.
|
|
3999
|
+
function useResultNavigation(items, list) {
|
|
4000
|
+
const [highlight, setHighlight] = React__default.useState({
|
|
4001
|
+
index: 0,
|
|
4002
|
+
list
|
|
4003
|
+
});
|
|
4004
|
+
const highlightedIndex = highlight.list === list ? highlight.index : 0;
|
|
4005
|
+
const highlighted = items[highlightedIndex];
|
|
4006
|
+
const {
|
|
4007
|
+
copiedUrl,
|
|
4008
|
+
copy
|
|
4009
|
+
} = useCopyLink();
|
|
4010
|
+
const highlightIndex = React__default.useCallback(index => setHighlight(current => current.list === list && current.index === index ? current : {
|
|
4011
|
+
index,
|
|
4012
|
+
list
|
|
4013
|
+
}), [list]);
|
|
4014
|
+
const handleKeyDown = event => {
|
|
4015
|
+
const step = verticalStep(event.key);
|
|
4016
|
+
if (step && items.length > 0) {
|
|
4017
|
+
event.preventDefault();
|
|
4018
|
+
highlightIndex((highlightedIndex + step + items.length) % items.length);
|
|
4019
|
+
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
4020
|
+
event.preventDefault();
|
|
4021
|
+
open(highlighted.url, {
|
|
4022
|
+
newTab: hasCommandKey(event)
|
|
4023
|
+
});
|
|
4024
|
+
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
4025
|
+
event.preventDefault();
|
|
4026
|
+
copy(highlighted.url);
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
return {
|
|
4030
|
+
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.url),
|
|
4031
|
+
handleKeyDown,
|
|
4032
|
+
highlightIndex,
|
|
4033
|
+
highlighted,
|
|
4034
|
+
highlightedIndex: highlighted ? highlightedIndex : null
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
function groupSections(groups = []) {
|
|
4039
|
+
return groups.map(({
|
|
4040
|
+
results,
|
|
4041
|
+
type
|
|
4042
|
+
}) => ({
|
|
4043
|
+
heading: type,
|
|
4044
|
+
items: results.map(resultItem),
|
|
4045
|
+
key: type
|
|
4046
|
+
}));
|
|
4047
|
+
}
|
|
4048
|
+
function resultItem({
|
|
4049
|
+
attributes,
|
|
4050
|
+
id
|
|
4051
|
+
}) {
|
|
4052
|
+
return {
|
|
4053
|
+
key: id,
|
|
4054
|
+
subtitle: summarize(attributes.metadata),
|
|
4055
|
+
title: attributes.title,
|
|
4056
|
+
url: attributes.url
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
function visitItem(visit) {
|
|
4060
|
+
return {
|
|
4061
|
+
key: entryKey(visit.entry),
|
|
4062
|
+
...describeVisit(visit)
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
function summarize(metadata) {
|
|
4066
|
+
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
4067
|
+
}
|
|
4068
|
+
// Unlike search results, recents come from every product, so each one names
|
|
4069
|
+
// its product and type.
|
|
4070
|
+
function describeVisit(visit) {
|
|
4071
|
+
var _a, _b;
|
|
4072
|
+
const subtitle = (...parts) => parts.filter(Boolean).join(" · ");
|
|
4073
|
+
if (visit.status === "page") {
|
|
4074
|
+
const {
|
|
4075
|
+
app,
|
|
4076
|
+
metadata,
|
|
4077
|
+
title
|
|
4078
|
+
} = visit.entry;
|
|
4079
|
+
return {
|
|
4080
|
+
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 : ""),
|
|
4081
|
+
title,
|
|
4082
|
+
url: visit.url
|
|
4083
|
+
};
|
|
4084
|
+
} else {
|
|
4085
|
+
const {
|
|
4086
|
+
metadata,
|
|
4087
|
+
product,
|
|
4088
|
+
title,
|
|
4089
|
+
type
|
|
4090
|
+
} = visit.result.attributes;
|
|
4091
|
+
return {
|
|
4092
|
+
subtitle: subtitle(productName(product), type, summarize(metadata)),
|
|
4093
|
+
title,
|
|
4094
|
+
url: visit.url
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
// check-ins → Check-Ins
|
|
4099
|
+
function productName(slug) {
|
|
4100
|
+
return slug.split("-").map(capitalize).join("-");
|
|
4101
|
+
}
|
|
4102
|
+
function capitalize(word) {
|
|
4103
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
const STORAGE_KEY = "topbar.product_search.last_product";
|
|
4107
|
+
function readLastProduct() {
|
|
4108
|
+
try {
|
|
4109
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
4110
|
+
} catch {
|
|
4111
|
+
return null;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
function saveLastProduct(slug) {
|
|
4115
|
+
try {
|
|
4116
|
+
localStorage.setItem(STORAGE_KEY, slug);
|
|
4117
|
+
} catch {
|
|
4118
|
+
// Storage can be full or blocked; search still works without it.
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
function initialProduct(products) {
|
|
4122
|
+
var _a, _b, _c;
|
|
4123
|
+
const lastProduct = readLastProduct();
|
|
4124
|
+
const bySlug = slug => products.find(product => product.slug === slug);
|
|
4125
|
+
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];
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
function KeyboardHints({
|
|
4129
|
+
copied
|
|
4130
|
+
}) {
|
|
4131
|
+
const command = commandKeyLabel();
|
|
4132
|
+
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", {
|
|
4133
|
+
role: "status"
|
|
4134
|
+
}, copied ? "Copied!" : "Copy link"), " \u00B7 ", command, "K Open search");
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
var styles$d = {"tab":"tab-dz8ki"};
|
|
4138
|
+
|
|
4139
|
+
function ProductTabs({
|
|
4140
|
+
children,
|
|
4141
|
+
onKeyDown,
|
|
4142
|
+
onSelect,
|
|
4143
|
+
products,
|
|
4144
|
+
selected
|
|
4145
|
+
}) {
|
|
4146
|
+
const id = useInstanceId("product-tabs");
|
|
4147
|
+
const tabId = slug => `${id}-tab-${slug}`;
|
|
4148
|
+
const tabRefs = React__default.useRef(new Map());
|
|
4149
|
+
const handleKeyDown = event => {
|
|
4150
|
+
var _a;
|
|
4151
|
+
const offset = horizontalStep(event.key);
|
|
4152
|
+
if (offset) {
|
|
4153
|
+
event.preventDefault();
|
|
4154
|
+
const index = products.indexOf(selected);
|
|
4155
|
+
const next = products[(index + offset + products.length) % products.length];
|
|
4156
|
+
onSelect(next.slug);
|
|
4157
|
+
(_a = tabRefs.current.get(next.slug)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4158
|
+
} else {
|
|
4159
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
4160
|
+
}
|
|
4161
|
+
};
|
|
4162
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
4163
|
+
"aria-label": "Product",
|
|
4164
|
+
onKeyDown: handleKeyDown,
|
|
4165
|
+
role: "tablist"
|
|
4166
|
+
}, products.map(product => (/*#__PURE__*/React__default.createElement("button", {
|
|
4167
|
+
"aria-controls": `${id}-panel`,
|
|
4168
|
+
"aria-selected": product === selected,
|
|
4169
|
+
className: styles$d.tab,
|
|
4170
|
+
id: tabId(product.slug),
|
|
4171
|
+
key: product.slug,
|
|
4172
|
+
onClick: () => onSelect(product.slug),
|
|
4173
|
+
ref: element => {
|
|
4174
|
+
if (element) {
|
|
4175
|
+
tabRefs.current.set(product.slug, element);
|
|
4176
|
+
} else {
|
|
4177
|
+
tabRefs.current.delete(product.slug);
|
|
4178
|
+
}
|
|
4179
|
+
},
|
|
4180
|
+
role: "tab",
|
|
4181
|
+
tabIndex: product === selected ? 0 : -1,
|
|
4182
|
+
type: "button"
|
|
4183
|
+
}, product.appName)))), /*#__PURE__*/React__default.createElement("div", {
|
|
4184
|
+
"aria-labelledby": tabId(selected.slug),
|
|
4185
|
+
id: `${id}-panel`,
|
|
4186
|
+
role: "tabpanel"
|
|
4187
|
+
}, children));
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
function EndOfResultsObserver({
|
|
4191
|
+
onVisible
|
|
4192
|
+
}) {
|
|
4193
|
+
const ref = React__default.useRef(null);
|
|
4194
|
+
React__default.useEffect(() => {
|
|
4195
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
4196
|
+
if (entry.isIntersecting) onVisible();
|
|
4197
|
+
});
|
|
4198
|
+
if (ref.current) observer.observe(ref.current);
|
|
4199
|
+
return () => observer.disconnect();
|
|
4200
|
+
}, [onVisible]);
|
|
4201
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
4202
|
+
ref: ref
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
4207
|
+
|
|
4208
|
+
var styles$c = {"result":"result-Thq2C"};
|
|
4209
|
+
|
|
4210
|
+
function SearchResult({
|
|
4211
|
+
highlighted,
|
|
4212
|
+
id,
|
|
4213
|
+
onHighlight,
|
|
4214
|
+
subtitle,
|
|
4215
|
+
title,
|
|
4216
|
+
url
|
|
4217
|
+
}) {
|
|
4218
|
+
const ref = React__default.useRef(null);
|
|
4219
|
+
React__default.useEffect(() => {
|
|
4220
|
+
var _a;
|
|
4221
|
+
if (highlighted) (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
4222
|
+
block: "nearest"
|
|
4223
|
+
});
|
|
4224
|
+
}, [highlighted]);
|
|
4225
|
+
// Focus stays in the search input, which points at the highlighted result
|
|
4226
|
+
// with aria-activedescendant, so results are kept out of the tab order.
|
|
4227
|
+
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
4228
|
+
// pointer, and that shouldn't steal the highlight.
|
|
4229
|
+
return /*#__PURE__*/React__default.createElement("a", {
|
|
4230
|
+
"aria-selected": highlighted,
|
|
4231
|
+
className: styles$c.result,
|
|
4232
|
+
href: url,
|
|
4233
|
+
id: id,
|
|
4234
|
+
onMouseMove: onHighlight,
|
|
4235
|
+
ref: ref,
|
|
4236
|
+
role: "option",
|
|
4237
|
+
tabIndex: -1
|
|
4238
|
+
}, /*#__PURE__*/React__default.createElement("div", null, title), /*#__PURE__*/React__default.createElement("div", null, subtitle));
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4241
|
+
// Sections with a heading render as labelled groups; ones without render
|
|
4242
|
+
// their items straight into the listbox. Indexes run across every section.
|
|
4243
|
+
function ResultListbox({
|
|
4244
|
+
busy,
|
|
4245
|
+
highlightedIndex,
|
|
4246
|
+
id,
|
|
4247
|
+
label,
|
|
4248
|
+
labelledBy,
|
|
4249
|
+
onHighlight,
|
|
4250
|
+
sections
|
|
4251
|
+
}) {
|
|
4252
|
+
let index = 0;
|
|
4253
|
+
const renderItem = item => {
|
|
4254
|
+
const itemIndex = index++;
|
|
4255
|
+
return /*#__PURE__*/React__default.createElement(SearchResult, {
|
|
4256
|
+
highlighted: itemIndex === highlightedIndex,
|
|
4257
|
+
id: resultId(id, itemIndex),
|
|
4258
|
+
key: item.key,
|
|
4259
|
+
onHighlight: () => onHighlight(itemIndex),
|
|
4260
|
+
subtitle: item.subtitle,
|
|
4261
|
+
title: item.title,
|
|
4262
|
+
url: item.url
|
|
4263
|
+
});
|
|
4264
|
+
};
|
|
4265
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
4266
|
+
"aria-busy": busy,
|
|
4267
|
+
"aria-label": label,
|
|
4268
|
+
"aria-labelledby": labelledBy,
|
|
4269
|
+
id: id,
|
|
4270
|
+
role: "listbox"
|
|
4271
|
+
}, sections.map((section, sectionIndex) => {
|
|
4272
|
+
if (section.heading === undefined) {
|
|
4273
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, {
|
|
4274
|
+
key: section.key
|
|
4275
|
+
}, section.items.map(renderItem));
|
|
4276
|
+
}
|
|
4277
|
+
const headingId = `${id}-group-${sectionIndex}`;
|
|
4278
|
+
return /*#__PURE__*/React__default.createElement("div", {
|
|
4279
|
+
"aria-labelledby": headingId,
|
|
4280
|
+
key: section.key,
|
|
4281
|
+
role: "group"
|
|
4282
|
+
}, /*#__PURE__*/React__default.createElement("div", {
|
|
4283
|
+
id: headingId
|
|
4284
|
+
}, section.heading), section.items.map(renderItem));
|
|
4285
|
+
}));
|
|
4286
|
+
}
|
|
4287
|
+
|
|
4288
|
+
function RecentlyVisited({
|
|
4289
|
+
count,
|
|
4290
|
+
hasMore,
|
|
4291
|
+
items,
|
|
4292
|
+
loading,
|
|
4293
|
+
showMore,
|
|
4294
|
+
...navigation
|
|
4295
|
+
}) {
|
|
4296
|
+
const headingId = `${navigation.id}-heading`;
|
|
4297
|
+
if (items.length === 0 && !hasMore && !loading) {
|
|
4298
|
+
return /*#__PURE__*/React__default.createElement("p", null, "Pages you visit will show up here.");
|
|
4299
|
+
} else {
|
|
4300
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("div", {
|
|
4301
|
+
id: headingId
|
|
4302
|
+
}, "Recently visited"), /*#__PURE__*/React__default.createElement(ResultListbox, {
|
|
4303
|
+
labelledBy: headingId,
|
|
4304
|
+
sections: [{
|
|
4305
|
+
items,
|
|
4306
|
+
key: "recents"
|
|
4307
|
+
}],
|
|
4308
|
+
...navigation
|
|
4309
|
+
}), hasMore && /*#__PURE__*/React__default.createElement(EndOfResultsObserver, {
|
|
4310
|
+
key: count,
|
|
4311
|
+
onVisible: showMore
|
|
4312
|
+
}));
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4316
|
+
// A combobox: focus stays here while aria-activedescendant points screen
|
|
4317
|
+
// readers at the highlighted result in the listbox it controls.
|
|
4318
|
+
function SearchInput({
|
|
4319
|
+
expanded,
|
|
4320
|
+
highlightedIndex,
|
|
4321
|
+
inputRef,
|
|
4322
|
+
listboxId,
|
|
4323
|
+
onChange,
|
|
4324
|
+
onFocus,
|
|
4325
|
+
onKeyDown,
|
|
4326
|
+
value
|
|
4327
|
+
}) {
|
|
4328
|
+
return /*#__PURE__*/React__default.createElement("input", {
|
|
4329
|
+
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
4330
|
+
"aria-autocomplete": "list",
|
|
4331
|
+
"aria-controls": expanded ? listboxId : undefined,
|
|
4332
|
+
"aria-expanded": expanded,
|
|
4333
|
+
"aria-label": "Search",
|
|
4334
|
+
autoFocus: true,
|
|
4335
|
+
onChange: event => onChange(event.target.value),
|
|
4336
|
+
onFocus: onFocus,
|
|
4337
|
+
onKeyDown: onKeyDown,
|
|
4338
|
+
placeholder: "Search",
|
|
4339
|
+
ref: inputRef,
|
|
4340
|
+
role: "combobox",
|
|
4341
|
+
type: "search",
|
|
4342
|
+
value: value
|
|
4343
|
+
});
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
var styles$b = {"visuallyHidden":"visuallyHidden-S9VG5"};
|
|
4347
|
+
|
|
4348
|
+
function SearchResults({
|
|
4349
|
+
label,
|
|
4350
|
+
sections,
|
|
4351
|
+
status,
|
|
4352
|
+
term,
|
|
4353
|
+
...navigation
|
|
4354
|
+
}) {
|
|
4355
|
+
switch (status) {
|
|
4356
|
+
case "idle":
|
|
4357
|
+
return null;
|
|
4358
|
+
case "loading":
|
|
4359
|
+
if (sections.length > 0) {
|
|
4360
|
+
return /*#__PURE__*/React__default.createElement(ResultListbox, {
|
|
4361
|
+
busy: true,
|
|
4362
|
+
label: label,
|
|
4363
|
+
sections: sections,
|
|
4364
|
+
...navigation
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4368
|
+
role: "status"
|
|
4369
|
+
}, "Searching\u2026");
|
|
4370
|
+
case "error":
|
|
4371
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4372
|
+
role: "alert"
|
|
4373
|
+
}, "Search isn\u2019t working right now. Try again.");
|
|
4374
|
+
case "done":
|
|
4375
|
+
if (sections.length === 0) {
|
|
4376
|
+
return /*#__PURE__*/React__default.createElement("p", {
|
|
4377
|
+
role: "status"
|
|
4378
|
+
}, "No results for \u201C", term.trim(), "\u201D");
|
|
4379
|
+
}
|
|
4380
|
+
return /*#__PURE__*/React__default.createElement(ResultListbox, {
|
|
4381
|
+
busy: false,
|
|
4382
|
+
label: label,
|
|
4383
|
+
sections: sections,
|
|
4384
|
+
...navigation
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
// Keys the recents list for navigation, so the highlight
|
|
4390
|
+
// stays put as more recents load and as their lookups answer.
|
|
4391
|
+
const RECENTS = "recents";
|
|
4392
|
+
function SearchPanel({
|
|
4393
|
+
env,
|
|
4394
|
+
products
|
|
4395
|
+
}) {
|
|
4396
|
+
var _a, _b;
|
|
4397
|
+
const listboxId = useInstanceId("product-search-results");
|
|
4398
|
+
const [term, setTerm] = React__default.useState("");
|
|
4399
|
+
const [selectedSlug, setSelectedSlug] = React__default.useState(() => initialProduct(products).slug);
|
|
4400
|
+
const product = (_a = products.find(({
|
|
4401
|
+
slug
|
|
4402
|
+
}) => slug === selectedSlug)) !== null && _a !== void 0 ? _a : products[0];
|
|
4403
|
+
const hasTerm = term.trim() !== "";
|
|
4404
|
+
const search = useProductSearch(product.slug, term);
|
|
4405
|
+
const groups = "groups" in search ? search.groups : undefined;
|
|
4406
|
+
const sections = React__default.useMemo(() => groupSections(groups), [groups]);
|
|
4407
|
+
const {
|
|
4408
|
+
visits,
|
|
4409
|
+
...recents
|
|
4410
|
+
} = useRecentVisits(env);
|
|
4411
|
+
const recentItems = visits.map(visitItem);
|
|
4412
|
+
const items = hasTerm ? sections.flatMap(section => section.items) : recentItems;
|
|
4413
|
+
const navigation = useResultNavigation(items, hasTerm ? sections : RECENTS);
|
|
4414
|
+
const {
|
|
4415
|
+
highlightedIndex
|
|
4416
|
+
} = navigation;
|
|
4417
|
+
const inputRef = React__default.useRef(null);
|
|
4418
|
+
const [announceHighlight, setAnnounceHighlight] = React__default.useState(false);
|
|
4419
|
+
// The tabs keep focus while arrowing through results, but only the focused
|
|
4420
|
+
// input can point screen readers at the highlight, so from the tabs it's
|
|
4421
|
+
// announced instead. Typing goes back to the input, keystroke and all.
|
|
4422
|
+
const handleTabsKeyDown = event => {
|
|
4423
|
+
var _a;
|
|
4424
|
+
if (isTypingKey(event)) {
|
|
4425
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4426
|
+
} else if (verticalStep(event.key)) {
|
|
4427
|
+
setAnnounceHighlight(true);
|
|
4428
|
+
navigation.handleKeyDown(event);
|
|
4429
|
+
} else {
|
|
4430
|
+
navigation.handleKeyDown(event);
|
|
4431
|
+
}
|
|
4432
|
+
};
|
|
4433
|
+
React__default.useEffect(() => {
|
|
4434
|
+
if (hasTerm) saveLastProduct(product.slug);
|
|
4435
|
+
}, [hasTerm, product.slug]);
|
|
4436
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(SearchInput, {
|
|
4437
|
+
expanded: items.length > 0,
|
|
4438
|
+
highlightedIndex: highlightedIndex,
|
|
4439
|
+
inputRef: inputRef,
|
|
4440
|
+
listboxId: listboxId,
|
|
4441
|
+
onChange: setTerm,
|
|
4442
|
+
onFocus: () => setAnnounceHighlight(false),
|
|
4443
|
+
onKeyDown: navigation.handleKeyDown,
|
|
4444
|
+
value: term
|
|
4445
|
+
}), /*#__PURE__*/React__default.createElement(ProductTabs, {
|
|
4446
|
+
onKeyDown: handleTabsKeyDown,
|
|
4447
|
+
onSelect: setSelectedSlug,
|
|
4448
|
+
products: products,
|
|
4449
|
+
selected: product
|
|
4450
|
+
}, hasTerm ? (/*#__PURE__*/React__default.createElement(SearchResults, {
|
|
4451
|
+
highlightedIndex: highlightedIndex,
|
|
4452
|
+
id: listboxId,
|
|
4453
|
+
label: `${product.appName} results`,
|
|
4454
|
+
onHighlight: navigation.highlightIndex,
|
|
4455
|
+
sections: sections,
|
|
4456
|
+
status: search.status,
|
|
4457
|
+
term: term
|
|
4458
|
+
})) : (/*#__PURE__*/React__default.createElement(RecentlyVisited, {
|
|
4459
|
+
highlightedIndex: highlightedIndex,
|
|
4460
|
+
id: listboxId,
|
|
4461
|
+
items: recentItems,
|
|
4462
|
+
onHighlight: navigation.highlightIndex,
|
|
4463
|
+
...recents
|
|
4464
|
+
}))), /*#__PURE__*/React__default.createElement("div", {
|
|
4465
|
+
"aria-live": "polite",
|
|
4466
|
+
className: styles$b.visuallyHidden
|
|
4467
|
+
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.title : null), /*#__PURE__*/React__default.createElement(KeyboardHints, {
|
|
4468
|
+
copied: navigation.copied
|
|
4469
|
+
}));
|
|
3964
4470
|
}
|
|
3965
|
-
|
|
3966
|
-
|
|
4471
|
+
|
|
4472
|
+
// Products open modals from many libraries, not all of which mark themselves
|
|
4473
|
+
// with roles, so a modal is recognized by what it does: cover the page.
|
|
4474
|
+
function isModalOpen() {
|
|
4475
|
+
const {
|
|
4476
|
+
clientHeight,
|
|
4477
|
+
clientWidth
|
|
4478
|
+
} = document.documentElement;
|
|
4479
|
+
const element = document.elementFromPoint(clientWidth / 2, clientHeight / 2);
|
|
4480
|
+
return element !== null && (isInNativeDialog(element) || hasBackdrop(element, clientWidth, clientHeight));
|
|
3967
4481
|
}
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
return `page:${entry.product}:${entry.path}`;
|
|
3973
|
-
}
|
|
4482
|
+
// A native modal's backdrop is drawn by the browser rather than being an
|
|
4483
|
+
// element; hit-testing it lands on the dialog itself.
|
|
4484
|
+
function isInNativeDialog(element) {
|
|
4485
|
+
return element.closest("dialog[open]") !== null;
|
|
3974
4486
|
}
|
|
3975
|
-
function
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3980
|
-
} else if (value.type === "page") {
|
|
3981
|
-
return isNonBlankString(value.product) && isNonBlankString(value.title) && isNonBlankString(value.path) && typeof value.details === "string";
|
|
3982
|
-
} else {
|
|
3983
|
-
return false;
|
|
4487
|
+
function hasBackdrop(element, width, height) {
|
|
4488
|
+
let current = element;
|
|
4489
|
+
while (current && !isBackdrop(current, width, height)) {
|
|
4490
|
+
current = current.parentElement;
|
|
3984
4491
|
}
|
|
4492
|
+
return current !== null;
|
|
3985
4493
|
}
|
|
3986
|
-
//
|
|
3987
|
-
|
|
3988
|
-
|
|
4494
|
+
// Some backdrops start below the topbar rather than at the top of the screen,
|
|
4495
|
+
// so the layer only has to span the width and reach from the center to the
|
|
4496
|
+
// bottom. Its own box has to reach the center: a bar along the bottom can
|
|
4497
|
+
// hold a panel that pops up over the center, like Services' media player.
|
|
4498
|
+
// react-aria leaves a closed modal's backdrop up while it fades out, search's
|
|
4499
|
+
// own included, so one that's on its way out doesn't count.
|
|
4500
|
+
function isBackdrop(element, width, height) {
|
|
4501
|
+
const rect = element.getBoundingClientRect();
|
|
4502
|
+
return !element.matches("[data-exiting]") && getComputedStyle(element).position === "fixed" && rect.width >= width - SCROLLBAR_SLACK && rect.top <= height / 2 && rect.bottom >= height - ROUNDING_SLACK;
|
|
3989
4503
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
4504
|
+
// react-aria keeps a classic scrollbar's gutter reserved while its modals lock
|
|
4505
|
+
// scrolling, so their backdrops stop short of the right edge by its width.
|
|
4506
|
+
const SCROLLBAR_SLACK = 24;
|
|
4507
|
+
// Rects are fractional at non-100% zoom, but clientWidth and clientHeight are
|
|
4508
|
+
// rounded to whole pixels.
|
|
4509
|
+
const ROUNDING_SLACK = 1;
|
|
4510
|
+
|
|
4511
|
+
function useSearchShortcut(enabled, open, setOpen) {
|
|
4512
|
+
React__default.useEffect(() => {
|
|
4513
|
+
if (!enabled) return undefined;
|
|
4514
|
+
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
4515
|
+
// window and stopping propagation keeps them from ever seeing it. Under a
|
|
4516
|
+
// product's modal, search stays shut rather than stacking a second modal,
|
|
4517
|
+
// and the keystroke goes on to the product.
|
|
4518
|
+
const handleKeyDown = event => {
|
|
4519
|
+
if (open && isSearchShortcut(event)) {
|
|
4520
|
+
event.preventDefault();
|
|
4521
|
+
event.stopPropagation();
|
|
4522
|
+
setOpen(false);
|
|
4523
|
+
} else if (!open && (isSearchShortcut(event) || isLegacySearchShortcut(event)) && !isModalOpen()) {
|
|
4524
|
+
event.preventDefault();
|
|
4525
|
+
event.stopPropagation();
|
|
4526
|
+
setOpen(true);
|
|
4527
|
+
}
|
|
3998
4528
|
};
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
4007
|
-
} catch {
|
|
4008
|
-
parsed = null;
|
|
4009
|
-
}
|
|
4010
|
-
if (isRecord(parsed) && parsed.v === FORMAT_VERSION && parsed.person === personId && Array.isArray(parsed.entries)) {
|
|
4011
|
-
return parsed.entries.filter(isEntry);
|
|
4012
|
-
} else {
|
|
4013
|
-
return [];
|
|
4014
|
-
}
|
|
4015
|
-
}
|
|
4016
|
-
function addVisit(entries, visit) {
|
|
4017
|
-
const key = entryKey(visit);
|
|
4018
|
-
return [clampEntry(visit), ...entries.filter(entry => entryKey(entry) !== key)].slice(0, MAX_ENTRIES);
|
|
4529
|
+
window.addEventListener("keydown", handleKeyDown, {
|
|
4530
|
+
capture: true
|
|
4531
|
+
});
|
|
4532
|
+
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
4533
|
+
capture: true
|
|
4534
|
+
});
|
|
4535
|
+
}, [enabled, open, setOpen]);
|
|
4019
4536
|
}
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4537
|
+
|
|
4538
|
+
const SEARCHABLE_PRODUCTS = [{
|
|
4539
|
+
appName: "Calendar",
|
|
4540
|
+
slug: "calendar"
|
|
4541
|
+
}, {
|
|
4542
|
+
appName: "Check-Ins",
|
|
4543
|
+
slug: "check-ins"
|
|
4544
|
+
}, {
|
|
4545
|
+
appName: "Giving",
|
|
4546
|
+
slug: "giving"
|
|
4547
|
+
}, {
|
|
4548
|
+
appName: "Groups",
|
|
4549
|
+
slug: "groups"
|
|
4550
|
+
}, {
|
|
4551
|
+
appName: "People",
|
|
4552
|
+
slug: "people"
|
|
4553
|
+
}, {
|
|
4554
|
+
appName: "Publishing",
|
|
4555
|
+
slug: "publishing"
|
|
4556
|
+
}, {
|
|
4557
|
+
appName: "Registrations",
|
|
4558
|
+
slug: "registrations"
|
|
4559
|
+
}, {
|
|
4560
|
+
appName: "Services",
|
|
4561
|
+
slug: "services"
|
|
4562
|
+
}];
|
|
4563
|
+
|
|
4564
|
+
const PERMISSION_CHECKS = {
|
|
4565
|
+
"check-ins": () => fetchMe("check-ins", "permission").then(({
|
|
4566
|
+
permission
|
|
4567
|
+
}) => permission !== null),
|
|
4568
|
+
giving: personId => {
|
|
4569
|
+
if (personId === undefined) {
|
|
4570
|
+
return Promise.reject(new Error("No person to check Giving access for"));
|
|
4571
|
+
} else {
|
|
4572
|
+
return fetchAttributes(`giving/v2/person_permissions/${personId}`, {
|
|
4573
|
+
"fields[PersonPermissions]": "can_search"
|
|
4574
|
+
}).then(({
|
|
4575
|
+
can_search
|
|
4576
|
+
}) => can_search);
|
|
4577
|
+
}
|
|
4578
|
+
},
|
|
4579
|
+
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
4580
|
+
people_permissions,
|
|
4581
|
+
site_administrator
|
|
4582
|
+
}) => site_administrator || people_permissions !== null),
|
|
4583
|
+
registrations: () => fetchMe("registrations", "can_search").then(({
|
|
4584
|
+
can_search
|
|
4585
|
+
}) => can_search),
|
|
4586
|
+
services: () => fetchMe("services", "can_search").then(({
|
|
4587
|
+
can_search
|
|
4588
|
+
}) => can_search)
|
|
4589
|
+
};
|
|
4590
|
+
function fetchMe(product, fields) {
|
|
4591
|
+
return fetchAttributes(`${product}/v2/me`, {
|
|
4592
|
+
"fields[Person]": fields
|
|
4025
4593
|
});
|
|
4026
4594
|
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4595
|
+
function fetchAttributes(path, params) {
|
|
4596
|
+
const url = `${window.location.origin}/~api/${path}?${new URLSearchParams(params)}`;
|
|
4597
|
+
return apiRequest(defaultFetch, url, {
|
|
4598
|
+
headers: apiVersionHeaders(url)
|
|
4599
|
+
}).then(({
|
|
4600
|
+
json,
|
|
4601
|
+
ok
|
|
4602
|
+
}) => {
|
|
4603
|
+
if (!ok || json === null) throw new Error("Permission check failed");
|
|
4604
|
+
return json.data.attributes;
|
|
4605
|
+
});
|
|
4034
4606
|
}
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4607
|
+
// Permissions rarely change mid-session, so a successful check is kept until
|
|
4608
|
+
// the page reloads. A failed one is dropped so a remount tries again.
|
|
4609
|
+
const permissionCache = new Map();
|
|
4610
|
+
function canSearch(slug, personId) {
|
|
4611
|
+
const check = PERMISSION_CHECKS[slug];
|
|
4612
|
+
const cached = permissionCache.get(slug);
|
|
4613
|
+
if (!check) {
|
|
4614
|
+
return Promise.resolve(true);
|
|
4615
|
+
} else if (cached) {
|
|
4616
|
+
return cached;
|
|
4617
|
+
} else {
|
|
4618
|
+
const promise = check(personId).catch(() => {
|
|
4619
|
+
permissionCache.delete(slug);
|
|
4620
|
+
return false;
|
|
4621
|
+
});
|
|
4622
|
+
permissionCache.set(slug, promise);
|
|
4623
|
+
return promise;
|
|
4042
4624
|
}
|
|
4043
4625
|
}
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
})
|
|
4051
|
-
const
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4626
|
+
|
|
4627
|
+
function useSearchableProducts(apps) {
|
|
4628
|
+
// Keyed by name because providers hand over a fresh `apps` array on renders
|
|
4629
|
+
// where nothing changed.
|
|
4630
|
+
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
4631
|
+
slug
|
|
4632
|
+
}) => slug).join(",");
|
|
4633
|
+
const personId = useCurrentPersonId();
|
|
4634
|
+
const [settled, setSettled] = React__default.useState();
|
|
4635
|
+
React__default.useEffect(() => {
|
|
4636
|
+
let current = true;
|
|
4637
|
+
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
4638
|
+
Promise.all(accessible.map(({
|
|
4639
|
+
slug
|
|
4640
|
+
}) => canSearch(slug, personId))).then(allowed => {
|
|
4641
|
+
if (current) {
|
|
4642
|
+
setSettled({
|
|
4643
|
+
accessibleKey,
|
|
4644
|
+
personId,
|
|
4645
|
+
products: accessible.filter((_, index) => allowed[index])
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
});
|
|
4649
|
+
return () => {
|
|
4650
|
+
current = false;
|
|
4056
4651
|
};
|
|
4057
|
-
}
|
|
4652
|
+
}, [accessibleKey, personId]);
|
|
4653
|
+
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey && settled.personId === personId) {
|
|
4058
4654
|
return {
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
product,
|
|
4062
|
-
title,
|
|
4063
|
-
type: "page"
|
|
4655
|
+
products: settled.products,
|
|
4656
|
+
status: "done"
|
|
4064
4657
|
};
|
|
4065
4658
|
} else {
|
|
4066
|
-
return
|
|
4659
|
+
return {
|
|
4660
|
+
status: "loading"
|
|
4661
|
+
};
|
|
4067
4662
|
}
|
|
4068
4663
|
}
|
|
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]);
|
|
4083
|
-
}
|
|
4084
|
-
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
4085
|
-
// and several pass it as a number.
|
|
4086
|
-
function useCurrentPersonId() {
|
|
4087
|
-
const {
|
|
4088
|
-
userId
|
|
4089
|
-
} = useStore();
|
|
4090
|
-
return userId == null ? undefined : String(userId);
|
|
4664
|
+
function inSwitcherOrder(apps) {
|
|
4665
|
+
return groupApps(apps).restProducts.flatMap(app => SEARCHABLE_PRODUCTS.filter(product => product.appName === app.name));
|
|
4091
4666
|
}
|
|
4092
4667
|
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
function storageKey({
|
|
4097
|
-
env,
|
|
4098
|
-
personId
|
|
4668
|
+
function ProductSearch({
|
|
4669
|
+
apps,
|
|
4670
|
+
env
|
|
4099
4671
|
}) {
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
try {
|
|
4117
|
-
localStorage.setItem(storageKey(scope), serializeRecentlyVisited(addVisit(readRecentlyVisited(scope), visit), scope.personId));
|
|
4118
|
-
changes.setState({
|
|
4119
|
-
version: changes.getState().version + 1
|
|
4120
|
-
});
|
|
4121
|
-
} catch {
|
|
4122
|
-
// Storage can be full or blocked; recents are disposable.
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4672
|
+
const searchable = useSearchableProducts(apps);
|
|
4673
|
+
const [open, setOpen] = React__default.useState(false);
|
|
4674
|
+
const available = searchable.status === "done" && searchable.products.length > 0;
|
|
4675
|
+
useSearchShortcut(available, open, setOpen);
|
|
4676
|
+
if (!available) return null;
|
|
4677
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(SearchButton, {
|
|
4678
|
+
onClick: () => setOpen(true)
|
|
4679
|
+
}), /*#__PURE__*/React__default.createElement(Modal, {
|
|
4680
|
+
"aria-label": "Search",
|
|
4681
|
+
dismissible: true,
|
|
4682
|
+
onClose: () => setOpen(false),
|
|
4683
|
+
open: open
|
|
4684
|
+
}, /*#__PURE__*/React__default.createElement(SearchPanel, {
|
|
4685
|
+
env: env,
|
|
4686
|
+
products: searchable.products
|
|
4687
|
+
})));
|
|
4125
4688
|
}
|
|
4126
4689
|
|
|
4127
4690
|
function useRecordRecentlyVisited({
|
|
@@ -4166,9 +4729,9 @@ function StyledRoot({
|
|
|
4166
4729
|
`), children);
|
|
4167
4730
|
}
|
|
4168
4731
|
|
|
4169
|
-
var styles$
|
|
4732
|
+
var styles$a = {"route":"route--8BWe","is-active":"is-active-FsU9Y","apps-routes-separator":"apps-routes-separator-Nlm-N"};
|
|
4170
4733
|
|
|
4171
|
-
var styles$
|
|
4734
|
+
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
4735
|
|
|
4173
4736
|
const UserMenu = ({
|
|
4174
4737
|
appName,
|
|
@@ -4187,7 +4750,7 @@ const UserMenu = ({
|
|
|
4187
4750
|
component: Outsider,
|
|
4188
4751
|
onOutsideClick: () => setUserMenuVisible(false),
|
|
4189
4752
|
cleanup: !userMenuVisible,
|
|
4190
|
-
className: styles$
|
|
4753
|
+
className: styles$9.popup,
|
|
4191
4754
|
isVisible: userMenuVisible
|
|
4192
4755
|
}, /*#__PURE__*/React__default.createElement("div", null, hasConnectedPeople ? (/*#__PURE__*/React__default.createElement("div", {
|
|
4193
4756
|
style: {
|
|
@@ -4295,7 +4858,7 @@ const UserMenu = ({
|
|
|
4295
4858
|
}, /*#__PURE__*/React__default.createElement(Text, {
|
|
4296
4859
|
color: "var(--t-text-color-secondary)",
|
|
4297
4860
|
lineHeight: "24px"
|
|
4298
|
-
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon, {
|
|
4861
|
+
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
4299
4862
|
name: "general.intoBox",
|
|
4300
4863
|
fill: "var(--t-icon-color-secondary)"
|
|
4301
4864
|
})))))));
|
|
@@ -4331,11 +4894,11 @@ const UserMenuTrigger = ({
|
|
|
4331
4894
|
userMenuVisible
|
|
4332
4895
|
}) => {
|
|
4333
4896
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
4334
|
-
className: styles$
|
|
4897
|
+
className: styles$9["trigger-wrap"],
|
|
4335
4898
|
"data-pendo": "topbar-org-switcher"
|
|
4336
4899
|
}, /*#__PURE__*/React__default.createElement(Hoverable, {
|
|
4337
|
-
className: classnames(styles$
|
|
4338
|
-
[styles$
|
|
4900
|
+
className: classnames(styles$9["trigger"], {
|
|
4901
|
+
[styles$9["is-visible"]]: userMenuVisible
|
|
4339
4902
|
}),
|
|
4340
4903
|
component: "button",
|
|
4341
4904
|
type: "button",
|
|
@@ -4347,7 +4910,7 @@ const UserMenuTrigger = ({
|
|
|
4347
4910
|
requestConnectedPeopleFetch();
|
|
4348
4911
|
}
|
|
4349
4912
|
}, showOrgName && (/*#__PURE__*/React__default.createElement("span", {
|
|
4350
|
-
className: styles$
|
|
4913
|
+
className: styles$9["trigger-orgname"]
|
|
4351
4914
|
}, orgName)), /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)));
|
|
4352
4915
|
};
|
|
4353
4916
|
|
|
@@ -4407,13 +4970,14 @@ const NotSmallTopbar = ({
|
|
|
4407
4970
|
colors: props.colors,
|
|
4408
4971
|
requestAppsFetch: props.requestAppsFetch
|
|
4409
4972
|
})), /*#__PURE__*/React__default.createElement("div", {
|
|
4410
|
-
className: styles$
|
|
4973
|
+
className: styles$a["apps-routes-separator"]
|
|
4411
4974
|
}), routesVisible && props.routes, /*#__PURE__*/React__default.createElement("div", {
|
|
4412
4975
|
style: {
|
|
4413
4976
|
margin: "auto"
|
|
4414
4977
|
}
|
|
4415
4978
|
}), !notificationsEnabled && notifications(), productSearchEnabled ? (/*#__PURE__*/React__default.createElement(ProductSearch, {
|
|
4416
|
-
apps: props.apps
|
|
4979
|
+
apps: props.apps,
|
|
4980
|
+
env: props.env
|
|
4417
4981
|
})) : search({
|
|
4418
4982
|
hideRoutes: () => setRoutesVisible(false),
|
|
4419
4983
|
showRoutes: () => setRoutesVisible(true)
|
|
@@ -4447,12 +5011,12 @@ const NotSmallRoute = ({
|
|
|
4447
5011
|
...nativeProps
|
|
4448
5012
|
}) => {
|
|
4449
5013
|
return /*#__PURE__*/React__default.createElement("a", {
|
|
4450
|
-
className: classnames(styles$
|
|
5014
|
+
className: classnames(styles$a["route"], className, active && styles$a["is-active"]),
|
|
4451
5015
|
...nativeProps
|
|
4452
5016
|
});
|
|
4453
5017
|
};
|
|
4454
5018
|
|
|
4455
|
-
var styles$
|
|
5019
|
+
var styles$8 = {"avatar":"avatar-tW-wn"};
|
|
4456
5020
|
|
|
4457
5021
|
function Avatar({
|
|
4458
5022
|
env,
|
|
@@ -4462,7 +5026,7 @@ function Avatar({
|
|
|
4462
5026
|
}) {
|
|
4463
5027
|
const url = incomingUrl || `${pcoUrl$1(env)("people")}/static/no_photo_thumbnail_gray.svg`;
|
|
4464
5028
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
4465
|
-
className: styles$
|
|
5029
|
+
className: styles$8.avatar,
|
|
4466
5030
|
style: {
|
|
4467
5031
|
backgroundImage: `url(${url})`,
|
|
4468
5032
|
...style
|
|
@@ -32221,7 +32785,7 @@ function requireReactDom () {
|
|
|
32221
32785
|
|
|
32222
32786
|
var reactDomExports = requireReactDom();
|
|
32223
32787
|
|
|
32224
|
-
var styles$
|
|
32788
|
+
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
32789
|
|
|
32226
32790
|
const AppsSwitcher = ({
|
|
32227
32791
|
appName,
|
|
@@ -32249,8 +32813,8 @@ const AppsSwitcher = ({
|
|
|
32249
32813
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32250
32814
|
"aria-current": name === appName ? "page" : undefined,
|
|
32251
32815
|
"aria-label": name,
|
|
32252
|
-
className: classnames(styles$
|
|
32253
|
-
[styles$
|
|
32816
|
+
className: classnames(styles$7.link, {
|
|
32817
|
+
[styles$7.current]: name === appName
|
|
32254
32818
|
}),
|
|
32255
32819
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32256
32820
|
"data-turbolinks": false,
|
|
@@ -32262,48 +32826,48 @@ const AppsSwitcher = ({
|
|
|
32262
32826
|
"aria-expanded": visible,
|
|
32263
32827
|
"aria-haspopup": "true",
|
|
32264
32828
|
"aria-label": `App switcher: ${appName}`,
|
|
32265
|
-
className: styles$
|
|
32829
|
+
className: styles$7.trigger,
|
|
32266
32830
|
onClick: onTriggerClick,
|
|
32267
32831
|
type: "button"
|
|
32268
32832
|
}, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
32269
32833
|
appName: appName.replace(/[\s-]/, "")
|
|
32270
32834
|
})), /*#__PURE__*/reactDomExports.createPortal(/*#__PURE__*/React__default.createElement("div", {
|
|
32271
32835
|
ref: containerRef,
|
|
32272
|
-
className: classnames(styles$
|
|
32273
|
-
[styles$
|
|
32836
|
+
className: classnames(styles$7.layer, {
|
|
32837
|
+
[styles$7["layer--visible"]]: visible
|
|
32274
32838
|
}),
|
|
32275
32839
|
onClick: toggle,
|
|
32276
32840
|
onKeyDown: handleKeyDown
|
|
32277
32841
|
}, /*#__PURE__*/React__default.createElement("nav", {
|
|
32278
32842
|
"aria-label": "Switch app",
|
|
32279
32843
|
"aria-modal": visible,
|
|
32280
|
-
className: styles$
|
|
32844
|
+
className: styles$7.panel,
|
|
32281
32845
|
"data-pendo": "app-switcher",
|
|
32282
32846
|
onClick: event => event.stopPropagation(),
|
|
32283
32847
|
role: "dialog"
|
|
32284
32848
|
}, /*#__PURE__*/React__default.createElement("div", {
|
|
32285
|
-
className: styles$
|
|
32849
|
+
className: styles$7.header
|
|
32286
32850
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
32287
|
-
className: styles$
|
|
32851
|
+
className: styles$7.heading
|
|
32288
32852
|
}, "Switch to"), /*#__PURE__*/React__default.createElement("button", {
|
|
32289
32853
|
"aria-label": "Close",
|
|
32290
|
-
className: styles$
|
|
32854
|
+
className: styles$7.close,
|
|
32291
32855
|
onClick: toggle,
|
|
32292
32856
|
type: "button"
|
|
32293
32857
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
32294
32858
|
"aria-hidden": "true",
|
|
32295
32859
|
fill: "var(--t-icon-color-dim)"
|
|
32296
32860
|
}))), /*#__PURE__*/React__default.createElement("ul", {
|
|
32297
|
-
className: styles$
|
|
32861
|
+
className: styles$7.list,
|
|
32298
32862
|
role: "list"
|
|
32299
32863
|
}, homeProduct && productLink(homeProduct.name), restProducts.map(({
|
|
32300
32864
|
name
|
|
32301
32865
|
}) => productLink(name)), accountsProduct && (/*#__PURE__*/React__default.createElement("li", {
|
|
32302
|
-
className: styles$
|
|
32866
|
+
className: styles$7["accounts-item"]
|
|
32303
32867
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32304
32868
|
"aria-current": appName === "Accounts" ? "page" : undefined,
|
|
32305
32869
|
"aria-label": "Account settings",
|
|
32306
|
-
className: classnames(styles$
|
|
32870
|
+
className: classnames(styles$7.link, styles$7["accounts-link"]),
|
|
32307
32871
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
32308
32872
|
"data-turbolinks": false,
|
|
32309
32873
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -32326,14 +32890,14 @@ ClientStorage.defaultProps = {
|
|
|
32326
32890
|
method: "localStorage"
|
|
32327
32891
|
};
|
|
32328
32892
|
|
|
32329
|
-
var styles$
|
|
32893
|
+
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","profile-button":"profile-button-a2WO2"};
|
|
32330
32894
|
|
|
32331
32895
|
function AccountSettingsLink({
|
|
32332
32896
|
env
|
|
32333
32897
|
}) {
|
|
32334
32898
|
const accountsName = "accounts";
|
|
32335
32899
|
return /*#__PURE__*/React__default.createElement("a", {
|
|
32336
|
-
className: classnames(styles$
|
|
32900
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--bottom"]),
|
|
32337
32901
|
"data-pendo": `app-switcher--change-app--${accountsName}`,
|
|
32338
32902
|
"data-turbolinks": false,
|
|
32339
32903
|
href: pcoUrl$1(env)(accountsName) + getAppsSwitchProductPath(accountsName)
|
|
@@ -32356,7 +32920,7 @@ const CloseMenuButton = ({
|
|
|
32356
32920
|
}) => (/*#__PURE__*/React__default.createElement("button", {
|
|
32357
32921
|
"aria-label": "Close menu",
|
|
32358
32922
|
type: "button",
|
|
32359
|
-
className: styles$
|
|
32923
|
+
className: styles$6["appmenu-close-button"],
|
|
32360
32924
|
style: style
|
|
32361
32925
|
}, /*#__PURE__*/React__default.createElement(X, {
|
|
32362
32926
|
fill: color,
|
|
@@ -32417,7 +32981,7 @@ const LegacyAppsSwitcher = ({
|
|
|
32417
32981
|
"aria-haspopup": "true",
|
|
32418
32982
|
"aria-label": `App switcher: ${appName}`,
|
|
32419
32983
|
type: "button",
|
|
32420
|
-
className: styles$
|
|
32984
|
+
className: styles$6["appmenu-button"],
|
|
32421
32985
|
onClick: onTriggerClick
|
|
32422
32986
|
}, /*#__PURE__*/React__default.createElement(ColorAppIcon, {
|
|
32423
32987
|
appName: appName.replace(/[\s-]/, "")
|
|
@@ -32425,7 +32989,7 @@ const LegacyAppsSwitcher = ({
|
|
|
32425
32989
|
item: "Topbar:Small:Menus:Products:visibility",
|
|
32426
32990
|
render: () => (/*#__PURE__*/React__default.createElement("div", {
|
|
32427
32991
|
ref: appMenuRef,
|
|
32428
|
-
className: styles$
|
|
32992
|
+
className: styles$6["appmenu-layer"],
|
|
32429
32993
|
style: {
|
|
32430
32994
|
...(visible && {
|
|
32431
32995
|
display: "block"
|
|
@@ -32442,37 +33006,37 @@ const LegacyAppsSwitcher = ({
|
|
|
32442
33006
|
e.stopPropagation();
|
|
32443
33007
|
}
|
|
32444
33008
|
},
|
|
32445
|
-
className: styles$
|
|
33009
|
+
className: styles$6["appmenu-container"]
|
|
32446
33010
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32447
33011
|
"aria-current": appName === (homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) ? "page" : undefined,
|
|
32448
33012
|
"aria-label": homeProduct ? homeProduct.name : appName,
|
|
32449
33013
|
"data-pendo": `app-switcher--change-app--${homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name}`,
|
|
32450
33014
|
"data-turbolinks": false,
|
|
32451
|
-
className: classnames(styles$
|
|
33015
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--top"]),
|
|
32452
33016
|
href: homeProduct ? pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) : undefined
|
|
32453
33017
|
}, /*#__PURE__*/React__default.createElement(UserMenuAppLockup, {
|
|
32454
33018
|
appName: homeProduct ? homeProduct.name : appName
|
|
32455
33019
|
})), /*#__PURE__*/React__default.createElement("div", {
|
|
32456
|
-
className: styles$
|
|
33020
|
+
className: styles$6["appmenu-scroll"]
|
|
32457
33021
|
}, /*#__PURE__*/React__default.createElement("ul", {
|
|
32458
33022
|
"data-pendo": "app-switcher",
|
|
32459
|
-
className: styles$
|
|
33023
|
+
className: styles$6["appmenu-list"]
|
|
32460
33024
|
}, appsMenuFormatter(restProducts).map(({
|
|
32461
33025
|
name
|
|
32462
33026
|
}) => (/*#__PURE__*/React__default.createElement("li", {
|
|
32463
33027
|
key: name,
|
|
32464
|
-
className: styles$
|
|
33028
|
+
className: styles$6["appmenu-list-li"]
|
|
32465
33029
|
}, /*#__PURE__*/React__default.createElement("a", {
|
|
32466
33030
|
"aria-current": name === appName ? "page" : undefined,
|
|
32467
33031
|
"aria-label": name,
|
|
32468
33032
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32469
33033
|
"data-turbolinks": false,
|
|
32470
|
-
className: styles$
|
|
33034
|
+
className: styles$6["appmenu-list-a"],
|
|
32471
33035
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(name)
|
|
32472
33036
|
}, /*#__PURE__*/React__default.createElement(UserMenuAppLockup, {
|
|
32473
33037
|
appName: name
|
|
32474
33038
|
}))))), accountsProduct && (/*#__PURE__*/React__default.createElement("li", {
|
|
32475
|
-
className: styles$
|
|
33039
|
+
className: styles$6["appmenu-list-li"]
|
|
32476
33040
|
}, /*#__PURE__*/React__default.createElement(AccountSettingsLink, {
|
|
32477
33041
|
env: env
|
|
32478
33042
|
}))))))))
|
|
@@ -32536,19 +33100,19 @@ const SmallTopbar = ({
|
|
|
32536
33100
|
toggle: toggleProductSwitcher,
|
|
32537
33101
|
visible: productSwitcherVisible
|
|
32538
33102
|
})), routesMenuVisible && (/*#__PURE__*/React__default.createElement("div", {
|
|
32539
|
-
className: styles$
|
|
33103
|
+
className: styles$6["routes"]
|
|
32540
33104
|
}, props.routes)), /*#__PURE__*/React__default.createElement("button", {
|
|
32541
33105
|
type: "button",
|
|
32542
|
-
className: classnames(styles$
|
|
32543
|
-
[styles$
|
|
33106
|
+
className: classnames(styles$6["current-route"], {
|
|
33107
|
+
[styles$6["is-active"]]: routesMenuVisible
|
|
32544
33108
|
}),
|
|
32545
33109
|
onClick: props.routes.length > 0 ? () => {
|
|
32546
33110
|
setProductSwitcherVisible(false);
|
|
32547
33111
|
setRoutesMenuVisible(visible => !visible);
|
|
32548
33112
|
} : null
|
|
32549
|
-
}, /*#__PURE__*/React__default.createElement(CurrentRouteComponent, null, capitalize(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React__default.createElement("button", {
|
|
33113
|
+
}, /*#__PURE__*/React__default.createElement(CurrentRouteComponent, null, capitalize$1(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React__default.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React__default.createElement("button", {
|
|
32550
33114
|
type: "button",
|
|
32551
|
-
className: styles$
|
|
33115
|
+
className: styles$6["profile-button"],
|
|
32552
33116
|
onClick: () => {
|
|
32553
33117
|
var _a, _b;
|
|
32554
33118
|
props.requestConnectedPeopleFetch();
|
|
@@ -32564,7 +33128,7 @@ const SmallRoute = ({
|
|
|
32564
33128
|
className,
|
|
32565
33129
|
...props
|
|
32566
33130
|
}) => (/*#__PURE__*/React__default.createElement("a", {
|
|
32567
|
-
className: classnames(styles$
|
|
33131
|
+
className: classnames(styles$6["route"], className, active && styles$6["is-active"]),
|
|
32568
33132
|
...props
|
|
32569
33133
|
}));
|
|
32570
33134
|
|
|
@@ -32703,7 +33267,7 @@ const topbarTheme = {
|
|
|
32703
33267
|
id: "topbar"
|
|
32704
33268
|
};
|
|
32705
33269
|
|
|
32706
|
-
var styles$
|
|
33270
|
+
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
33271
|
|
|
32708
33272
|
function isPresent(value) {
|
|
32709
33273
|
return value !== undefined && value.trim() !== "";
|
|
@@ -32732,15 +33296,31 @@ function Field({
|
|
|
32732
33296
|
value
|
|
32733
33297
|
}) {
|
|
32734
33298
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32735
|
-
className: styles$
|
|
33299
|
+
className: styles$5["debug-pco-resource-field"]
|
|
32736
33300
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
32737
|
-
className: styles$
|
|
33301
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
32738
33302
|
}, label), /*#__PURE__*/React__default.createElement("span", {
|
|
32739
|
-
className: styles$
|
|
33303
|
+
className: styles$5["debug-pco-resource-field-value"]
|
|
32740
33304
|
}, value));
|
|
32741
33305
|
}
|
|
33306
|
+
// Only staff ghosting or impersonating a person get pco:staff-access-level,
|
|
33307
|
+
// which keeps the panel away from regular users on staging.
|
|
32742
33308
|
function DebugPcoResource({
|
|
32743
33309
|
env
|
|
33310
|
+
}) {
|
|
33311
|
+
const {
|
|
33312
|
+
"staff-access-level": staffAccessLevel
|
|
33313
|
+
} = usePcoMeta();
|
|
33314
|
+
if (isPresent(staffAccessLevel)) {
|
|
33315
|
+
return /*#__PURE__*/React__default.createElement(DebugPanel, {
|
|
33316
|
+
env: env
|
|
33317
|
+
});
|
|
33318
|
+
} else {
|
|
33319
|
+
return null;
|
|
33320
|
+
}
|
|
33321
|
+
}
|
|
33322
|
+
function DebugPanel({
|
|
33323
|
+
env
|
|
32744
33324
|
}) {
|
|
32745
33325
|
const {
|
|
32746
33326
|
recently_visitable: recentlyVisitable,
|
|
@@ -32749,12 +33329,17 @@ function DebugPcoResource({
|
|
|
32749
33329
|
const [lookup, setLookup] = useState({
|
|
32750
33330
|
status: "idle"
|
|
32751
33331
|
});
|
|
32752
|
-
const
|
|
33332
|
+
const visit = useCurrentVisit();
|
|
33333
|
+
const personId = useCurrentPersonId();
|
|
33334
|
+
const recentlyVisited = useRecentlyVisited({
|
|
33335
|
+
env,
|
|
33336
|
+
personId
|
|
33337
|
+
});
|
|
33338
|
+
const pcrl = (visit === null || visit === void 0 ? void 0 : visit.type) === "resource" ? visit.pcrl : null;
|
|
32753
33339
|
const hasRecentlyVisitable = isPresent(recentlyVisitable);
|
|
33340
|
+
const hasInvalidResource = isPresent(resource) && !isPcrl(resource);
|
|
32754
33341
|
useEffect(() => {
|
|
32755
|
-
|
|
32756
|
-
// tags are present.
|
|
32757
|
-
const url = hasResource ? pcrlLookupUrl(resource, env) : null;
|
|
33342
|
+
const url = pcrl ? pcrlLookupUrl(pcrl, env) : null;
|
|
32758
33343
|
if (!url) {
|
|
32759
33344
|
setLookup({
|
|
32760
33345
|
status: "idle"
|
|
@@ -32786,37 +33371,45 @@ function DebugPcoResource({
|
|
|
32786
33371
|
return () => {
|
|
32787
33372
|
cancelled = true;
|
|
32788
33373
|
};
|
|
32789
|
-
}, [
|
|
33374
|
+
}, [pcrl, env]);
|
|
32790
33375
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32791
|
-
className: classnames("staff-only", styles$
|
|
33376
|
+
className: classnames("staff-only", styles$5["debug-pco-resource"])
|
|
32792
33377
|
}, /*#__PURE__*/React__default.createElement("div", {
|
|
32793
|
-
className: styles$
|
|
33378
|
+
className: styles$5["debug-pco-resource-header"]
|
|
32794
33379
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
32795
|
-
className: styles$
|
|
33380
|
+
className: styles$5["debug-pco-resource-badge"]
|
|
32796
33381
|
}, "Staff"), /*#__PURE__*/React__default.createElement("span", {
|
|
32797
|
-
className: styles$
|
|
33382
|
+
className: styles$5["debug-pco-resource-title"]
|
|
32798
33383
|
}, "Recently Visited debug")), /*#__PURE__*/React__default.createElement(Field, {
|
|
32799
33384
|
label: "pco:resource",
|
|
32800
33385
|
value: describeTag("resource", resource)
|
|
32801
33386
|
}), /*#__PURE__*/React__default.createElement(Field, {
|
|
32802
33387
|
label: "pco:recently_visitable",
|
|
32803
33388
|
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
|
-
|
|
33389
|
+
}), hasInvalidResource && (/*#__PURE__*/React__default.createElement("p", {
|
|
33390
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33391
|
+
}, "pco:resource isn't a PCRL (product/v2/type/id), so Recently Visited ignores it.")), visit === null && (/*#__PURE__*/React__default.createElement("p", {
|
|
33392
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33393
|
+
}, "This page is not eligible for Recently Visited")), pcrl && hasRecentlyVisitable && (/*#__PURE__*/React__default.createElement("p", {
|
|
33394
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33395
|
+
}, "Both tags are present \u2014 pco:resource takes precedence for the Recently Visited entry.")), pcrl && lookup.status === "loading" && (/*#__PURE__*/React__default.createElement("p", {
|
|
33396
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33397
|
+
}, "Looking up resource\u2026")), pcrl && lookup.status === "error" && (/*#__PURE__*/React__default.createElement("p", {
|
|
33398
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33399
|
+
}, lookup.message)), pcrl && lookup.status === "loaded" && (/*#__PURE__*/React__default.createElement("pre", {
|
|
33400
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33401
|
+
}, JSON.stringify(lookup.json, null, 2))), (visit === null || visit === void 0 ? void 0 : visit.type) === "page" && (/*#__PURE__*/React__default.createElement("p", {
|
|
33402
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33403
|
+
}, "Recently Visited title: ", visit.title)), /*#__PURE__*/React__default.createElement("span", {
|
|
33404
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
33405
|
+
}, "Recently Visited"), personId ? (/*#__PURE__*/React__default.createElement("pre", {
|
|
33406
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33407
|
+
}, JSON.stringify(recentlyVisited, null, 2))) : (/*#__PURE__*/React__default.createElement("p", {
|
|
33408
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33409
|
+
}, "No current person, so nothing is recorded.")));
|
|
33410
|
+
}
|
|
33411
|
+
|
|
33412
|
+
var styles$4 = {"drawer-wrap":"drawer-wrap-J0rSL","is-visible":"is-visible-KTME8","drawer":"drawer-j7f4l"};
|
|
32820
33413
|
|
|
32821
33414
|
function Drawer({
|
|
32822
33415
|
children,
|
|
@@ -32824,7 +33417,7 @@ function Drawer({
|
|
|
32824
33417
|
}) {
|
|
32825
33418
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32826
33419
|
id: id,
|
|
32827
|
-
className: styles$
|
|
33420
|
+
className: styles$4["drawer"]
|
|
32828
33421
|
}, children);
|
|
32829
33422
|
}
|
|
32830
33423
|
function DrawerWrap({
|
|
@@ -32834,7 +33427,7 @@ function DrawerWrap({
|
|
|
32834
33427
|
...props
|
|
32835
33428
|
}) {
|
|
32836
33429
|
return /*#__PURE__*/React__default.createElement("div", {
|
|
32837
|
-
className: classnames(styles$
|
|
33430
|
+
className: classnames(styles$4["drawer-wrap"], className, isVisible && styles$4["is-visible"]),
|
|
32838
33431
|
...props
|
|
32839
33432
|
}, children);
|
|
32840
33433
|
}
|
|
@@ -33018,15 +33611,50 @@ const SwitchView = ({
|
|
|
33018
33611
|
}));
|
|
33019
33612
|
};
|
|
33020
33613
|
|
|
33021
|
-
|
|
33614
|
+
const collections = {
|
|
33615
|
+
general,
|
|
33616
|
+
toolbar
|
|
33617
|
+
};
|
|
33618
|
+
const paths = collections;
|
|
33619
|
+
function Icon({
|
|
33620
|
+
name,
|
|
33621
|
+
size = 16,
|
|
33622
|
+
style,
|
|
33623
|
+
theme,
|
|
33624
|
+
title,
|
|
33625
|
+
...props
|
|
33626
|
+
}) {
|
|
33627
|
+
const [collection, symbol] = name.split(".");
|
|
33628
|
+
return /*#__PURE__*/React__default.createElement("svg", {
|
|
33629
|
+
"aria-hidden": title ? undefined : true,
|
|
33630
|
+
fill: "currentColor",
|
|
33631
|
+
height: size,
|
|
33632
|
+
role: title ? "img" : undefined,
|
|
33633
|
+
style: {
|
|
33634
|
+
color: theme && `var(${theme})`,
|
|
33635
|
+
flexShrink: 0,
|
|
33636
|
+
...style
|
|
33637
|
+
},
|
|
33638
|
+
viewBox: "0 0 16 16",
|
|
33639
|
+
width: size,
|
|
33640
|
+
...props
|
|
33641
|
+
}, title && /*#__PURE__*/React__default.createElement("title", null, title), /*#__PURE__*/React__default.createElement("path", {
|
|
33642
|
+
d: paths[collection][symbol]
|
|
33643
|
+
}));
|
|
33644
|
+
}
|
|
33645
|
+
|
|
33646
|
+
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
33647
|
|
|
33023
33648
|
const COLOR_MODES = [{
|
|
33649
|
+
icon: "toolbar.sun",
|
|
33024
33650
|
label: "Light",
|
|
33025
33651
|
value: "light"
|
|
33026
33652
|
}, {
|
|
33653
|
+
icon: "toolbar.moon",
|
|
33027
33654
|
label: "Dark",
|
|
33028
33655
|
value: "dark"
|
|
33029
33656
|
}, {
|
|
33657
|
+
icon: "toolbar.display",
|
|
33030
33658
|
label: "System",
|
|
33031
33659
|
value: "system"
|
|
33032
33660
|
}];
|
|
@@ -33038,14 +33666,15 @@ function ColorModeSwitcher() {
|
|
|
33038
33666
|
const updateStore = useUpdateStore();
|
|
33039
33667
|
const [counter] = useState(() => COLOR_MODE_INSTANCE_COUNTER++);
|
|
33040
33668
|
return /*#__PURE__*/React__default.createElement("fieldset", {
|
|
33041
|
-
className: styles$
|
|
33669
|
+
className: styles$3["color-mode-switcher"]
|
|
33042
33670
|
}, /*#__PURE__*/React__default.createElement("legend", {
|
|
33043
|
-
className: styles$
|
|
33671
|
+
className: styles$3["color-mode-switcher-label"]
|
|
33044
33672
|
}, "Appearance", " "), /*#__PURE__*/React__default.createElement("div", {
|
|
33045
|
-
className: styles$
|
|
33673
|
+
className: styles$3["color-mode-switcher-modes"]
|
|
33046
33674
|
}, COLOR_MODES.map(({
|
|
33047
|
-
|
|
33048
|
-
label
|
|
33675
|
+
icon,
|
|
33676
|
+
label,
|
|
33677
|
+
value
|
|
33049
33678
|
}) => (/*#__PURE__*/React__default.createElement("label", {
|
|
33050
33679
|
key: value
|
|
33051
33680
|
}, /*#__PURE__*/React__default.createElement("input", {
|
|
@@ -33062,8 +33691,9 @@ function ColorModeSwitcher() {
|
|
|
33062
33691
|
document.documentElement.setAttribute(RESOLVED_COLOR_MODE_ATTRIBUTE, resolveColorMode(colorMode));
|
|
33063
33692
|
updateColorMode(colorMode);
|
|
33064
33693
|
}
|
|
33065
|
-
}), /*#__PURE__*/React__default.createElement(
|
|
33066
|
-
|
|
33694
|
+
}), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33695
|
+
name: icon,
|
|
33696
|
+
size: 15
|
|
33067
33697
|
}), label)))));
|
|
33068
33698
|
}
|
|
33069
33699
|
function updateColorMode(mode) {
|
|
@@ -33078,28 +33708,6 @@ function updateColorMode(mode) {
|
|
|
33078
33708
|
method: "POST"
|
|
33079
33709
|
});
|
|
33080
33710
|
}
|
|
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
33711
|
|
|
33104
33712
|
const requestClearConnectedPeopleCache = () => remove();
|
|
33105
33713
|
function UserProfile({
|
|
@@ -33164,8 +33772,8 @@ function UserProfile({
|
|
|
33164
33772
|
width: "100%"
|
|
33165
33773
|
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
33166
33774
|
name: mfaConfigured ? "general.lock" : "general.unlock",
|
|
33167
|
-
size:
|
|
33168
|
-
|
|
33775
|
+
size: 12,
|
|
33776
|
+
theme: mfaConfigured ? "--t-icon-color-status-success" : "--t-icon-color-status-error"
|
|
33169
33777
|
}), /*#__PURE__*/React__default.createElement(Text, {
|
|
33170
33778
|
size: 4,
|
|
33171
33779
|
weight: 400,
|
|
@@ -33215,7 +33823,8 @@ function UserProfile({
|
|
|
33215
33823
|
size: 3
|
|
33216
33824
|
}, "Log out"), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33217
33825
|
name: "general.intoBox",
|
|
33218
|
-
|
|
33826
|
+
size: 14,
|
|
33827
|
+
theme: "--t-icon-color-secondary"
|
|
33219
33828
|
})))), env !== "production" && /*#__PURE__*/React__default.createElement(DebugPcoResource, {
|
|
33220
33829
|
env: env
|
|
33221
33830
|
}), hasConnectedPeople && (/*#__PURE__*/React__default.createElement(StackView$2, {
|
|
@@ -33277,26 +33886,42 @@ function UserProfile({
|
|
|
33277
33886
|
|
|
33278
33887
|
var ActionType;
|
|
33279
33888
|
(function (ActionType) {
|
|
33889
|
+
ActionType["clearPending"] = "clearPending";
|
|
33280
33890
|
ActionType["close"] = "close";
|
|
33281
33891
|
ActionType["mount"] = "mount";
|
|
33282
33892
|
ActionType["open"] = "open";
|
|
33893
|
+
ActionType["setPending"] = "setPending";
|
|
33283
33894
|
ActionType["unmount"] = "unmount";
|
|
33284
33895
|
})(ActionType || (ActionType = {}));
|
|
33285
33896
|
const initialState = {
|
|
33286
33897
|
currentContent: null,
|
|
33287
|
-
open: false
|
|
33898
|
+
open: false,
|
|
33899
|
+
pending: null
|
|
33288
33900
|
};
|
|
33289
33901
|
const reducer = (state, action) => {
|
|
33902
|
+
var _a, _b;
|
|
33290
33903
|
switch (action.type) {
|
|
33291
33904
|
case ActionType.mount:
|
|
33292
33905
|
return {
|
|
33293
33906
|
...state,
|
|
33294
|
-
currentContent: action.payload.content
|
|
33907
|
+
currentContent: action.payload.content,
|
|
33908
|
+
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
33909
|
};
|
|
33296
33910
|
case ActionType.unmount:
|
|
33297
33911
|
return {
|
|
33298
33912
|
...state,
|
|
33299
|
-
currentContent: null
|
|
33913
|
+
currentContent: null,
|
|
33914
|
+
pending: null
|
|
33915
|
+
};
|
|
33916
|
+
case ActionType.setPending:
|
|
33917
|
+
return {
|
|
33918
|
+
...state,
|
|
33919
|
+
pending: action.payload
|
|
33920
|
+
};
|
|
33921
|
+
case ActionType.clearPending:
|
|
33922
|
+
return {
|
|
33923
|
+
...state,
|
|
33924
|
+
pending: null
|
|
33300
33925
|
};
|
|
33301
33926
|
case ActionType.open:
|
|
33302
33927
|
return {
|
|
@@ -33315,13 +33940,181 @@ const useToolbarReducer = overrides => React.useReducer(reducer, {
|
|
|
33315
33940
|
...overrides
|
|
33316
33941
|
});
|
|
33317
33942
|
|
|
33318
|
-
var styles$
|
|
33943
|
+
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
33944
|
|
|
33320
|
-
|
|
33945
|
+
// PCO Box serves TLS by default, and a bare http:// frame inside an https
|
|
33946
|
+
// product page is blocked as mixed content, so follow the page's scheme.
|
|
33947
|
+
const iframeSrc = () => `${window.location.protocol}//pco.test/iframe`;
|
|
33948
|
+
const STAFF_PINK = "var(--t-fill-color-product-staff)";
|
|
33949
|
+
// The pco.test domain is only ever served by a local PCO Box, so the tool
|
|
33950
|
+
// stays hidden in staging and production — and on Topbar's own dev server,
|
|
33951
|
+
// which runs on localhost and has no pco.test to frame.
|
|
33952
|
+
function isPcoTestHost() {
|
|
33953
|
+
const {
|
|
33954
|
+
hostname
|
|
33955
|
+
} = window.location;
|
|
33956
|
+
return hostname === "pco.test" || hostname.endsWith(".pco.test");
|
|
33957
|
+
}
|
|
33958
|
+
// Flattened from the source artwork's two outline paths — its gradients,
|
|
33959
|
+
// filters and clip paths carry nothing at icon size. Those paths are authored
|
|
33960
|
+
// on a 128px canvas, hence the transform: it fits the pair into the same ~24px
|
|
33961
|
+
// optical box the sibling toolbar icons occupy. Filled explicitly rather than
|
|
33962
|
+
// with `currentColor` so the mark stays staff pink instead of inheriting the
|
|
33963
|
+
// toolbar's text color and its hover/active states.
|
|
33964
|
+
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";
|
|
33965
|
+
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";
|
|
33966
|
+
function ToolsIcon() {
|
|
33967
|
+
return /*#__PURE__*/React__default.createElement("svg", {
|
|
33968
|
+
"aria-hidden": "true",
|
|
33969
|
+
width: "32",
|
|
33970
|
+
height: "32",
|
|
33971
|
+
viewBox: "0 0 32 32",
|
|
33972
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
33973
|
+
}, /*#__PURE__*/React__default.createElement("g", {
|
|
33974
|
+
fill: STAFF_PINK,
|
|
33975
|
+
transform: "translate(2.92 2.66) scale(.204)"
|
|
33976
|
+
}, /*#__PURE__*/React__default.createElement("path", {
|
|
33977
|
+
d: WRENCH
|
|
33978
|
+
}), /*#__PURE__*/React__default.createElement("path", {
|
|
33979
|
+
d: SCREWDRIVER
|
|
33980
|
+
})));
|
|
33981
|
+
}
|
|
33982
|
+
// A cross-origin frame's own history object is unreachable (touching it throws
|
|
33983
|
+
// a SecurityError), but the frame's navigations do land in the top-level joint
|
|
33984
|
+
// session history, so the *parent's* back/forward traverse them while leaving
|
|
33985
|
+
// the host page in place. Counting frame loads tracks how deep we are, which is
|
|
33986
|
+
// what keeps a click on Back from stepping past the frame's first page and
|
|
33987
|
+
// navigating the host app instead. Loads we caused are attributed to the
|
|
33988
|
+
// traversal that requested them rather than counted as new navigations.
|
|
33989
|
+
function useFrameHistory() {
|
|
33990
|
+
const [steps, setSteps] = useState({
|
|
33991
|
+
back: 0,
|
|
33992
|
+
forward: 0
|
|
33993
|
+
});
|
|
33994
|
+
// Traversal has to read the current counts outside of render, so the ref is
|
|
33995
|
+
// the source of truth and state exists to paint the buttons.
|
|
33996
|
+
const stepsRef = useRef(steps);
|
|
33997
|
+
const pendingTraversal = useRef(null);
|
|
33998
|
+
const frameIsLoaded = useRef(false);
|
|
33999
|
+
const moveTo = useCallback(next => {
|
|
34000
|
+
stepsRef.current = next;
|
|
34001
|
+
setSteps(next);
|
|
34002
|
+
}, []);
|
|
34003
|
+
const onFrameLoad = useCallback(() => {
|
|
34004
|
+
if (!frameIsLoaded.current) {
|
|
34005
|
+
// The frame's first page is the floor, not a step.
|
|
34006
|
+
frameIsLoaded.current = true;
|
|
34007
|
+
} else {
|
|
34008
|
+
const traversal = pendingTraversal.current;
|
|
34009
|
+
pendingTraversal.current = null;
|
|
34010
|
+
const {
|
|
34011
|
+
back,
|
|
34012
|
+
forward
|
|
34013
|
+
} = stepsRef.current;
|
|
34014
|
+
if (traversal === "back") {
|
|
34015
|
+
moveTo({
|
|
34016
|
+
back: back - 1,
|
|
34017
|
+
forward: forward + 1
|
|
34018
|
+
});
|
|
34019
|
+
} else if (traversal === "forward") {
|
|
34020
|
+
moveTo({
|
|
34021
|
+
back: back + 1,
|
|
34022
|
+
forward: forward - 1
|
|
34023
|
+
});
|
|
34024
|
+
} else {
|
|
34025
|
+
moveTo({
|
|
34026
|
+
back: back + 1,
|
|
34027
|
+
forward: 0
|
|
34028
|
+
});
|
|
34029
|
+
}
|
|
34030
|
+
}
|
|
34031
|
+
}, [moveTo]);
|
|
34032
|
+
// Guarded here rather than trusting the button's disabled state: traversing
|
|
34033
|
+
// with no frame step left would navigate the host app out from under us.
|
|
34034
|
+
const traverse = useCallback(direction => {
|
|
34035
|
+
if (stepsRef.current[direction] > 0) {
|
|
34036
|
+
pendingTraversal.current = direction;
|
|
34037
|
+
window.history[direction]();
|
|
34038
|
+
}
|
|
34039
|
+
}, []);
|
|
34040
|
+
return {
|
|
34041
|
+
onFrameLoad,
|
|
34042
|
+
steps,
|
|
34043
|
+
traverse
|
|
34044
|
+
};
|
|
34045
|
+
}
|
|
34046
|
+
function NavButton({
|
|
34047
|
+
direction,
|
|
34048
|
+
disabled,
|
|
34049
|
+
onClick
|
|
34050
|
+
}) {
|
|
34051
|
+
return /*#__PURE__*/React__default.createElement(Button$1
|
|
34052
|
+
// Tapestry's `disabled` suppresses the click but emits no ARIA state.
|
|
34053
|
+
, {
|
|
34054
|
+
"aria-disabled": disabled,
|
|
34055
|
+
disabled: disabled,
|
|
34056
|
+
hover: {
|
|
34057
|
+
backgroundColor: "var(--t-fill-color-neutral-070)"
|
|
34058
|
+
},
|
|
34059
|
+
icon: {
|
|
34060
|
+
fill: "var(--t-icon-color-dim)",
|
|
34061
|
+
name: direction === "back" ? "general.leftChevron" : "general.rightChevron",
|
|
34062
|
+
size: "md"
|
|
34063
|
+
},
|
|
34064
|
+
onClick: onClick,
|
|
34065
|
+
opacity: disabled ? 0.35 : 1,
|
|
34066
|
+
theme: false,
|
|
34067
|
+
title: direction === "back" ? "Back" : "Forward"
|
|
34068
|
+
});
|
|
34069
|
+
}
|
|
34070
|
+
function DeveloperToolsComponent({
|
|
34071
|
+
closeDrawer
|
|
34072
|
+
}) {
|
|
34073
|
+
const {
|
|
34074
|
+
onFrameLoad,
|
|
34075
|
+
steps,
|
|
34076
|
+
traverse
|
|
34077
|
+
} = useFrameHistory();
|
|
34078
|
+
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
34079
|
+
closeDrawer: closeDrawer
|
|
34080
|
+
}, /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
34081
|
+
alignment: "center",
|
|
34082
|
+
axis: "horizontal",
|
|
34083
|
+
spacing: 0.25
|
|
34084
|
+
}, /*#__PURE__*/React__default.createElement(NavButton, {
|
|
34085
|
+
direction: "back",
|
|
34086
|
+
disabled: steps.back === 0,
|
|
34087
|
+
onClick: () => traverse("back")
|
|
34088
|
+
}), /*#__PURE__*/React__default.createElement(NavButton, {
|
|
34089
|
+
direction: "forward",
|
|
34090
|
+
disabled: steps.forward === 0,
|
|
34091
|
+
onClick: () => traverse("forward")
|
|
34092
|
+
}), /*#__PURE__*/React__default.createElement(DrawerHeading, {
|
|
34093
|
+
paddingLeft: 0.5
|
|
34094
|
+
}, "Developer Tools"))), /*#__PURE__*/React__default.createElement(Box, {
|
|
34095
|
+
as: "iframe",
|
|
34096
|
+
border: "none",
|
|
34097
|
+
flex: "1",
|
|
34098
|
+
minHeight: 0,
|
|
34099
|
+
onLoad: onFrameLoad,
|
|
34100
|
+
src: iframeSrc(),
|
|
34101
|
+
title: "Developer Tools",
|
|
34102
|
+
width: "100%"
|
|
34103
|
+
}));
|
|
34104
|
+
}
|
|
34105
|
+
const DeveloperTools = {
|
|
34106
|
+
component: DeveloperToolsComponent,
|
|
34107
|
+
icons: {
|
|
34108
|
+
component: ToolsIcon
|
|
34109
|
+
},
|
|
34110
|
+
label: "Developer Tools",
|
|
34111
|
+
// No space: `name` becomes an element id and a pendo attribute.
|
|
34112
|
+
name: "DeveloperTools"
|
|
34113
|
+
};
|
|
33321
34114
|
|
|
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"};
|
|
34115
|
+
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
34116
|
|
|
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";
|
|
34117
|
+
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
34118
|
// Same stops as the AskAIBanner border gradient, so the two stay in sync.
|
|
33326
34119
|
const GRADIENT_STOPS = [{
|
|
33327
34120
|
color: "var(--t-fill-color-tag-teal-040)",
|
|
@@ -33370,7 +34163,7 @@ function SparkleIcon({
|
|
|
33370
34163
|
stopColor: color
|
|
33371
34164
|
}
|
|
33372
34165
|
})))))), /*#__PURE__*/React__default.createElement("path", {
|
|
33373
|
-
d: SPARKLE_PATH,
|
|
34166
|
+
d: SPARKLE_PATH$1,
|
|
33374
34167
|
fill: isGradient ? `url(#${gradientId})` : "currentColor"
|
|
33375
34168
|
}));
|
|
33376
34169
|
}
|
|
@@ -33379,8 +34172,8 @@ function AskAIBanner(props) {
|
|
|
33379
34172
|
var _a, _b;
|
|
33380
34173
|
const isStacked = props.variant !== "inline";
|
|
33381
34174
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
33382
|
-
className: classnames(styles.banner, {
|
|
33383
|
-
[styles.stacked]: isStacked
|
|
34175
|
+
className: classnames(styles$1.banner, {
|
|
34176
|
+
[styles$1.stacked]: isStacked
|
|
33384
34177
|
}),
|
|
33385
34178
|
onClick: () => props.onClick(),
|
|
33386
34179
|
type: "button"
|
|
@@ -33395,15 +34188,15 @@ function StackedContent({
|
|
|
33395
34188
|
title
|
|
33396
34189
|
}) {
|
|
33397
34190
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
|
|
33398
|
-
className: styles.content
|
|
34191
|
+
className: styles$1.content
|
|
33399
34192
|
}, /*#__PURE__*/React__default.createElement(AskAIIcon, null), /*#__PURE__*/React__default.createElement("span", {
|
|
33400
|
-
className: styles.text
|
|
34193
|
+
className: styles$1.text
|
|
33401
34194
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
33402
|
-
className: styles.title
|
|
34195
|
+
className: styles$1.title
|
|
33403
34196
|
}, 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,
|
|
34197
|
+
className: styles$1.subtitle
|
|
34198
|
+
}, "Support answers tailored to you"))), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
34199
|
+
className: styles$1.chevron,
|
|
33407
34200
|
name: "general.rightChevron",
|
|
33408
34201
|
size: "16px"
|
|
33409
34202
|
}));
|
|
@@ -33413,18 +34206,18 @@ function InlineContent({
|
|
|
33413
34206
|
title
|
|
33414
34207
|
}) {
|
|
33415
34208
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement("span", {
|
|
33416
|
-
className: styles.inlineContent
|
|
34209
|
+
className: styles$1.inlineContent
|
|
33417
34210
|
}, /*#__PURE__*/React__default.createElement(SparkleIcon, {
|
|
33418
|
-
className: styles.sparkle,
|
|
34211
|
+
className: styles$1.sparkle,
|
|
33419
34212
|
fill: "currentColor"
|
|
33420
34213
|
}), /*#__PURE__*/React__default.createElement("span", {
|
|
33421
|
-
className: styles.inlineTitle
|
|
34214
|
+
className: styles$1.inlineTitle
|
|
33422
34215
|
}, title)), /*#__PURE__*/React__default.createElement("span", {
|
|
33423
|
-
className: styles.action
|
|
34216
|
+
className: styles$1.action
|
|
33424
34217
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
33425
|
-
className: styles.actionLabel
|
|
33426
|
-
}, action), /*#__PURE__*/React__default.createElement(Icon, {
|
|
33427
|
-
className: styles.actionChevron,
|
|
34218
|
+
className: styles$1.actionLabel
|
|
34219
|
+
}, action), /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
34220
|
+
className: styles$1.actionChevron,
|
|
33428
34221
|
name: "general.rightChevron",
|
|
33429
34222
|
size: "14px"
|
|
33430
34223
|
})));
|
|
@@ -33441,9 +34234,9 @@ function AskAIIcon() {
|
|
|
33441
34234
|
const borderA = `${uid}-border-a`;
|
|
33442
34235
|
const borderB = `${uid}-border-b`;
|
|
33443
34236
|
return /*#__PURE__*/React__default.createElement("span", {
|
|
33444
|
-
className: styles.icon
|
|
34237
|
+
className: styles$1.icon
|
|
33445
34238
|
}, /*#__PURE__*/React__default.createElement("svg", {
|
|
33446
|
-
className: styles.iconShape,
|
|
34239
|
+
className: styles$1.iconShape,
|
|
33447
34240
|
viewBox: "0 0 32 32",
|
|
33448
34241
|
fill: "none",
|
|
33449
34242
|
"aria-hidden": "true"
|
|
@@ -33614,7 +34407,7 @@ function AskAIIcon() {
|
|
|
33614
34407
|
stopColor: "#D48B91",
|
|
33615
34408
|
stopOpacity: "0"
|
|
33616
34409
|
})))), /*#__PURE__*/React__default.createElement("svg", {
|
|
33617
|
-
className: styles.iconSparkle,
|
|
34410
|
+
className: styles$1.iconSparkle,
|
|
33618
34411
|
viewBox: "0 0 19.4995 17.9999",
|
|
33619
34412
|
"aria-hidden": "true"
|
|
33620
34413
|
}, /*#__PURE__*/React__default.createElement("path", {
|
|
@@ -33623,6 +34416,23 @@ function AskAIIcon() {
|
|
|
33623
34416
|
})));
|
|
33624
34417
|
}
|
|
33625
34418
|
|
|
34419
|
+
var styles = {"button":"button-2NVI1","sparkle":"sparkle-ruJGP","label":"label-4NUGB"};
|
|
34420
|
+
|
|
34421
|
+
function AskAIButton({
|
|
34422
|
+
onClick,
|
|
34423
|
+
text = "Ask AI"
|
|
34424
|
+
}) {
|
|
34425
|
+
return /*#__PURE__*/React__default.createElement("button", {
|
|
34426
|
+
className: styles.button,
|
|
34427
|
+
onClick: () => onClick(),
|
|
34428
|
+
type: "button"
|
|
34429
|
+
}, /*#__PURE__*/React__default.createElement(SparkleIcon, {
|
|
34430
|
+
className: styles.sparkle
|
|
34431
|
+
}), /*#__PURE__*/React__default.createElement("span", {
|
|
34432
|
+
className: styles.label
|
|
34433
|
+
}, text));
|
|
34434
|
+
}
|
|
34435
|
+
|
|
33626
34436
|
// The article heading lives in the deepest populated hierarchy level (lvl0 is
|
|
33627
34437
|
// always "Planning Center").
|
|
33628
34438
|
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 +34456,7 @@ const HeadingIcon = ({
|
|
|
33646
34456
|
const logoName = appName.toLowerCase().replace(/\s+/g, "-");
|
|
33647
34457
|
// Accounts has no tapestry Logo, so use the cog icon
|
|
33648
34458
|
if (logoName === "accounts") {
|
|
33649
|
-
return /*#__PURE__*/React__default.createElement(Icon, {
|
|
34459
|
+
return /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33650
34460
|
name: "general.cog",
|
|
33651
34461
|
color: "foregroundSecondary"
|
|
33652
34462
|
});
|
|
@@ -33710,7 +34520,7 @@ const CustomSearchBox = ({
|
|
|
33710
34520
|
},
|
|
33711
34521
|
placeholder: "Search our documentation",
|
|
33712
34522
|
value: query,
|
|
33713
|
-
renderRight: /*#__PURE__*/React__default.createElement(Icon, {
|
|
34523
|
+
renderRight: /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
33714
34524
|
name: "general.search"
|
|
33715
34525
|
}),
|
|
33716
34526
|
onChange: e => setQuery(e.target.value),
|
|
@@ -33778,16 +34588,24 @@ const DocumentationSearch = ({
|
|
|
33778
34588
|
// not the path, so updating it is a same-document navigation that doesn't
|
|
33779
34589
|
// reload the iframe, but also lets the route survive a forced reload (e.g.
|
|
33780
34590
|
// Turbo re-parenting the topbar).
|
|
33781
|
-
|
|
34591
|
+
//
|
|
34592
|
+
// `panel` loads picobot as its own toolbar panel rather than inside Helpdesk.
|
|
34593
|
+
function buildPicobotSrc(origin, path, {
|
|
34594
|
+
panel = false
|
|
34595
|
+
} = {}) {
|
|
33782
34596
|
const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
|
|
33783
34597
|
const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
|
|
33784
34598
|
const fragment = queryPart !== undefined ? `${safePath}?${queryPart}` : safePath;
|
|
33785
|
-
const
|
|
34599
|
+
const url = new URL("topbar", origin);
|
|
34600
|
+
if (panel) url.searchParams.set("panel", "1");
|
|
34601
|
+
const base = url.toString();
|
|
33786
34602
|
return fragment ? `${base}#${fragment}` : base;
|
|
33787
34603
|
}
|
|
33788
34604
|
|
|
33789
34605
|
const LOAD_TIMEOUT_MS = 3000;
|
|
33790
|
-
function createPicobotIframe(picobotOrigin
|
|
34606
|
+
function createPicobotIframe(picobotOrigin, {
|
|
34607
|
+
panel = false
|
|
34608
|
+
} = {}) {
|
|
33791
34609
|
return /*#__PURE__*/React__default.forwardRef(function PicobotIframe({
|
|
33792
34610
|
currentPath: iframePath,
|
|
33793
34611
|
onChangePath: onIframeChangePath,
|
|
@@ -33843,7 +34661,9 @@ function createPicobotIframe(picobotOrigin) {
|
|
|
33843
34661
|
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, picobotOrigin);
|
|
33844
34662
|
}
|
|
33845
34663
|
}), []);
|
|
33846
|
-
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath
|
|
34664
|
+
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath, {
|
|
34665
|
+
panel
|
|
34666
|
+
});
|
|
33847
34667
|
return /*#__PURE__*/React__default.createElement("iframe", {
|
|
33848
34668
|
ref: iframeRef,
|
|
33849
34669
|
src: iframeSrc,
|
|
@@ -33882,22 +34702,28 @@ function HelpdeskComponent({
|
|
|
33882
34702
|
currentPath,
|
|
33883
34703
|
env,
|
|
33884
34704
|
helpdeskConfig,
|
|
33885
|
-
|
|
34705
|
+
isPicobotLoading,
|
|
34706
|
+
onChangePath,
|
|
34707
|
+
openPicobot
|
|
33886
34708
|
}) {
|
|
33887
34709
|
const PicobotIframe = React__default.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot")), [env]);
|
|
34710
|
+
const loadingFallback = /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
34711
|
+
flex: 1,
|
|
34712
|
+
axis: "vertical"
|
|
34713
|
+
}, /*#__PURE__*/React__default.createElement(DrawerHeader, {
|
|
34714
|
+
closeDrawer: closeDrawer
|
|
34715
|
+
}, /*#__PURE__*/React__default.createElement(StackView$1, {
|
|
34716
|
+
axis: "horizontal",
|
|
34717
|
+
spacing: 1
|
|
34718
|
+
}, /*#__PURE__*/React__default.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React__default.createElement(LoadingIndicator$1, null))));
|
|
34719
|
+
// The add-on reads its args once, so mounting before the gate answers
|
|
34720
|
+
// would leave out openPicobot for good.
|
|
34721
|
+
if (isPicobotLoading) return loadingFallback;
|
|
33888
34722
|
return /*#__PURE__*/React__default.createElement(UiExtensionFirstPartyComponent, {
|
|
33889
34723
|
grow: 1,
|
|
33890
34724
|
display: "flex",
|
|
33891
34725
|
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)))),
|
|
34726
|
+
loadingFallback: loadingFallback,
|
|
33901
34727
|
additionalComponents: {
|
|
33902
34728
|
AskAIBanner: {
|
|
33903
34729
|
component: AskAIBanner,
|
|
@@ -33917,6 +34743,18 @@ function HelpdeskComponent({
|
|
|
33917
34743
|
}
|
|
33918
34744
|
}
|
|
33919
34745
|
},
|
|
34746
|
+
AskAIButton: {
|
|
34747
|
+
component: AskAIButton,
|
|
34748
|
+
properties: {
|
|
34749
|
+
onClick: {
|
|
34750
|
+
event: false,
|
|
34751
|
+
type: Function
|
|
34752
|
+
},
|
|
34753
|
+
text: {
|
|
34754
|
+
type: String
|
|
34755
|
+
}
|
|
34756
|
+
}
|
|
34757
|
+
},
|
|
33920
34758
|
CalendlyWidget: {
|
|
33921
34759
|
component: CalendlyWidget,
|
|
33922
34760
|
properties: {
|
|
@@ -33995,7 +34833,11 @@ function HelpdeskComponent({
|
|
|
33995
34833
|
currentPageParams: location.search,
|
|
33996
34834
|
currentPath,
|
|
33997
34835
|
helpdeskConfig,
|
|
33998
|
-
onChangePath
|
|
34836
|
+
onChangePath,
|
|
34837
|
+
// Its presence tells helpdesk that Picobot has its own panel.
|
|
34838
|
+
...(openPicobot && {
|
|
34839
|
+
openPicobot
|
|
34840
|
+
})
|
|
33999
34841
|
}
|
|
34000
34842
|
});
|
|
34001
34843
|
}
|
|
@@ -34542,6 +35384,142 @@ const Notifications = {
|
|
|
34542
35384
|
name: "Notifications"
|
|
34543
35385
|
};
|
|
34544
35386
|
|
|
35387
|
+
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";
|
|
35388
|
+
// Helpdesk reads this context off the email path to open its email form the
|
|
35389
|
+
// same way a handoff inside its own drawer does.
|
|
35390
|
+
function emailPathWithContext(context = {}) {
|
|
35391
|
+
const params = new URLSearchParams();
|
|
35392
|
+
for (const key of ["conversationId", "source", "turnIndex"]) {
|
|
35393
|
+
const value = context[key];
|
|
35394
|
+
if (value !== null && value !== undefined) params.set(key, String(value));
|
|
35395
|
+
}
|
|
35396
|
+
return `email?${params}`;
|
|
35397
|
+
}
|
|
35398
|
+
function PicobotComponent({
|
|
35399
|
+
clearPending,
|
|
35400
|
+
closeDrawer,
|
|
35401
|
+
currentPath,
|
|
35402
|
+
env,
|
|
35403
|
+
onChangePath,
|
|
35404
|
+
openTool,
|
|
35405
|
+
pending
|
|
35406
|
+
}) {
|
|
35407
|
+
const PicobotIframe = React__default.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot"), {
|
|
35408
|
+
panel: true
|
|
35409
|
+
}), [env]);
|
|
35410
|
+
const picobotRef = React__default.useRef(null);
|
|
35411
|
+
const [isPicobotReady, setIsPicobotReady] = React__default.useState(false);
|
|
35412
|
+
// Picobot has no listener until it sends READY, so openPicobot's draft or
|
|
35413
|
+
// conversation waits for it.
|
|
35414
|
+
React__default.useEffect(function handOverPendingOncePicobotIsReady() {
|
|
35415
|
+
var _a, _b;
|
|
35416
|
+
if (!pending || !isPicobotReady) return;
|
|
35417
|
+
const {
|
|
35418
|
+
conversationId,
|
|
35419
|
+
draft,
|
|
35420
|
+
ticketExternalId
|
|
35421
|
+
} = pending;
|
|
35422
|
+
if (conversationId) {
|
|
35423
|
+
(_a = picobotRef.current) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
35424
|
+
payload: {
|
|
35425
|
+
conversation_id: conversationId,
|
|
35426
|
+
// Picobot asks whether this resolved the ticket, and closes it.
|
|
35427
|
+
...(ticketExternalId && {
|
|
35428
|
+
external_id: ticketExternalId,
|
|
35429
|
+
source: "ticket_submitted"
|
|
35430
|
+
})
|
|
35431
|
+
},
|
|
35432
|
+
type: "LOAD_CONVERSATION"
|
|
35433
|
+
});
|
|
35434
|
+
} else if (draft) {
|
|
35435
|
+
// Not both: Picobot cancels a conversation load when a prefill
|
|
35436
|
+
// starts a new conversation.
|
|
35437
|
+
(_b = picobotRef.current) === null || _b === void 0 ? void 0 : _b.postMessage({
|
|
35438
|
+
payload: {
|
|
35439
|
+
source: "email_form",
|
|
35440
|
+
text: draft
|
|
35441
|
+
},
|
|
35442
|
+
type: "PREFILL_COMPOSER"
|
|
35443
|
+
});
|
|
35444
|
+
}
|
|
35445
|
+
clearPending();
|
|
35446
|
+
}, [pending, isPicobotReady, clearPending]);
|
|
35447
|
+
const [iframeErrored, setIframeErrored] = React__default.useState(false);
|
|
35448
|
+
const [mountKey, setMountKey] = React__default.useState(0);
|
|
35449
|
+
const handleRetry = React__default.useCallback(() => {
|
|
35450
|
+
setIframeErrored(false);
|
|
35451
|
+
setIsPicobotReady(false);
|
|
35452
|
+
setMountKey(key => key + 1);
|
|
35453
|
+
}, []);
|
|
35454
|
+
const handleMessage = React__default.useCallback(data => {
|
|
35455
|
+
var _a;
|
|
35456
|
+
const message = data;
|
|
35457
|
+
switch (message === null || message === void 0 ? void 0 : message.type) {
|
|
35458
|
+
case "CLOSE_DRAWER":
|
|
35459
|
+
return closeDrawer();
|
|
35460
|
+
case "READY":
|
|
35461
|
+
return setIsPicobotReady(true);
|
|
35462
|
+
case "PICOBOT_SUPPORT_HANDOFF":
|
|
35463
|
+
return openTool("helpdesk", ((_a = message.payload) === null || _a === void 0 ? void 0 : _a.method) === "zoom" ? "phone" : emailPathWithContext(message.payload));
|
|
35464
|
+
case "IFRAME_LOAD_ERROR":
|
|
35465
|
+
console.warn("topbar: Picobot iframe failed to load");
|
|
35466
|
+
return setIframeErrored(true);
|
|
35467
|
+
}
|
|
35468
|
+
}, [closeDrawer, openTool]);
|
|
35469
|
+
if (iframeErrored) {
|
|
35470
|
+
return /*#__PURE__*/React__default.createElement(LoadError, {
|
|
35471
|
+
onContactSupport: () => openTool("helpdesk", "email"),
|
|
35472
|
+
onRetry: handleRetry
|
|
35473
|
+
});
|
|
35474
|
+
}
|
|
35475
|
+
return /*#__PURE__*/React__default.createElement(PicobotIframe, {
|
|
35476
|
+
key: mountKey,
|
|
35477
|
+
ref: picobotRef,
|
|
35478
|
+
currentPath: currentPath,
|
|
35479
|
+
onChangePath: onChangePath,
|
|
35480
|
+
onMessage: handleMessage
|
|
35481
|
+
});
|
|
35482
|
+
}
|
|
35483
|
+
function LoadError({
|
|
35484
|
+
onContactSupport,
|
|
35485
|
+
onRetry
|
|
35486
|
+
}) {
|
|
35487
|
+
return /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
35488
|
+
axis: "vertical",
|
|
35489
|
+
flex: 1,
|
|
35490
|
+
alignment: "center",
|
|
35491
|
+
distribution: "center",
|
|
35492
|
+
padding: 3,
|
|
35493
|
+
spacing: 1
|
|
35494
|
+
}, /*#__PURE__*/React__default.createElement(Heading, {
|
|
35495
|
+
level: 2
|
|
35496
|
+
}, "Picobot is temporarily unavailable"), /*#__PURE__*/React__default.createElement(Text, {
|
|
35497
|
+
size: 5,
|
|
35498
|
+
color: "var(--t-text-color-secondary)"
|
|
35499
|
+
}, "Try again or contact support."), /*#__PURE__*/React__default.createElement(Box, {
|
|
35500
|
+
paddingTop: 2
|
|
35501
|
+
}, /*#__PURE__*/React__default.createElement(Button$1, {
|
|
35502
|
+
onClick: onRetry,
|
|
35503
|
+
iconLeft: "general.refresh",
|
|
35504
|
+
theme: "primary",
|
|
35505
|
+
variant: "fill",
|
|
35506
|
+
title: "Retry"
|
|
35507
|
+
})), /*#__PURE__*/React__default.createElement(Button$1, {
|
|
35508
|
+
onClick: onContactSupport,
|
|
35509
|
+
variant: "naked",
|
|
35510
|
+
theme: "primary",
|
|
35511
|
+
title: "Contact support"
|
|
35512
|
+
}));
|
|
35513
|
+
}
|
|
35514
|
+
const Picobot = {
|
|
35515
|
+
component: PicobotComponent,
|
|
35516
|
+
icons: {
|
|
35517
|
+
staticDotPath: SPARKLE_PATH,
|
|
35518
|
+
staticPath: SPARKLE_PATH
|
|
35519
|
+
},
|
|
35520
|
+
name: "Picobot"
|
|
35521
|
+
};
|
|
35522
|
+
|
|
34545
35523
|
const {
|
|
34546
35524
|
LoadingIndicator,
|
|
34547
35525
|
StackView
|
|
@@ -34937,6 +35915,41 @@ const useChatConfig = ({
|
|
|
34937
35915
|
};
|
|
34938
35916
|
};
|
|
34939
35917
|
|
|
35918
|
+
// Picobot's API rejects requests without a version header, and apiRequest
|
|
35919
|
+
// sets its own headers, so the version rides in on the fetch.
|
|
35920
|
+
const picobotFetch = (url, opts) => defaultFetch(url, {
|
|
35921
|
+
...opts,
|
|
35922
|
+
headers: {
|
|
35923
|
+
...(opts === null || opts === void 0 ? void 0 : opts.headers),
|
|
35924
|
+
"X-PCO-API-Version": "2026-08-11"
|
|
35925
|
+
}
|
|
35926
|
+
});
|
|
35927
|
+
// Routes wait on the gate, so a slow Picobot can't hold them up for long.
|
|
35928
|
+
const GATE_TIMEOUT_MS = 3000;
|
|
35929
|
+
const usePicobotConfig = ({
|
|
35930
|
+
isEnabled
|
|
35931
|
+
}) => {
|
|
35932
|
+
// Undefined until Picobot answers.
|
|
35933
|
+
const [panelEnabled, setPanelEnabled] = useState();
|
|
35934
|
+
useEffect(() => {
|
|
35935
|
+
if (!isEnabled) return undefined;
|
|
35936
|
+
// Whatever settles first wins, so a late answer can't reopen the gate.
|
|
35937
|
+
const settle = enabled => setPanelEnabled(current => current !== null && current !== void 0 ? current : enabled);
|
|
35938
|
+
const timeout = setTimeout(() => settle(false), GATE_TIMEOUT_MS);
|
|
35939
|
+
apiRequest(picobotFetch, `${window.location.origin}/~api/picobot/v2/me`).then(({
|
|
35940
|
+
json
|
|
35941
|
+
}) => {
|
|
35942
|
+
var _a, _b;
|
|
35943
|
+
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);
|
|
35944
|
+
}).catch(() => settle(false)).finally(() => clearTimeout(timeout));
|
|
35945
|
+
return () => clearTimeout(timeout);
|
|
35946
|
+
}, [isEnabled]);
|
|
35947
|
+
return {
|
|
35948
|
+
displayPicobotIcon: panelEnabled === true,
|
|
35949
|
+
isLoading: isEnabled && panelEnabled === undefined
|
|
35950
|
+
};
|
|
35951
|
+
};
|
|
35952
|
+
|
|
34940
35953
|
const Profile = {
|
|
34941
35954
|
component: UserProfile,
|
|
34942
35955
|
icons: {
|
|
@@ -34947,10 +35960,16 @@ const Profile = {
|
|
|
34947
35960
|
};
|
|
34948
35961
|
|
|
34949
35962
|
const DURATION = 100;
|
|
35963
|
+
// Exempt paths match regardless of query, so `email?conversationId=…` is
|
|
35964
|
+
// exempt along with `email`.
|
|
34950
35965
|
function isToolPathExemptFromStorage(toolName, path) {
|
|
34951
35966
|
var _a, _b;
|
|
34952
35967
|
const tool = toolsRouteMap[toolName];
|
|
34953
|
-
|
|
35968
|
+
const [pathWithoutQuery] = path.split("?");
|
|
35969
|
+
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;
|
|
35970
|
+
}
|
|
35971
|
+
function isConversationPath(path) {
|
|
35972
|
+
return Boolean(path === null || path === void 0 ? void 0 : path.startsWith("conversations/"));
|
|
34954
35973
|
}
|
|
34955
35974
|
function scrollToolDrawerToTop(toolName) {
|
|
34956
35975
|
var _a;
|
|
@@ -34963,6 +35982,7 @@ const TOOLS_MAP = {
|
|
|
34963
35982
|
Chat,
|
|
34964
35983
|
Helpdesk,
|
|
34965
35984
|
Notifications,
|
|
35985
|
+
Picobot,
|
|
34966
35986
|
Profile,
|
|
34967
35987
|
Tasks
|
|
34968
35988
|
};
|
|
@@ -34990,7 +36010,8 @@ function Toolbar({
|
|
|
34990
36010
|
});
|
|
34991
36011
|
const {
|
|
34992
36012
|
currentContent,
|
|
34993
|
-
open
|
|
36013
|
+
open,
|
|
36014
|
+
pending
|
|
34994
36015
|
} = state;
|
|
34995
36016
|
const mounted = !!currentContent;
|
|
34996
36017
|
const storeData = useStore();
|
|
@@ -35003,6 +36024,15 @@ function Toolbar({
|
|
|
35003
36024
|
...(env === "development" ? _storeDataOverride !== null && _storeDataOverride !== void 0 ? _storeDataOverride : {} : {})
|
|
35004
36025
|
};
|
|
35005
36026
|
const updateStore = useUpdateStore();
|
|
36027
|
+
const {
|
|
36028
|
+
displayPicobotIcon,
|
|
36029
|
+
isLoading: isPicobotGateLoading
|
|
36030
|
+
} = usePicobotConfig({
|
|
36031
|
+
isEnabled: Boolean(userId)
|
|
36032
|
+
});
|
|
36033
|
+
// Picobot is a kill-switched tool: until its gate says the panel is
|
|
36034
|
+
// enabled, its routes are held, and if it never does, they're ignored.
|
|
36035
|
+
const routableTool = useCallback(name => name === Picobot.name.toLowerCase() && !displayPicobotIcon ? undefined : toolsRouteMap[name], [displayPicobotIcon]);
|
|
35006
36036
|
React__default.useEffect(() => {
|
|
35007
36037
|
if (open) {
|
|
35008
36038
|
document.body.style.overflow = "hidden";
|
|
@@ -35090,7 +36120,9 @@ function Toolbar({
|
|
|
35090
36120
|
});
|
|
35091
36121
|
}, [clearRoute]);
|
|
35092
36122
|
React__default.useEffect(() => {
|
|
35093
|
-
if (!toolbarFragmentRoute)
|
|
36123
|
+
if (!toolbarFragmentRoute || !routableTool(toolbarFragmentRoute.tool)) {
|
|
36124
|
+
return;
|
|
36125
|
+
}
|
|
35094
36126
|
const isExempt = isToolPathExemptFromStorage(toolbarFragmentRoute.tool, toolbarFragmentRoute.path);
|
|
35095
36127
|
if (isExempt) return;
|
|
35096
36128
|
const nextState = {
|
|
@@ -35100,12 +36132,32 @@ function Toolbar({
|
|
|
35100
36132
|
if (!shallowCompare(nextState, toolStateCookie)) {
|
|
35101
36133
|
setToolStateCookie(nextState);
|
|
35102
36134
|
}
|
|
35103
|
-
}, [toolbarFragmentRoute, toolStateCookie]);
|
|
36135
|
+
}, [toolbarFragmentRoute, toolStateCookie, routableTool]);
|
|
35104
36136
|
React__default.useEffect(() => {
|
|
35105
|
-
|
|
35106
|
-
|
|
36137
|
+
// Helpdesk conversations may belong to Picobot, so they wait for its gate
|
|
36138
|
+
// rather than flashing open in Helpdesk first.
|
|
36139
|
+
const mayRedirectToPicobot = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path) && (isPicobotGateLoading || displayPicobotIcon);
|
|
36140
|
+
const tool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && !mayRedirectToPicobot && routableTool(toolbarFragmentRoute.tool);
|
|
36141
|
+
if (tool && (currentContent !== tool || !open)) {
|
|
36142
|
+
setToolAndOpenDrawer(tool);
|
|
35107
36143
|
}
|
|
35108
|
-
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, currentContent, open]);
|
|
36144
|
+
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path, currentContent, open, routableTool, isPicobotGateLoading, displayPicobotIcon]);
|
|
36145
|
+
const openTool = useCallback((name, path, pendingValue) => {
|
|
36146
|
+
const tool = routableTool(name.toLowerCase());
|
|
36147
|
+
if (!tool) return;
|
|
36148
|
+
dispatch({
|
|
36149
|
+
payload: pendingValue === undefined ? null : {
|
|
36150
|
+
tool: tool.name,
|
|
36151
|
+
value: pendingValue
|
|
36152
|
+
},
|
|
36153
|
+
type: ActionType.setPending
|
|
36154
|
+
});
|
|
36155
|
+
onChangeToolbarFragmentRoute(tool.name.toLowerCase(), path);
|
|
36156
|
+
scrollToolDrawerToTop(tool.name);
|
|
36157
|
+
}, [onChangeToolbarFragmentRoute, routableTool]);
|
|
36158
|
+
const clearPending = useCallback(() => dispatch({
|
|
36159
|
+
type: ActionType.clearPending
|
|
36160
|
+
}), []);
|
|
35109
36161
|
const toggleDrawer = content => {
|
|
35110
36162
|
if (!open) {
|
|
35111
36163
|
return openDrawer(content);
|
|
@@ -35122,12 +36174,52 @@ function Toolbar({
|
|
|
35122
36174
|
joltPromise: storeData === null || storeData === void 0 ? void 0 : storeData.joltPromise,
|
|
35123
36175
|
userId
|
|
35124
36176
|
});
|
|
35125
|
-
|
|
36177
|
+
// Picobot conversations moved from Helpdesk to the Picobot panel, so
|
|
36178
|
+
// send old links and saved paths there once the panel is enabled.
|
|
36179
|
+
React__default.useEffect(() => {
|
|
36180
|
+
if (!displayPicobotIcon) return;
|
|
36181
|
+
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path)) {
|
|
36182
|
+
onChangeToolbarFragmentRoute(Picobot.name.toLowerCase(), toolbarFragmentRoute.path);
|
|
36183
|
+
}
|
|
36184
|
+
}, [displayPicobotIcon, toolbarFragmentRoute, onChangeToolbarFragmentRoute]);
|
|
36185
|
+
React__default.useEffect(() => {
|
|
36186
|
+
if (!displayPicobotIcon) return;
|
|
36187
|
+
const {
|
|
36188
|
+
helpdesk,
|
|
36189
|
+
picobot
|
|
36190
|
+
} = toolStateCookie;
|
|
36191
|
+
if (!isConversationPath(helpdesk)) return;
|
|
36192
|
+
setToolStateCookie({
|
|
36193
|
+
...toolStateCookie,
|
|
36194
|
+
helpdesk: "",
|
|
36195
|
+
picobot: picobot || helpdesk
|
|
36196
|
+
});
|
|
36197
|
+
}, [displayPicobotIcon, toolStateCookie]);
|
|
36198
|
+
const openPicobot = useCallback(options => {
|
|
36199
|
+
var _a;
|
|
36200
|
+
// Moving an open Picobot off its path reloads or navigates the iframe,
|
|
36201
|
+
// which would race the options it's being handed.
|
|
36202
|
+
const path = currentContent === Picobot ? (_a = toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path) !== null && _a !== void 0 ? _a : "" : "";
|
|
36203
|
+
openTool(Picobot.name, path, options);
|
|
36204
|
+
}, [openTool, currentContent, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path]);
|
|
36205
|
+
React__default.useEffect(() => {
|
|
36206
|
+
if (!displayPicobotIcon) return undefined;
|
|
36207
|
+
window.topbar = window.topbar || {};
|
|
36208
|
+
window.topbar.openPicobot = openPicobot;
|
|
36209
|
+
return () => {
|
|
36210
|
+
delete window.topbar.openPicobot;
|
|
36211
|
+
};
|
|
36212
|
+
}, [displayPicobotIcon, openPicobot]);
|
|
36213
|
+
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat, displayPicobotIcon && Picobot, isPcoTestHost() && DeveloperTools].filter(Boolean);
|
|
35126
36214
|
const handleToolOnClick = tool => {
|
|
35127
36215
|
toggleDrawer(tool);
|
|
35128
36216
|
scrollToolDrawerToTop(tool.name);
|
|
35129
36217
|
};
|
|
35130
36218
|
const toolSpecificProps = {
|
|
36219
|
+
Helpdesk: {
|
|
36220
|
+
isPicobotLoading: isPicobotGateLoading,
|
|
36221
|
+
openPicobot: displayPicobotIcon ? openPicobot : undefined
|
|
36222
|
+
},
|
|
35131
36223
|
Profile: {
|
|
35132
36224
|
appName,
|
|
35133
36225
|
closeDrawer,
|
|
@@ -35150,6 +36242,7 @@ function Toolbar({
|
|
|
35150
36242
|
const routeIsForCurrentTool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && toolsRouteMap[toolbarFragmentRoute.tool] === currentContent;
|
|
35151
36243
|
const currentToolProps = {
|
|
35152
36244
|
appName,
|
|
36245
|
+
clearPending,
|
|
35153
36246
|
closeDrawer,
|
|
35154
36247
|
currentPath: routeIsForCurrentTool ? toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path : null,
|
|
35155
36248
|
env,
|
|
@@ -35158,6 +36251,8 @@ function Toolbar({
|
|
|
35158
36251
|
joltPromise,
|
|
35159
36252
|
onChangePath,
|
|
35160
36253
|
openDrawer: () => currentContent && openDrawer(currentContent),
|
|
36254
|
+
openTool,
|
|
36255
|
+
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
36256
|
userId,
|
|
35162
36257
|
...(currentContent ? toolSpecificProps[currentContent.name] : {})
|
|
35163
36258
|
};
|
|
@@ -35168,38 +36263,37 @@ function Toolbar({
|
|
|
35168
36263
|
return /*#__PURE__*/React__default.createElement(ThemeProvider, {
|
|
35169
36264
|
theme: topbarTheme
|
|
35170
36265
|
}, /*#__PURE__*/React__default.createElement(UiExtensionDataContext, null, /*#__PURE__*/React__default.createElement("div", {
|
|
35171
|
-
className: styles$
|
|
36266
|
+
className: styles$2["toolbar"],
|
|
35172
36267
|
"data-pendo": "toolbar-profile-support-wrapper"
|
|
35173
36268
|
}, /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35174
36269
|
"aria-label": `Open ${userName}’s profile drawer`,
|
|
35175
36270
|
onClick: () => toggleDrawer(Profile),
|
|
35176
36271
|
"data-pendo": "toolbar-profile",
|
|
35177
|
-
className: styles$
|
|
36272
|
+
className: styles$2["toolbar-button--avatar"]
|
|
35178
36273
|
}, /*#__PURE__*/React__default.createElement(Avatar, {
|
|
35179
36274
|
env: env,
|
|
35180
36275
|
url: userAvatarPath
|
|
35181
36276
|
})), /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35182
|
-
className: styles$
|
|
36277
|
+
className: styles$2["toolbar-button--home"],
|
|
35183
36278
|
onClick: () => closeDrawer()
|
|
35184
|
-
}, /*#__PURE__*/React__default.createElement(Icon, {
|
|
36279
|
+
}, /*#__PURE__*/React__default.createElement(Icon$1, {
|
|
35185
36280
|
name: "toolbar.planningCenter",
|
|
35186
36281
|
size: "24px"
|
|
35187
36282
|
})), [HelpDeskTool, ...TOOLS].map(tool => {
|
|
36283
|
+
var _a;
|
|
35188
36284
|
const current = open && currentContent === tool;
|
|
35189
36285
|
const indicator = indicatorState[tool.name];
|
|
35190
36286
|
return /*#__PURE__*/React__default.createElement(ToolbarButton, {
|
|
35191
36287
|
current: current,
|
|
35192
36288
|
onClick: () => handleToolOnClick(tool),
|
|
35193
36289
|
id: getDataPendoAttribute(tool.name),
|
|
35194
|
-
"aria-label": `Open ${tool.name} drawer`,
|
|
36290
|
+
"aria-label": `Open ${(_a = tool.label) !== null && _a !== void 0 ? _a : tool.name} drawer`,
|
|
35195
36291
|
"data-pendo": getDataPendoAttribute(tool.name),
|
|
35196
36292
|
key: tool.name
|
|
35197
|
-
},
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
dot: true
|
|
35202
|
-
})));
|
|
36293
|
+
}, /*#__PURE__*/React__default.createElement(ToolbarToolIcon, {
|
|
36294
|
+
icons: tool.icons,
|
|
36295
|
+
dot: !!indicator
|
|
36296
|
+
}));
|
|
35203
36297
|
})), /*#__PURE__*/React__default.createElement(DrawerWrap, {
|
|
35204
36298
|
isVisible: open
|
|
35205
36299
|
}, mounted && (/*#__PURE__*/React__default.createElement(Drawer, {
|
|
@@ -35217,12 +36311,25 @@ const ToolbarButton = ({
|
|
|
35217
36311
|
}) => {
|
|
35218
36312
|
return /*#__PURE__*/React__default.createElement("button", {
|
|
35219
36313
|
type: "button",
|
|
35220
|
-
className: classnames(styles$
|
|
36314
|
+
className: classnames(styles$2["toolbar-button"], className, current && styles$2["is-current"]),
|
|
35221
36315
|
onClick: onClick,
|
|
35222
36316
|
"data-toolbar-link": "true",
|
|
35223
36317
|
...restProps
|
|
35224
36318
|
}, children);
|
|
35225
36319
|
};
|
|
36320
|
+
function ToolbarToolIcon({
|
|
36321
|
+
dot,
|
|
36322
|
+
icons
|
|
36323
|
+
}) {
|
|
36324
|
+
if ("component" in icons) {
|
|
36325
|
+
return /*#__PURE__*/React__default.createElement(icons.component, null);
|
|
36326
|
+
} else {
|
|
36327
|
+
return /*#__PURE__*/React__default.createElement(ToolbarButtonIcon, {
|
|
36328
|
+
path: dot ? icons.staticDotPath : icons.staticPath,
|
|
36329
|
+
dot: dot
|
|
36330
|
+
});
|
|
36331
|
+
}
|
|
36332
|
+
}
|
|
35226
36333
|
function ToolbarButtonIcon({
|
|
35227
36334
|
dot,
|
|
35228
36335
|
path
|