@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.umd.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
var people__namespace = /*#__PURE__*/_interopNamespaceDefault(people);
|
|
28
28
|
var toolbar__namespace = /*#__PURE__*/_interopNamespaceDefault(toolbar);
|
|
29
29
|
|
|
30
|
-
var styles$
|
|
30
|
+
var styles$m = {"accessibility-toolbar":"accessibility-toolbar-jgjUG"};
|
|
31
31
|
|
|
32
32
|
const AccessibilityToolbar = ({
|
|
33
33
|
children
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
(_a = document.getElementById("main-content")) === null || _a === void 0 ? void 0 : _a.focus();
|
|
40
40
|
};
|
|
41
41
|
return /*#__PURE__*/React.createElement("div", {
|
|
42
|
-
className: styles$
|
|
42
|
+
className: styles$m["accessibility-toolbar"]
|
|
43
43
|
}, /*#__PURE__*/React.createElement(tapestry.Link, {
|
|
44
44
|
href: "#main-content",
|
|
45
45
|
kind: "primary",
|
|
@@ -166,12 +166,14 @@
|
|
|
166
166
|
}
|
|
167
167
|
function apiRequest(fetch, url, {
|
|
168
168
|
method = "GET",
|
|
169
|
-
data = null
|
|
169
|
+
data = null,
|
|
170
|
+
headers = {}
|
|
170
171
|
} = {}) {
|
|
171
172
|
const options = {
|
|
172
173
|
headers: {
|
|
173
174
|
Accept: "application/json",
|
|
174
|
-
"Content-Type": "application/json"
|
|
175
|
+
"Content-Type": "application/json",
|
|
176
|
+
...headers
|
|
175
177
|
},
|
|
176
178
|
method,
|
|
177
179
|
redirect: "follow"
|
|
@@ -356,11 +358,11 @@
|
|
|
356
358
|
fill: fill
|
|
357
359
|
})));
|
|
358
360
|
|
|
359
|
-
var styles$
|
|
361
|
+
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"};
|
|
360
362
|
|
|
361
363
|
function AnnouncementIcon() {
|
|
362
364
|
return /*#__PURE__*/React.createElement("svg", {
|
|
363
|
-
className: styles$
|
|
365
|
+
className: styles$l["announcement-icon"],
|
|
364
366
|
width: "16",
|
|
365
367
|
height: "16",
|
|
366
368
|
viewBox: "0 0 16 16",
|
|
@@ -485,7 +487,7 @@
|
|
|
485
487
|
} = this.props;
|
|
486
488
|
const isPromo = variant === "promo";
|
|
487
489
|
return /*#__PURE__*/React.createElement("div", {
|
|
488
|
-
className: classnames(styles$
|
|
490
|
+
className: classnames(styles$l["announcement"], styles$l[`announcement--${variant !== null && variant !== void 0 ? variant : "platform"}`]),
|
|
489
491
|
"data-announcement-variant": variant !== null && variant !== void 0 ? variant : "platform",
|
|
490
492
|
style: isPromo ? {
|
|
491
493
|
backgroundImage: promoBackgroundUrl,
|
|
@@ -494,7 +496,7 @@
|
|
|
494
496
|
backgroundSize: "cover"
|
|
495
497
|
} : undefined
|
|
496
498
|
}, !isPromo && /*#__PURE__*/React.createElement(AnnouncementIcon, null), /*#__PURE__*/React.createElement("div", {
|
|
497
|
-
className: styles$
|
|
499
|
+
className: styles$l["announcement-content"],
|
|
498
500
|
style: style,
|
|
499
501
|
...platformProps
|
|
500
502
|
}));
|
|
@@ -543,7 +545,7 @@
|
|
|
543
545
|
const isPromo = variant === "promo";
|
|
544
546
|
return /*#__PURE__*/React.createElement("button", {
|
|
545
547
|
type: "button",
|
|
546
|
-
className: classnames(styles$
|
|
548
|
+
className: classnames(styles$l.dismiss, isPromo && styles$l["dismiss--promo"]),
|
|
547
549
|
...props
|
|
548
550
|
}, /*#__PURE__*/React.createElement(X, {
|
|
549
551
|
fill: isPromo ? "currentColor" : "white",
|
|
@@ -623,9 +625,9 @@
|
|
|
623
625
|
...platformProps
|
|
624
626
|
} = this.props;
|
|
625
627
|
return /*#__PURE__*/React.createElement("div", {
|
|
626
|
-
className: styles$
|
|
628
|
+
className: styles$l["announcement"]
|
|
627
629
|
}, /*#__PURE__*/React.createElement(AnnouncementIcon, null), /*#__PURE__*/React.createElement("div", {
|
|
628
|
-
className: styles$
|
|
630
|
+
className: styles$l["announcement-content"],
|
|
629
631
|
style: style,
|
|
630
632
|
...platformProps
|
|
631
633
|
}));
|
|
@@ -636,7 +638,7 @@
|
|
|
636
638
|
}) {
|
|
637
639
|
return /*#__PURE__*/React.createElement("button", {
|
|
638
640
|
type: "button",
|
|
639
|
-
className: styles$
|
|
641
|
+
className: styles$l.dismiss,
|
|
640
642
|
...props
|
|
641
643
|
}, /*#__PURE__*/React.createElement(X, {
|
|
642
644
|
fill: "white",
|
|
@@ -791,7 +793,7 @@
|
|
|
791
793
|
return matches;
|
|
792
794
|
}
|
|
793
795
|
|
|
794
|
-
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
796
|
+
const capitalize$1 = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
795
797
|
const RESOLVED_COLOR_MODE_ATTRIBUTE = "data-resolved-color-mode";
|
|
796
798
|
function resolveColorMode(colorMode) {
|
|
797
799
|
if (colorMode === "system") {
|
|
@@ -1758,7 +1760,7 @@
|
|
|
1758
1760
|
})) : (/*#__PURE__*/React.createElement("div", null, "appName not supported")));
|
|
1759
1761
|
}
|
|
1760
1762
|
|
|
1761
|
-
var styles$
|
|
1763
|
+
var styles$k = {"icon-slot":"icon-slot-m3ZIk","name":"name-UNqbW"};
|
|
1762
1764
|
|
|
1763
1765
|
const AppRow = ({
|
|
1764
1766
|
appName,
|
|
@@ -1767,24 +1769,24 @@
|
|
|
1767
1769
|
}) => {
|
|
1768
1770
|
if (appName === "Accounts") {
|
|
1769
1771
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
1770
|
-
className: styles$
|
|
1772
|
+
className: styles$k["icon-slot"]
|
|
1771
1773
|
}, /*#__PURE__*/React.createElement(MonoAppIcon, {
|
|
1772
1774
|
appName: "Accounts",
|
|
1773
1775
|
colors: colors,
|
|
1774
1776
|
size: iconSize
|
|
1775
1777
|
})), /*#__PURE__*/React.createElement("span", {
|
|
1776
|
-
className: styles$
|
|
1778
|
+
className: styles$k.name
|
|
1777
1779
|
}, "Account settings"));
|
|
1778
1780
|
}
|
|
1779
1781
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
1780
1782
|
appName: appName.replace(/[\s-]/, ""),
|
|
1781
1783
|
size: iconSize
|
|
1782
1784
|
}), /*#__PURE__*/React.createElement("span", {
|
|
1783
|
-
className: styles$
|
|
1785
|
+
className: styles$k.name
|
|
1784
1786
|
}, appName));
|
|
1785
1787
|
};
|
|
1786
1788
|
|
|
1787
|
-
var styles$
|
|
1789
|
+
var styles$j = {"button":"button-XMbeQ","label":"label-884of","chevron":"chevron-RRg9k"};
|
|
1788
1790
|
|
|
1789
1791
|
const AppsButton = /*#__PURE__*/React.forwardRef(({
|
|
1790
1792
|
appName,
|
|
@@ -1796,10 +1798,10 @@
|
|
|
1796
1798
|
...restProps,
|
|
1797
1799
|
ref: ref,
|
|
1798
1800
|
"aria-label": `App switcher: ${appName}`,
|
|
1799
|
-
className: styles$
|
|
1801
|
+
className: styles$j.button,
|
|
1800
1802
|
kind: "neutral-inline",
|
|
1801
1803
|
label: /*#__PURE__*/React.createElement("span", {
|
|
1802
|
-
className: styles$
|
|
1804
|
+
className: styles$j.label
|
|
1803
1805
|
}, isAccounts ? "Account settings" : appName),
|
|
1804
1806
|
prefix: isAccounts ? (/*#__PURE__*/React.createElement(MonoAppIcon, {
|
|
1805
1807
|
appName: "Accounts",
|
|
@@ -1810,13 +1812,13 @@
|
|
|
1810
1812
|
size: 22
|
|
1811
1813
|
})),
|
|
1812
1814
|
suffix: /*#__PURE__*/React.createElement(DisclosureChevronIcon, {
|
|
1813
|
-
className: `${styles$
|
|
1815
|
+
className: `${styles$j.chevron} symbol`
|
|
1814
1816
|
})
|
|
1815
1817
|
});
|
|
1816
1818
|
});
|
|
1817
1819
|
AppsButton.displayName = "AppsButton";
|
|
1818
1820
|
|
|
1819
|
-
var styles$
|
|
1821
|
+
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"};
|
|
1820
1822
|
|
|
1821
1823
|
const AppsMenu = ({
|
|
1822
1824
|
appName,
|
|
@@ -1837,8 +1839,8 @@
|
|
|
1837
1839
|
}, /*#__PURE__*/React.createElement("a", {
|
|
1838
1840
|
"aria-current": isCurrent ? "page" : undefined,
|
|
1839
1841
|
"aria-label": name,
|
|
1840
|
-
className: classnames(styles$
|
|
1841
|
-
[styles$
|
|
1842
|
+
className: classnames(styles$i.link, {
|
|
1843
|
+
[styles$i.current]: isCurrent
|
|
1842
1844
|
}),
|
|
1843
1845
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
1844
1846
|
"data-turbolinks": false,
|
|
@@ -1854,22 +1856,22 @@
|
|
|
1854
1856
|
colors: colors
|
|
1855
1857
|
})), /*#__PURE__*/React.createElement(tapestry.PopoverContent, {
|
|
1856
1858
|
"aria-label": "Switch app",
|
|
1857
|
-
className: styles$
|
|
1859
|
+
className: styles$i["popover-content"],
|
|
1858
1860
|
placement: "bottom start"
|
|
1859
1861
|
}, /*#__PURE__*/React.createElement("nav", {
|
|
1860
|
-
className: styles$
|
|
1862
|
+
className: styles$i.nav,
|
|
1861
1863
|
"data-pendo": "app-switcher"
|
|
1862
1864
|
}, /*#__PURE__*/React.createElement("ul", {
|
|
1863
|
-
className: styles$
|
|
1865
|
+
className: styles$i.list,
|
|
1864
1866
|
role: "list"
|
|
1865
1867
|
}, homeProduct && renderProductLink(homeProduct.name), restProducts.map(({
|
|
1866
1868
|
name
|
|
1867
1869
|
}) => renderProductLink(name)), accountsProduct && (/*#__PURE__*/React.createElement("li", {
|
|
1868
|
-
className: styles$
|
|
1870
|
+
className: styles$i["accounts-item"]
|
|
1869
1871
|
}, /*#__PURE__*/React.createElement("a", {
|
|
1870
1872
|
"aria-current": appName === accountsProduct.name ? "page" : undefined,
|
|
1871
1873
|
"aria-label": "Account settings",
|
|
1872
|
-
className: classnames(styles$
|
|
1874
|
+
className: classnames(styles$i.link, styles$i["accounts-link"]),
|
|
1873
1875
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
1874
1876
|
"data-turbolinks": false,
|
|
1875
1877
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -2077,7 +2079,7 @@
|
|
|
2077
2079
|
})) : (/*#__PURE__*/React.createElement("div", null, "appName not supported"));
|
|
2078
2080
|
}
|
|
2079
2081
|
|
|
2080
|
-
var styles$
|
|
2082
|
+
var styles$h = {"unbutton":"unbutton-lEbTR"};
|
|
2081
2083
|
|
|
2082
2084
|
function Unbutton({
|
|
2083
2085
|
className,
|
|
@@ -2086,7 +2088,7 @@
|
|
|
2086
2088
|
...nativeProps
|
|
2087
2089
|
}) {
|
|
2088
2090
|
return /*#__PURE__*/React.createElement("button", {
|
|
2089
|
-
className: classnames(styles$
|
|
2091
|
+
className: classnames(styles$h.unbutton, className),
|
|
2090
2092
|
style: {
|
|
2091
2093
|
...style
|
|
2092
2094
|
},
|
|
@@ -2095,7 +2097,7 @@
|
|
|
2095
2097
|
});
|
|
2096
2098
|
}
|
|
2097
2099
|
|
|
2098
|
-
var styles$
|
|
2100
|
+
var styles$g = {"button":"button-wsipc","button-accounts":"button-accounts-zQmBM","chevron":"chevron-obyBF","is-expanded":"is-expanded-PoCQ9"};
|
|
2099
2101
|
|
|
2100
2102
|
const LegacyAppsButton = ({
|
|
2101
2103
|
appName,
|
|
@@ -2110,8 +2112,8 @@
|
|
|
2110
2112
|
"aria-haspopup": "true",
|
|
2111
2113
|
"aria-label": `App switcher: ${appName}`,
|
|
2112
2114
|
onClick: onClick,
|
|
2113
|
-
className: classnames(styles$
|
|
2114
|
-
[styles$
|
|
2115
|
+
className: classnames(styles$g.button, {
|
|
2116
|
+
[styles$g["is-expanded"]]: expanded
|
|
2115
2117
|
}),
|
|
2116
2118
|
type: "button",
|
|
2117
2119
|
style: style,
|
|
@@ -2120,7 +2122,7 @@
|
|
|
2120
2122
|
appName: "Accounts",
|
|
2121
2123
|
colors: colors
|
|
2122
2124
|
}), /*#__PURE__*/React.createElement("div", {
|
|
2123
|
-
className: styles$
|
|
2125
|
+
className: styles$g["button-accounts"]
|
|
2124
2126
|
}, "Account settings"))) : (/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
2125
2127
|
appName: appName.replace(/[\s-]/, "")
|
|
2126
2128
|
}), /*#__PURE__*/React.createElement(MonoAppName, {
|
|
@@ -2128,7 +2130,7 @@
|
|
|
2128
2130
|
key: "mono-app-name",
|
|
2129
2131
|
size: 24
|
|
2130
2132
|
}))), /*#__PURE__*/React.createElement(DisclosureChevronIcon, {
|
|
2131
|
-
className: styles$
|
|
2133
|
+
className: styles$g.chevron,
|
|
2132
2134
|
key: "disclosure-chevron-icon"
|
|
2133
2135
|
}));
|
|
2134
2136
|
};
|
|
@@ -2261,7 +2263,7 @@
|
|
|
2261
2263
|
}
|
|
2262
2264
|
}
|
|
2263
2265
|
|
|
2264
|
-
var styles$
|
|
2266
|
+
var styles$f = {"popup-root":"popup-root-SeL5u","popup":"popup-nQXYW","is-visible":"is-visible-3DAUP"};
|
|
2265
2267
|
|
|
2266
2268
|
const Popup = ({
|
|
2267
2269
|
className,
|
|
@@ -2269,8 +2271,8 @@
|
|
|
2269
2271
|
isVisible,
|
|
2270
2272
|
...nativeProps
|
|
2271
2273
|
}) => (/*#__PURE__*/React.createElement(C, {
|
|
2272
|
-
className: classnames(styles$
|
|
2273
|
-
[styles$
|
|
2274
|
+
className: classnames(styles$f.popup, {
|
|
2275
|
+
[styles$f["is-visible"]]: isVisible
|
|
2274
2276
|
}, className),
|
|
2275
2277
|
...nativeProps
|
|
2276
2278
|
}));
|
|
@@ -2278,11 +2280,11 @@
|
|
|
2278
2280
|
const PopupRoot = ({
|
|
2279
2281
|
...nativeProps
|
|
2280
2282
|
}) => (/*#__PURE__*/React.createElement("div", {
|
|
2281
|
-
className: styles$
|
|
2283
|
+
className: styles$f["popup-root"],
|
|
2282
2284
|
...nativeProps
|
|
2283
2285
|
}));
|
|
2284
2286
|
|
|
2285
|
-
var styles$
|
|
2287
|
+
var styles$e = {"popup":"popup-bVhSa"};
|
|
2286
2288
|
|
|
2287
2289
|
const LegacyAppsMenu = ({
|
|
2288
2290
|
appName,
|
|
@@ -2320,7 +2322,7 @@
|
|
|
2320
2322
|
cleanup: !visible,
|
|
2321
2323
|
component: Outsider,
|
|
2322
2324
|
onOutsideClick: toggle,
|
|
2323
|
-
className: styles$
|
|
2325
|
+
className: styles$e.popup,
|
|
2324
2326
|
isVisible: visible
|
|
2325
2327
|
}, /*#__PURE__*/React.createElement("menu", {
|
|
2326
2328
|
ref: menuRef,
|
|
@@ -3233,27 +3235,34 @@ true;
|
|
|
3233
3235
|
const isMac = () => /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
3234
3236
|
const hasCommandKey = event => isMac() ? event.metaKey : event.ctrlKey;
|
|
3235
3237
|
const commandKeyLabel = () => isMac() ? "⌘" : "Ctrl+";
|
|
3238
|
+
const isSearchShortcut = event => event.key.toLowerCase() === "k" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3239
|
+
// Products opened their own search with `/` before this one replaced it.
|
|
3240
|
+
const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !event.ctrlKey && !event.metaKey && !isTypingIn(event.target);
|
|
3241
|
+
const isCopyShortcut = event => event.key.toLowerCase() === "c" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3242
|
+
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3243
|
+
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3244
|
+
const verticalStep = key => ({
|
|
3245
|
+
ArrowDown: 1,
|
|
3246
|
+
ArrowUp: -1
|
|
3247
|
+
})[key];
|
|
3248
|
+
const horizontalStep = key => ({
|
|
3249
|
+
ArrowLeft: -1,
|
|
3250
|
+
ArrowRight: 1
|
|
3251
|
+
})[key];
|
|
3236
3252
|
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
} catch {
|
|
3249
|
-
// Storage can be full or blocked; search still works without it.
|
|
3250
|
-
}
|
|
3253
|
+
function SearchButton({
|
|
3254
|
+
onClick
|
|
3255
|
+
}) {
|
|
3256
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
3257
|
+
"aria-keyshortcuts": `${isMac() ? "Meta+K" : "Control+K"} /`,
|
|
3258
|
+
"aria-label": "Search",
|
|
3259
|
+
onClick: onClick,
|
|
3260
|
+
type: "button"
|
|
3261
|
+
}, /*#__PURE__*/React.createElement(Spyglass, {
|
|
3262
|
+
fill: "currentColor"
|
|
3263
|
+
}));
|
|
3251
3264
|
}
|
|
3252
3265
|
|
|
3253
|
-
var styles$c = {"visuallyHidden":"visuallyHidden-C28ju"};
|
|
3254
|
-
|
|
3255
|
-
var styles$b = {"tab":"tab-lQLDx"};
|
|
3256
|
-
|
|
3257
3266
|
// useId isn't available on React 17, which our peer range still supports.
|
|
3258
3267
|
let instanceCount$1 = 0;
|
|
3259
3268
|
function useInstanceId(prefix) {
|
|
@@ -3261,173 +3270,33 @@ true;
|
|
|
3261
3270
|
return id;
|
|
3262
3271
|
}
|
|
3263
3272
|
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
|
-
}
|
|
3289
|
-
};
|
|
3290
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
3291
|
-
"aria-label": "Product",
|
|
3292
|
-
onKeyDown: handleKeyDown,
|
|
3293
|
-
role: "tablist"
|
|
3294
|
-
}, products.map(product => (/*#__PURE__*/React.createElement("button", {
|
|
3295
|
-
"aria-controls": `${id}-panel`,
|
|
3296
|
-
"aria-selected": product === selected,
|
|
3297
|
-
className: styles$b.tab,
|
|
3298
|
-
id: tabId(product.slug),
|
|
3299
|
-
key: product.slug,
|
|
3300
|
-
onClick: () => onSelect(product.slug),
|
|
3301
|
-
ref: element => {
|
|
3302
|
-
if (element) {
|
|
3303
|
-
tabRefs.current.set(product.slug, element);
|
|
3304
|
-
} else {
|
|
3305
|
-
tabRefs.current.delete(product.slug);
|
|
3306
|
-
}
|
|
3307
|
-
},
|
|
3308
|
-
role: "tab",
|
|
3309
|
-
tabIndex: product === selected ? 0 : -1,
|
|
3310
|
-
type: "button"
|
|
3311
|
-
}, product.appName)))), /*#__PURE__*/React.createElement("div", {
|
|
3312
|
-
"aria-labelledby": tabId(selected.slug),
|
|
3313
|
-
id: `${id}-panel`,
|
|
3314
|
-
role: "tabpanel"
|
|
3315
|
-
}, children));
|
|
3316
|
-
}
|
|
3317
|
-
|
|
3318
|
-
var styles$a = {"result":"result-WCnxr"};
|
|
3319
|
-
|
|
3320
|
-
function SearchResult({
|
|
3321
|
-
highlighted,
|
|
3322
|
-
id,
|
|
3323
|
-
onHighlight,
|
|
3324
|
-
result
|
|
3325
|
-
}) {
|
|
3326
|
-
const {
|
|
3327
|
-
metadata,
|
|
3328
|
-
title,
|
|
3329
|
-
url
|
|
3330
|
-
} = result.attributes;
|
|
3331
|
-
const ref = React.useRef(null);
|
|
3332
|
-
React.useEffect(() => {
|
|
3333
|
-
var _a;
|
|
3334
|
-
if (highlighted) (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
3335
|
-
block: "nearest"
|
|
3336
|
-
});
|
|
3337
|
-
}, [highlighted]);
|
|
3338
|
-
// Focus stays in the search input, which points at the highlighted result
|
|
3339
|
-
// with aria-activedescendant, so results are kept out of the tab order.
|
|
3340
|
-
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
3341
|
-
// pointer, and that shouldn't steal the highlight.
|
|
3342
|
-
return /*#__PURE__*/React.createElement("a", {
|
|
3343
|
-
"aria-selected": highlighted,
|
|
3344
|
-
className: styles$a.result,
|
|
3345
|
-
href: url,
|
|
3346
|
-
id: id,
|
|
3347
|
-
onMouseMove: onHighlight,
|
|
3348
|
-
ref: ref,
|
|
3349
|
-
role: "option",
|
|
3350
|
-
tabIndex: -1
|
|
3351
|
-
}, /*#__PURE__*/React.createElement("div", null, title), /*#__PURE__*/React.createElement("div", null, summarize(metadata)));
|
|
3352
|
-
}
|
|
3353
|
-
function summarize(metadata) {
|
|
3354
|
-
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
3358
|
-
function SearchResults({
|
|
3359
|
-
search,
|
|
3360
|
-
term,
|
|
3361
|
-
...navigation
|
|
3362
|
-
}) {
|
|
3363
|
-
var _a;
|
|
3364
|
-
switch (search.status) {
|
|
3365
|
-
case "idle":
|
|
3366
|
-
return null;
|
|
3367
|
-
case "loading":
|
|
3368
|
-
if ((_a = search.groups) === null || _a === void 0 ? void 0 : _a.length) {
|
|
3369
|
-
return /*#__PURE__*/React.createElement(ResultGroups, {
|
|
3370
|
-
busy: true,
|
|
3371
|
-
groups: search.groups,
|
|
3372
|
-
...navigation
|
|
3373
|
-
});
|
|
3374
|
-
}
|
|
3375
|
-
return /*#__PURE__*/React.createElement("p", {
|
|
3376
|
-
role: "status"
|
|
3377
|
-
}, "Searching\u2026");
|
|
3378
|
-
case "error":
|
|
3379
|
-
return /*#__PURE__*/React.createElement("p", {
|
|
3380
|
-
role: "alert"
|
|
3381
|
-
}, "Search isn\u2019t working right now. Try again.");
|
|
3382
|
-
case "done":
|
|
3383
|
-
if (search.groups.length === 0) {
|
|
3384
|
-
return /*#__PURE__*/React.createElement("p", {
|
|
3385
|
-
role: "status"
|
|
3386
|
-
}, "No results for \u201C", term.trim(), "\u201D");
|
|
3387
|
-
}
|
|
3388
|
-
return /*#__PURE__*/React.createElement(ResultGroups, {
|
|
3389
|
-
groups: search.groups,
|
|
3390
|
-
...navigation
|
|
3391
|
-
});
|
|
3273
|
+
// The versions home-mobile pins, so web and mobile read the same response
|
|
3274
|
+
// shapes. Some endpoints only exist from a version on: Registrations' me,
|
|
3275
|
+
// product_search_results, and pcrl_lookups need 2025-05-01.
|
|
3276
|
+
const API_VERSIONS = {
|
|
3277
|
+
calendar: "2022-07-07",
|
|
3278
|
+
"check-ins": "2025-05-28",
|
|
3279
|
+
giving: "2019-10-18",
|
|
3280
|
+
groups: "2023-07-10",
|
|
3281
|
+
home: "2018-08-01",
|
|
3282
|
+
people: "2023-03-21",
|
|
3283
|
+
publishing: "2024-03-25",
|
|
3284
|
+
registrations: "2025-05-01",
|
|
3285
|
+
services: "2018-11-01"
|
|
3286
|
+
};
|
|
3287
|
+
// For a `/~api/{product}/v2/...` URL, the header that pins its product's
|
|
3288
|
+
// version.
|
|
3289
|
+
function apiVersionHeaders(url) {
|
|
3290
|
+
const [,, product] = new URL(url).pathname.split("/");
|
|
3291
|
+
const version = API_VERSIONS[product];
|
|
3292
|
+
if (version) {
|
|
3293
|
+
return {
|
|
3294
|
+
"X-PCO-API-Version": version
|
|
3295
|
+
};
|
|
3296
|
+
} else {
|
|
3297
|
+
return {};
|
|
3392
3298
|
}
|
|
3393
3299
|
}
|
|
3394
|
-
function ResultGroups({
|
|
3395
|
-
busy = false,
|
|
3396
|
-
groups,
|
|
3397
|
-
highlightedIndex,
|
|
3398
|
-
id,
|
|
3399
|
-
label,
|
|
3400
|
-
onHighlight
|
|
3401
|
-
}) {
|
|
3402
|
-
let index = 0;
|
|
3403
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
3404
|
-
"aria-busy": busy,
|
|
3405
|
-
"aria-label": label,
|
|
3406
|
-
id: id,
|
|
3407
|
-
role: "listbox"
|
|
3408
|
-
}, groups.map(({
|
|
3409
|
-
results,
|
|
3410
|
-
type
|
|
3411
|
-
}, groupIndex) => {
|
|
3412
|
-
const headingId = `${id}-group-${groupIndex}`;
|
|
3413
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
3414
|
-
"aria-labelledby": headingId,
|
|
3415
|
-
key: type,
|
|
3416
|
-
role: "group"
|
|
3417
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
3418
|
-
id: headingId
|
|
3419
|
-
}, type), results.map(result => {
|
|
3420
|
-
const resultIndex = index++;
|
|
3421
|
-
return /*#__PURE__*/React.createElement(SearchResult, {
|
|
3422
|
-
highlighted: resultIndex === highlightedIndex,
|
|
3423
|
-
id: resultId(id, resultIndex),
|
|
3424
|
-
key: result.id,
|
|
3425
|
-
onHighlight: () => onHighlight(resultIndex),
|
|
3426
|
-
result: result
|
|
3427
|
-
});
|
|
3428
|
-
}));
|
|
3429
|
-
}));
|
|
3430
|
-
}
|
|
3431
3300
|
|
|
3432
3301
|
const CACHE_TTL_MS = 60000;
|
|
3433
3302
|
const MAX_ENTRIES$1 = 50;
|
|
@@ -3444,7 +3313,9 @@ true;
|
|
|
3444
3313
|
}
|
|
3445
3314
|
}
|
|
3446
3315
|
function fetchIntoCache(url) {
|
|
3447
|
-
const promise = apiRequest(defaultFetch, url
|
|
3316
|
+
const promise = apiRequest(defaultFetch, url, {
|
|
3317
|
+
headers: apiVersionHeaders(url)
|
|
3318
|
+
}).then(({
|
|
3448
3319
|
json,
|
|
3449
3320
|
ok
|
|
3450
3321
|
}) => {
|
|
@@ -3500,44 +3371,52 @@ true;
|
|
|
3500
3371
|
};
|
|
3501
3372
|
}
|
|
3502
3373
|
}
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
const
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
const
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
return {
|
|
3527
|
-
|
|
3528
|
-
status: "done"
|
|
3529
|
-
};
|
|
3530
|
-
} else if (request.status === "error") {
|
|
3531
|
-
return {
|
|
3532
|
-
status: "error"
|
|
3533
|
-
};
|
|
3534
|
-
} else {
|
|
3535
|
-
return {
|
|
3536
|
-
groups,
|
|
3537
|
-
status: "loading"
|
|
3374
|
+
// One request per URL, for callers that fan out to a varying number of
|
|
3375
|
+
// endpoints. Unlike `useCachedRequest`, a URL that hasn't settled yet has no
|
|
3376
|
+
// earlier data to fall back on.
|
|
3377
|
+
function useCachedRequests(urls) {
|
|
3378
|
+
const [settled, setSettled] = React.useState({});
|
|
3379
|
+
// Keyed by value because each settle re-renders the caller, and a caller
|
|
3380
|
+
// that builds a fresh `urls` array on render would otherwise loop forever.
|
|
3381
|
+
const key = urls.join("\n");
|
|
3382
|
+
React.useEffect(() => {
|
|
3383
|
+
let current = true;
|
|
3384
|
+
const settle = result => current && setSettled(prev => ({
|
|
3385
|
+
...prev,
|
|
3386
|
+
[result.url]: result
|
|
3387
|
+
}));
|
|
3388
|
+
for (const url of key.split("\n").filter(Boolean)) {
|
|
3389
|
+
cachedRequest(url).then(data => settle({
|
|
3390
|
+
data,
|
|
3391
|
+
url
|
|
3392
|
+
}), () => settle({
|
|
3393
|
+
failed: true,
|
|
3394
|
+
url
|
|
3395
|
+
}));
|
|
3396
|
+
}
|
|
3397
|
+
return () => {
|
|
3398
|
+
current = false;
|
|
3538
3399
|
};
|
|
3539
|
-
}
|
|
3400
|
+
}, [key]);
|
|
3401
|
+
return urls.map(url => {
|
|
3402
|
+
const result = settled[url];
|
|
3403
|
+
if (!result) {
|
|
3404
|
+
return {
|
|
3405
|
+
status: "loading"
|
|
3406
|
+
};
|
|
3407
|
+
} else if ("data" in result) {
|
|
3408
|
+
return {
|
|
3409
|
+
data: result.data,
|
|
3410
|
+
status: "done"
|
|
3411
|
+
};
|
|
3412
|
+
} else {
|
|
3413
|
+
return {
|
|
3414
|
+
status: "error"
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3417
|
+
});
|
|
3540
3418
|
}
|
|
3419
|
+
|
|
3541
3420
|
function productSearchUrl(product, query) {
|
|
3542
3421
|
const params = new URLSearchParams({
|
|
3543
3422
|
per_page: "100",
|
|
@@ -3568,14 +3447,7 @@ true;
|
|
|
3568
3447
|
};
|
|
3569
3448
|
return [...groups.values()].sort((a, b) => rank(a.type) - rank(b.type));
|
|
3570
3449
|
}
|
|
3571
|
-
|
|
3572
|
-
function useSettledProduct(product, status) {
|
|
3573
|
-
const [settledProduct, setSettledProduct] = React.useState();
|
|
3574
|
-
React.useEffect(() => {
|
|
3575
|
-
if (status === "done") setSettledProduct(product);
|
|
3576
|
-
}, [product, status]);
|
|
3577
|
-
return settledProduct;
|
|
3578
|
-
}
|
|
3450
|
+
|
|
3579
3451
|
function useDebouncedValue(value, delay) {
|
|
3580
3452
|
const [debounced, setDebounced] = React.useState(value);
|
|
3581
3453
|
React.useEffect(() => {
|
|
@@ -3585,310 +3457,50 @@ true;
|
|
|
3585
3457
|
return debounced;
|
|
3586
3458
|
}
|
|
3587
3459
|
|
|
3588
|
-
const
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
//
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
return undefined;
|
|
3609
|
-
}
|
|
3610
|
-
}, [copiedUrl]);
|
|
3611
|
-
const highlightIndex = React.useCallback(index => setHighlight(current => current.results === results && current.index === index ? current : {
|
|
3612
|
-
index,
|
|
3613
|
-
results
|
|
3614
|
-
}), [results]);
|
|
3615
|
-
const handleKeyDown = event => {
|
|
3616
|
-
const step = verticalStep(event.key);
|
|
3617
|
-
if (step && results.length > 0) {
|
|
3618
|
-
event.preventDefault();
|
|
3619
|
-
highlightIndex((highlightedIndex + step + results.length) % results.length);
|
|
3620
|
-
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
3621
|
-
event.preventDefault();
|
|
3622
|
-
open(highlighted.attributes.url, {
|
|
3623
|
-
newTab: hasCommandKey(event)
|
|
3624
|
-
});
|
|
3625
|
-
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
3626
|
-
event.preventDefault();
|
|
3627
|
-
const {
|
|
3628
|
-
url
|
|
3629
|
-
} = highlighted.attributes;
|
|
3630
|
-
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
3631
|
-
}
|
|
3632
|
-
};
|
|
3633
|
-
return {
|
|
3634
|
-
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.attributes.url),
|
|
3635
|
-
handleKeyDown,
|
|
3636
|
-
highlightIndex,
|
|
3637
|
-
highlighted,
|
|
3638
|
-
highlightedIndex: highlighted ? highlightedIndex : null
|
|
3639
|
-
};
|
|
3640
|
-
}
|
|
3641
|
-
function open(url, {
|
|
3642
|
-
newTab
|
|
3643
|
-
}) {
|
|
3644
|
-
if (newTab) {
|
|
3645
|
-
window.open(url, "_blank", "noopener");
|
|
3646
|
-
} else {
|
|
3647
|
-
window.location.assign(url);
|
|
3648
|
-
}
|
|
3649
|
-
}
|
|
3650
|
-
const isCopyShortcut = event => event.key.toLowerCase() === "c" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3651
|
-
// With text selected, ⌘C should copy that text as usual, not the link.
|
|
3652
|
-
function hasSelectedText(element) {
|
|
3653
|
-
var _a, _b;
|
|
3654
|
-
const selectedInField = element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
3655
|
-
return selectedInField || ((_b = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "") !== "";
|
|
3656
|
-
}
|
|
3657
|
-
|
|
3658
|
-
const SEARCHABLE_PRODUCTS = [{
|
|
3659
|
-
appName: "Groups",
|
|
3660
|
-
slug: "groups"
|
|
3661
|
-
}, {
|
|
3662
|
-
appName: "People",
|
|
3663
|
-
slug: "people"
|
|
3664
|
-
}, {
|
|
3665
|
-
appName: "Services",
|
|
3666
|
-
slug: "services"
|
|
3667
|
-
}];
|
|
3668
|
-
const PERMISSION_CHECKS = {
|
|
3669
|
-
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
3670
|
-
people_permissions,
|
|
3671
|
-
site_administrator
|
|
3672
|
-
}) => site_administrator || people_permissions !== null),
|
|
3673
|
-
services: () => fetchMe("services", "can_search").then(({
|
|
3674
|
-
can_search
|
|
3675
|
-
}) => can_search)
|
|
3676
|
-
};
|
|
3677
|
-
function fetchMe(product, fields) {
|
|
3678
|
-
const params = new URLSearchParams({
|
|
3679
|
-
"fields[Person]": fields
|
|
3680
|
-
});
|
|
3681
|
-
return apiRequest(defaultFetch, `${window.location.origin}/~api/${product}/v2/me?${params}`).then(({
|
|
3682
|
-
json,
|
|
3683
|
-
ok
|
|
3684
|
-
}) => {
|
|
3685
|
-
if (!ok || json === null) throw new Error("Permission check failed");
|
|
3686
|
-
return json.data.attributes;
|
|
3687
|
-
});
|
|
3688
|
-
}
|
|
3689
|
-
// Permissions rarely change mid-session, so a successful check is kept until
|
|
3690
|
-
// the page reloads. A failed one is dropped so a remount tries again.
|
|
3691
|
-
const permissionCache = new Map();
|
|
3692
|
-
function canSearch(slug) {
|
|
3693
|
-
const check = PERMISSION_CHECKS[slug];
|
|
3694
|
-
const cached = permissionCache.get(slug);
|
|
3695
|
-
if (!check) {
|
|
3696
|
-
return Promise.resolve(true);
|
|
3697
|
-
} else if (cached) {
|
|
3698
|
-
return cached;
|
|
3699
|
-
} else {
|
|
3700
|
-
const promise = check().catch(() => {
|
|
3701
|
-
permissionCache.delete(slug);
|
|
3702
|
-
return false;
|
|
3703
|
-
});
|
|
3704
|
-
permissionCache.set(slug, promise);
|
|
3705
|
-
return promise;
|
|
3706
|
-
}
|
|
3707
|
-
}
|
|
3708
|
-
function useSearchableProducts(apps) {
|
|
3709
|
-
// Keyed by name because providers hand over a fresh `apps` array on renders
|
|
3710
|
-
// where nothing changed.
|
|
3711
|
-
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
3712
|
-
slug
|
|
3713
|
-
}) => slug).join(",");
|
|
3714
|
-
const [settled, setSettled] = React.useState();
|
|
3715
|
-
React.useEffect(() => {
|
|
3716
|
-
let current = true;
|
|
3717
|
-
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
3718
|
-
Promise.all(accessible.map(({
|
|
3719
|
-
slug
|
|
3720
|
-
}) => canSearch(slug))).then(allowed => {
|
|
3721
|
-
if (current) {
|
|
3722
|
-
setSettled({
|
|
3723
|
-
accessibleKey,
|
|
3724
|
-
products: accessible.filter((_, index) => allowed[index])
|
|
3725
|
-
});
|
|
3726
|
-
}
|
|
3727
|
-
});
|
|
3728
|
-
return () => {
|
|
3729
|
-
current = false;
|
|
3460
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
3461
|
+
function useProductSearch(product, term) {
|
|
3462
|
+
const query = term.trim();
|
|
3463
|
+
const debouncedQuery = useDebouncedValue(query, SEARCH_DEBOUNCE_MS);
|
|
3464
|
+
const request = useCachedRequest(debouncedQuery ? productSearchUrl(product, debouncedQuery) : null);
|
|
3465
|
+
const response = "data" in request ? request.data : undefined;
|
|
3466
|
+
const settledProduct = useSettledProduct(product, request.status);
|
|
3467
|
+
// While a request loads, the previous response stays on screen, but only
|
|
3468
|
+
// if it was for this product: People results must never show under the
|
|
3469
|
+
// Services tab.
|
|
3470
|
+
const visibleResponse = request.status === "done" || settledProduct === product ? response : undefined;
|
|
3471
|
+
const groups = React.useMemo(() => visibleResponse && groupByType(visibleResponse), [visibleResponse]);
|
|
3472
|
+
if (!query) {
|
|
3473
|
+
return {
|
|
3474
|
+
status: "idle"
|
|
3475
|
+
};
|
|
3476
|
+
} else if (query !== debouncedQuery) {
|
|
3477
|
+
return {
|
|
3478
|
+
groups,
|
|
3479
|
+
status: "loading"
|
|
3730
3480
|
};
|
|
3731
|
-
}
|
|
3732
|
-
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey) {
|
|
3481
|
+
} else if (request.status === "done") {
|
|
3733
3482
|
return {
|
|
3734
|
-
|
|
3483
|
+
groups: groups !== null && groups !== void 0 ? groups : [],
|
|
3735
3484
|
status: "done"
|
|
3736
3485
|
};
|
|
3486
|
+
} else if (request.status === "error") {
|
|
3487
|
+
return {
|
|
3488
|
+
status: "error"
|
|
3489
|
+
};
|
|
3737
3490
|
} else {
|
|
3738
3491
|
return {
|
|
3492
|
+
groups,
|
|
3739
3493
|
status: "loading"
|
|
3740
3494
|
};
|
|
3741
3495
|
}
|
|
3742
3496
|
}
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
const isSearchShortcut = event => event.key.toLowerCase() === "k" && !event.altKey && !event.shiftKey && hasCommandKey(event);
|
|
3748
|
-
// Products opened their own search with `/` before this one replaced it.
|
|
3749
|
-
const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !event.ctrlKey && !event.metaKey && !isTypingIn(event.target);
|
|
3750
|
-
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3751
|
-
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3752
|
-
function ProductSearch({
|
|
3753
|
-
apps
|
|
3754
|
-
}) {
|
|
3755
|
-
const searchable = useSearchableProducts(apps);
|
|
3756
|
-
if (searchable.status === "done" && searchable.products.length > 0) {
|
|
3757
|
-
return /*#__PURE__*/React.createElement(SearchButtonAndDialog, {
|
|
3758
|
-
products: searchable.products
|
|
3759
|
-
});
|
|
3760
|
-
} else {
|
|
3761
|
-
return null;
|
|
3762
|
-
}
|
|
3763
|
-
}
|
|
3764
|
-
function SearchButtonAndDialog({
|
|
3765
|
-
products
|
|
3766
|
-
}) {
|
|
3767
|
-
const [open, setOpen] = React.useState(false);
|
|
3768
|
-
React.useEffect(() => {
|
|
3769
|
-
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
3770
|
-
// window and stopping propagation keeps them from ever seeing it.
|
|
3771
|
-
const handleKeyDown = event => {
|
|
3772
|
-
if (isSearchShortcut(event)) {
|
|
3773
|
-
event.preventDefault();
|
|
3774
|
-
event.stopPropagation();
|
|
3775
|
-
setOpen(wasOpen => !wasOpen);
|
|
3776
|
-
} else if (isLegacySearchShortcut(event)) {
|
|
3777
|
-
event.preventDefault();
|
|
3778
|
-
event.stopPropagation();
|
|
3779
|
-
setOpen(true);
|
|
3780
|
-
}
|
|
3781
|
-
};
|
|
3782
|
-
window.addEventListener("keydown", handleKeyDown, {
|
|
3783
|
-
capture: true
|
|
3784
|
-
});
|
|
3785
|
-
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
3786
|
-
capture: true
|
|
3787
|
-
});
|
|
3788
|
-
}, []);
|
|
3789
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
|
3790
|
-
"aria-keyshortcuts": `${isMac() ? "Meta+K" : "Control+K"} /`,
|
|
3791
|
-
"aria-label": "Search",
|
|
3792
|
-
onClick: () => setOpen(true),
|
|
3793
|
-
type: "button"
|
|
3794
|
-
}, /*#__PURE__*/React.createElement(Spyglass, {
|
|
3795
|
-
fill: "currentColor"
|
|
3796
|
-
})), /*#__PURE__*/React.createElement(tapestry.Modal, {
|
|
3797
|
-
"aria-label": "Search",
|
|
3798
|
-
dismissible: true,
|
|
3799
|
-
onClose: () => setOpen(false),
|
|
3800
|
-
open: open
|
|
3801
|
-
}, /*#__PURE__*/React.createElement(SearchPanel, {
|
|
3802
|
-
products: products
|
|
3803
|
-
})));
|
|
3804
|
-
}
|
|
3805
|
-
function SearchPanel({
|
|
3806
|
-
products
|
|
3807
|
-
}) {
|
|
3808
|
-
var _a, _b;
|
|
3809
|
-
const listboxId = useInstanceId("product-search-results");
|
|
3810
|
-
const [term, setTerm] = React.useState("");
|
|
3811
|
-
const [selectedSlug, setSelectedSlug] = React.useState(() => initialProduct(products).slug);
|
|
3812
|
-
const product = (_a = products.find(({
|
|
3813
|
-
slug
|
|
3814
|
-
}) => slug === selectedSlug)) !== null && _a !== void 0 ? _a : products[0];
|
|
3815
|
-
const hasTerm = term.trim() !== "";
|
|
3816
|
-
const search = useProductSearch(product.slug, term);
|
|
3817
|
-
const groups = "groups" in search ? search.groups : undefined;
|
|
3818
|
-
const results = React.useMemo(() => {
|
|
3819
|
-
var _a;
|
|
3820
|
-
return (_a = groups === null || groups === void 0 ? void 0 : groups.flatMap(group => group.results)) !== null && _a !== void 0 ? _a : [];
|
|
3821
|
-
}, [groups]);
|
|
3822
|
-
const navigation = useResultNavigation(results);
|
|
3823
|
-
const {
|
|
3824
|
-
highlightedIndex
|
|
3825
|
-
} = navigation;
|
|
3826
|
-
const inputRef = React.useRef(null);
|
|
3827
|
-
const [announceHighlight, setAnnounceHighlight] = React.useState(false);
|
|
3828
|
-
// The tabs keep focus while arrowing through results, but only the focused
|
|
3829
|
-
// input can point screen readers at the highlight, so from the tabs it's
|
|
3830
|
-
// announced instead. Typing goes back to the input, keystroke and all.
|
|
3831
|
-
const handleTabsKeyDown = event => {
|
|
3832
|
-
var _a;
|
|
3833
|
-
if (isTypingKey(event)) {
|
|
3834
|
-
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
3835
|
-
} else if (verticalStep(event.key)) {
|
|
3836
|
-
setAnnounceHighlight(true);
|
|
3837
|
-
navigation.handleKeyDown(event);
|
|
3838
|
-
} else {
|
|
3839
|
-
navigation.handleKeyDown(event);
|
|
3840
|
-
}
|
|
3841
|
-
};
|
|
3497
|
+
// The product the most recent finished request was for.
|
|
3498
|
+
function useSettledProduct(product, status) {
|
|
3499
|
+
const [settledProduct, setSettledProduct] = React.useState();
|
|
3842
3500
|
React.useEffect(() => {
|
|
3843
|
-
if (
|
|
3844
|
-
}, [
|
|
3845
|
-
return
|
|
3846
|
-
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
3847
|
-
"aria-autocomplete": "list",
|
|
3848
|
-
"aria-controls": results.length > 0 ? listboxId : undefined,
|
|
3849
|
-
"aria-expanded": results.length > 0,
|
|
3850
|
-
"aria-label": "Search",
|
|
3851
|
-
autoFocus: true,
|
|
3852
|
-
onChange: event => setTerm(event.target.value),
|
|
3853
|
-
onFocus: () => setAnnounceHighlight(false),
|
|
3854
|
-
onKeyDown: navigation.handleKeyDown,
|
|
3855
|
-
placeholder: "Search",
|
|
3856
|
-
ref: inputRef,
|
|
3857
|
-
role: "combobox",
|
|
3858
|
-
type: "search",
|
|
3859
|
-
value: term
|
|
3860
|
-
}), /*#__PURE__*/React.createElement(ProductTabs, {
|
|
3861
|
-
onKeyDown: handleTabsKeyDown,
|
|
3862
|
-
onSelect: setSelectedSlug,
|
|
3863
|
-
products: products,
|
|
3864
|
-
selected: product
|
|
3865
|
-
}, /*#__PURE__*/React.createElement(SearchResults, {
|
|
3866
|
-
highlightedIndex: highlightedIndex,
|
|
3867
|
-
id: listboxId,
|
|
3868
|
-
label: `${product.appName} results`,
|
|
3869
|
-
onHighlight: navigation.highlightIndex,
|
|
3870
|
-
search: search,
|
|
3871
|
-
term: term
|
|
3872
|
-
})), /*#__PURE__*/React.createElement("div", {
|
|
3873
|
-
"aria-live": "polite",
|
|
3874
|
-
className: styles$c.visuallyHidden
|
|
3875
|
-
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.attributes.title : null), /*#__PURE__*/React.createElement(KeyboardHints, {
|
|
3876
|
-
copied: navigation.copied
|
|
3877
|
-
}));
|
|
3878
|
-
}
|
|
3879
|
-
function KeyboardHints({
|
|
3880
|
-
copied
|
|
3881
|
-
}) {
|
|
3882
|
-
const command = commandKeyLabel();
|
|
3883
|
-
return /*#__PURE__*/React.createElement("p", null, "\u2191\u2193 Select \u00B7 \u21B5 Open \u00B7 ", command, "\u21B5 Open in new tab \u00B7 ", command, "C", " ", /*#__PURE__*/React.createElement("span", {
|
|
3884
|
-
role: "status"
|
|
3885
|
-
}, copied ? "Copied!" : "Copy link"), " \u00B7 ", command, "K Open search");
|
|
3886
|
-
}
|
|
3887
|
-
function initialProduct(products) {
|
|
3888
|
-
var _a, _b, _c;
|
|
3889
|
-
const lastProduct = readLastProduct();
|
|
3890
|
-
const bySlug = slug => products.find(product => product.slug === slug);
|
|
3891
|
-
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];
|
|
3501
|
+
if (status === "done") setSettledProduct(product);
|
|
3502
|
+
}, [product, status]);
|
|
3503
|
+
return settledProduct;
|
|
3892
3504
|
}
|
|
3893
3505
|
|
|
3894
3506
|
function parseValue(element) {
|
|
@@ -3957,10 +3569,14 @@ true;
|
|
|
3957
3569
|
return shim.useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3958
3570
|
}
|
|
3959
3571
|
|
|
3960
|
-
|
|
3961
|
-
|
|
3572
|
+
// Entries match the ones home-mobile stores, so one list can sync across web
|
|
3573
|
+
// and mobile.
|
|
3574
|
+
const ENTRY_VERSION = 3;
|
|
3575
|
+
const MAX_ENTRIES = 50;
|
|
3962
3576
|
const MAX_TEXT_LENGTH = 200;
|
|
3963
3577
|
const PRODUCTS = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3578
|
+
// Login has no pages worth returning to.
|
|
3579
|
+
const APPS = PRODUCTS.filter(product => product !== "login");
|
|
3964
3580
|
function isId(value) {
|
|
3965
3581
|
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3966
3582
|
}
|
|
@@ -3969,169 +3585,1116 @@ true;
|
|
|
3969
3585
|
const segments = value.split("/");
|
|
3970
3586
|
return segments.length === 4 && PRODUCTS.some(product => product === segments[0]) && isId(segments[3]);
|
|
3971
3587
|
}
|
|
3972
|
-
function
|
|
3973
|
-
return
|
|
3588
|
+
function isApp(value) {
|
|
3589
|
+
return APPS.some(app => app === value);
|
|
3590
|
+
}
|
|
3591
|
+
// Links are built by resolving the path against its app's origin, so a stored
|
|
3592
|
+
// path must not be able to resolve anywhere else (`//evil.com`, `/\evil.com`).
|
|
3593
|
+
function isPath(value) {
|
|
3594
|
+
const origin = "https://app.invalid";
|
|
3595
|
+
try {
|
|
3596
|
+
return typeof value === "string" && value.startsWith("/") && new URL(value, origin).origin === origin;
|
|
3597
|
+
} catch {
|
|
3598
|
+
return false;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
function isNonBlankString(value) {
|
|
3602
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3603
|
+
}
|
|
3604
|
+
function isOptionalString(value) {
|
|
3605
|
+
return value === undefined || typeof value === "string";
|
|
3606
|
+
}
|
|
3607
|
+
function isRecord(value) {
|
|
3608
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3609
|
+
}
|
|
3610
|
+
function isPageMetadata(value) {
|
|
3611
|
+
return value === undefined || isRecord(value) && isOptionalString(value.details) && isOptionalString(value.type);
|
|
3612
|
+
}
|
|
3613
|
+
// The query string is part of the link but not of the page's identity, so
|
|
3614
|
+
// revisiting with other filters moves the existing entry instead of adding one.
|
|
3615
|
+
function entryKey(entry) {
|
|
3616
|
+
if (entry.type === "resource") {
|
|
3617
|
+
return `resource:${entry.pcrl}`;
|
|
3618
|
+
} else {
|
|
3619
|
+
return `page:${entry.app}:${entry.path.split("?")[0]}`;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
function isEntry(value) {
|
|
3623
|
+
if (!isRecord(value) || value.version !== ENTRY_VERSION || typeof value.visitedAt !== "string") {
|
|
3624
|
+
return false;
|
|
3625
|
+
} else if (value.type === "resource") {
|
|
3626
|
+
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3627
|
+
} else if (value.type === "page") {
|
|
3628
|
+
return isApp(value.app) && isPath(value.path) && isNonBlankString(value.title) && isPageMetadata(value.metadata);
|
|
3629
|
+
} else {
|
|
3630
|
+
return false;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
// Cuts on code points so a surrogate pair is never split.
|
|
3634
|
+
function clampText(value) {
|
|
3635
|
+
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3636
|
+
}
|
|
3637
|
+
function clampMetadata({
|
|
3638
|
+
details,
|
|
3639
|
+
type
|
|
3640
|
+
}) {
|
|
3641
|
+
return {
|
|
3642
|
+
...(details && {
|
|
3643
|
+
details: clampText(details)
|
|
3644
|
+
}),
|
|
3645
|
+
...(type && {
|
|
3646
|
+
type: clampText(type)
|
|
3647
|
+
})
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
function clampVisit(visit) {
|
|
3651
|
+
if (visit.type === "resource") {
|
|
3652
|
+
return visit;
|
|
3653
|
+
} else {
|
|
3654
|
+
return {
|
|
3655
|
+
...visit,
|
|
3656
|
+
...(visit.metadata && {
|
|
3657
|
+
metadata: clampMetadata(visit.metadata)
|
|
3658
|
+
}),
|
|
3659
|
+
title: clampText(visit.title.trim())
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
function toEntry(visit, visitedAt) {
|
|
3664
|
+
return {
|
|
3665
|
+
...clampVisit(visit),
|
|
3666
|
+
version: ENTRY_VERSION,
|
|
3667
|
+
visitedAt: visitedAt.toISOString()
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
3671
|
+
// a single bad entry, a list without it) rather than throwing.
|
|
3672
|
+
function parseRecentlyVisited(value) {
|
|
3673
|
+
let parsed = null;
|
|
3674
|
+
try {
|
|
3675
|
+
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
3676
|
+
} catch {
|
|
3677
|
+
parsed = null;
|
|
3678
|
+
}
|
|
3679
|
+
if (Array.isArray(parsed)) {
|
|
3680
|
+
return parsed.filter(isEntry);
|
|
3681
|
+
} else {
|
|
3682
|
+
return [];
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
function addVisit(entries, entry) {
|
|
3686
|
+
const key = entryKey(entry);
|
|
3687
|
+
return [entry, ...entries.filter(existing => entryKey(existing) !== key)].slice(0, MAX_ENTRIES);
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
// The app whose URL, in this environment, has the current page's host.
|
|
3691
|
+
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
3692
|
+
// and guarantees the stored app rebuilds a working link.
|
|
3693
|
+
function currentApp() {
|
|
3694
|
+
var _a;
|
|
3695
|
+
return (_a = APPS.find(app => new URL(pcoUrl$1.pcoUrl(app)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
3696
|
+
}
|
|
3697
|
+
function isFilled(value) {
|
|
3698
|
+
return typeof value === "string" && value !== "";
|
|
3699
|
+
}
|
|
3700
|
+
// The tag's metadata is product-supplied JSON; only its string `type` and
|
|
3701
|
+
// `details` carry over, and an entry without either has no metadata at all.
|
|
3702
|
+
function metadataFromPage(metadata) {
|
|
3703
|
+
var _a;
|
|
3704
|
+
try {
|
|
3705
|
+
const {
|
|
3706
|
+
details,
|
|
3707
|
+
type
|
|
3708
|
+
} = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) !== null && _a !== void 0 ? _a : {};
|
|
3709
|
+
if (isFilled(details) || isFilled(type)) {
|
|
3710
|
+
return {
|
|
3711
|
+
...(isFilled(details) && {
|
|
3712
|
+
details
|
|
3713
|
+
}),
|
|
3714
|
+
...(isFilled(type) && {
|
|
3715
|
+
type
|
|
3716
|
+
})
|
|
3717
|
+
};
|
|
3718
|
+
} else {
|
|
3719
|
+
return undefined;
|
|
3720
|
+
}
|
|
3721
|
+
} catch {
|
|
3722
|
+
return undefined;
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
// Staff impersonating someone browse under `/O<org id>/P<person id>/…`.
|
|
3726
|
+
function withoutImpersonation(segments) {
|
|
3727
|
+
const [org = "", person = ""] = segments;
|
|
3728
|
+
if (org.startsWith("O") && isId(org.slice(1)) && person.startsWith("P") && isId(person.slice(1))) {
|
|
3729
|
+
return segments.slice(2);
|
|
3730
|
+
} else {
|
|
3731
|
+
return segments;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
// Normalized the way home-mobile does it, so a page visited on web and on
|
|
3735
|
+
// mobile is one entry once the list syncs.
|
|
3736
|
+
function pathFromLocation({
|
|
3737
|
+
pathname,
|
|
3738
|
+
search
|
|
3739
|
+
}) {
|
|
3740
|
+
const segments = withoutImpersonation(pathname.split("/").filter(Boolean));
|
|
3741
|
+
if (segments.length === 0) {
|
|
3742
|
+
return "/";
|
|
3743
|
+
} else {
|
|
3744
|
+
return `/${segments.join("/")}${search.length > 1 ? search : ""}`;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
// pco:resource is the more specific signal, so it wins when both tags are
|
|
3748
|
+
// present. A resource that isn't a PCRL is ignored rather than recorded.
|
|
3749
|
+
function visitFromPage({
|
|
3750
|
+
metadata,
|
|
3751
|
+
resource,
|
|
3752
|
+
title
|
|
3753
|
+
}) {
|
|
3754
|
+
const app = currentApp();
|
|
3755
|
+
if (resource && isPcrl(resource)) {
|
|
3756
|
+
return {
|
|
3757
|
+
pcrl: resource,
|
|
3758
|
+
type: "resource"
|
|
3759
|
+
};
|
|
3760
|
+
} else if (title && title.trim() !== "" && app) {
|
|
3761
|
+
const pageMetadata = metadataFromPage(metadata);
|
|
3762
|
+
return {
|
|
3763
|
+
app,
|
|
3764
|
+
...(pageMetadata && {
|
|
3765
|
+
metadata: pageMetadata
|
|
3766
|
+
}),
|
|
3767
|
+
path: pathFromLocation(window.location),
|
|
3768
|
+
title,
|
|
3769
|
+
type: "page"
|
|
3770
|
+
};
|
|
3771
|
+
} else {
|
|
3772
|
+
return null;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
// The entry Recently Visited would record for the current page, or null when
|
|
3776
|
+
// the page isn't eligible. Shared by the recorder and the debug panel so the
|
|
3777
|
+
// panel always shows what actually gets stored.
|
|
3778
|
+
function useCurrentVisit() {
|
|
3779
|
+
const {
|
|
3780
|
+
recently_visitable: title,
|
|
3781
|
+
resource
|
|
3782
|
+
} = usePcoMeta();
|
|
3783
|
+
const metadata = useRecentlyVisitableMetadata();
|
|
3784
|
+
return React.useMemo(() => visitFromPage({
|
|
3785
|
+
metadata,
|
|
3786
|
+
resource,
|
|
3787
|
+
title
|
|
3788
|
+
}), [metadata, resource, title]);
|
|
3789
|
+
}
|
|
3790
|
+
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
3791
|
+
// and several pass it as a number.
|
|
3792
|
+
function useCurrentPersonId() {
|
|
3793
|
+
const {
|
|
3794
|
+
userId
|
|
3795
|
+
} = useStore();
|
|
3796
|
+
return userId == null ? undefined : String(userId);
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
// Per-origin localStorage is a stopgap: each product keeps its own list until
|
|
3800
|
+
// PCA-409 moves this to storage shared across product subdomains. Only this
|
|
3801
|
+
// file should need to change when it does.
|
|
3802
|
+
function storageKey({
|
|
3803
|
+
env,
|
|
3804
|
+
personId
|
|
3805
|
+
}) {
|
|
3806
|
+
return `topbar.recently_visited.${env}.${personId}`;
|
|
3807
|
+
}
|
|
3808
|
+
// localStorage doesn't notify the tab that wrote it, so readers in this tab
|
|
3809
|
+
// learn about new visits from this counter instead.
|
|
3810
|
+
const changes = createStore({
|
|
3811
|
+
version: 0
|
|
3812
|
+
});
|
|
3813
|
+
function readRecentlyVisited(scope) {
|
|
3814
|
+
try {
|
|
3815
|
+
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)));
|
|
3816
|
+
} catch {
|
|
3817
|
+
return [];
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
function recordVisit(visit, scope) {
|
|
3821
|
+
const entry = toEntry(visit, new Date());
|
|
3822
|
+
if (isEntry(entry)) {
|
|
3823
|
+
try {
|
|
3824
|
+
localStorage.setItem(storageKey(scope), JSON.stringify(addVisit(readRecentlyVisited(scope), entry)));
|
|
3825
|
+
changes.setState({
|
|
3826
|
+
version: changes.getState().version + 1
|
|
3827
|
+
});
|
|
3828
|
+
} catch {
|
|
3829
|
+
// Storage can be full or blocked; recents are disposable.
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
function useRecentlyVisited({
|
|
3834
|
+
env,
|
|
3835
|
+
personId
|
|
3836
|
+
}) {
|
|
3837
|
+
const version = shim.useSyncExternalStore(changes.subscribe, () => changes.getState().version);
|
|
3838
|
+
// `version` isn't read, but listing it re-reads storage after each visit.
|
|
3839
|
+
return React.useMemo(() => personId ? readRecentlyVisited({
|
|
3840
|
+
env,
|
|
3841
|
+
personId
|
|
3842
|
+
}) : [], [env, personId, version]);
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
const pcoUrl = pcoUrl$1;
|
|
3846
|
+
|
|
3847
|
+
// Resolves resource entries with one pcrl_lookups request per product.
|
|
3848
|
+
// pcrl_lookups reports forbidden and deleted resources the same way, so both
|
|
3849
|
+
// become `unresolved`, as does every entry in a batch whose request failed.
|
|
3850
|
+
function useHydratedRecentlyVisited(entries, env) {
|
|
3851
|
+
const batches = React.useMemo(() => batchByProduct(entries), [entries]);
|
|
3852
|
+
const urls = React.useMemo(() => batches.map(([product, pcrls]) => pcrlLookupsUrl(product, pcrls)), [batches]);
|
|
3853
|
+
const requests = useCachedRequests(urls);
|
|
3854
|
+
const lookups = new Map();
|
|
3855
|
+
batches.forEach(([, pcrls], index) => {
|
|
3856
|
+
const request = requests[index];
|
|
3857
|
+
for (const pcrl of pcrls) lookups.set(pcrl, lookupFor(pcrl, request));
|
|
3858
|
+
});
|
|
3859
|
+
return entries.map(entry => {
|
|
3860
|
+
var _a;
|
|
3861
|
+
if (entry.type === "page") {
|
|
3862
|
+
return {
|
|
3863
|
+
entry,
|
|
3864
|
+
status: "page",
|
|
3865
|
+
url: pageUrl(entry, env)
|
|
3866
|
+
};
|
|
3867
|
+
} else {
|
|
3868
|
+
const lookup = (_a = lookups.get(entry.pcrl)) !== null && _a !== void 0 ? _a : {
|
|
3869
|
+
status: "unresolved"
|
|
3870
|
+
};
|
|
3871
|
+
if (lookup.status === "resolved") {
|
|
3872
|
+
return {
|
|
3873
|
+
...lookup,
|
|
3874
|
+
entry,
|
|
3875
|
+
url: lookup.result.attributes.url
|
|
3876
|
+
};
|
|
3877
|
+
} else {
|
|
3878
|
+
return {
|
|
3879
|
+
entry,
|
|
3880
|
+
status: lookup.status
|
|
3881
|
+
};
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
});
|
|
3885
|
+
}
|
|
3886
|
+
function lookupFor(pcrl, request) {
|
|
3887
|
+
if (request.status === "done") {
|
|
3888
|
+
const result = request.data.data.find(({
|
|
3889
|
+
attributes
|
|
3890
|
+
}) => attributes.pcrl === pcrl);
|
|
3891
|
+
return result ? {
|
|
3892
|
+
result,
|
|
3893
|
+
status: "resolved"
|
|
3894
|
+
} : {
|
|
3895
|
+
status: "unresolved"
|
|
3896
|
+
};
|
|
3897
|
+
} else if (request.status === "error") {
|
|
3898
|
+
return {
|
|
3899
|
+
status: "unresolved"
|
|
3900
|
+
};
|
|
3901
|
+
} else {
|
|
3902
|
+
return {
|
|
3903
|
+
status: "loading"
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
const RECENTLY_VISITED_PAGE_SIZE = 10;
|
|
3908
|
+
// A PCRL can only be looked up through its own product's API, which may not be
|
|
3909
|
+
// the product it was visited in. Each page of entries is batched on its own,
|
|
3910
|
+
// so loading the next page leaves earlier pages' URLs, and so their cache
|
|
3911
|
+
// keys, alone. Sorting keeps them stable as a page reorders.
|
|
3912
|
+
function batchByProduct(entries) {
|
|
3913
|
+
var _a;
|
|
3914
|
+
const batches = [];
|
|
3915
|
+
for (let start = 0; start < entries.length; start += RECENTLY_VISITED_PAGE_SIZE) {
|
|
3916
|
+
const page = new Map();
|
|
3917
|
+
for (const entry of entries.slice(start, start + RECENTLY_VISITED_PAGE_SIZE)) {
|
|
3918
|
+
if (entry.type === "resource") {
|
|
3919
|
+
const [product] = entry.pcrl.split("/");
|
|
3920
|
+
page.set(product, [...((_a = page.get(product)) !== null && _a !== void 0 ? _a : []), entry.pcrl]);
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
for (const [product, pcrls] of page) {
|
|
3924
|
+
batches.push([product, [...pcrls].sort()]);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
return batches;
|
|
3928
|
+
}
|
|
3929
|
+
function pcrlLookupsUrl(product, pcrls) {
|
|
3930
|
+
const params = new URLSearchParams({
|
|
3931
|
+
"where[pcrl]": pcrls.join(",")
|
|
3932
|
+
});
|
|
3933
|
+
return `${window.location.origin}/~api/${product}/v2/pcrl_lookups?${params}`;
|
|
3934
|
+
}
|
|
3935
|
+
// Built from the app rather than the current origin, so it stays right once
|
|
3936
|
+
// PCA-409 shares one list across product subdomains.
|
|
3937
|
+
function pageUrl(entry, env) {
|
|
3938
|
+
return new URL(entry.path, pcoUrl(env)(entry.app)).href;
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
function useRecentVisits(env) {
|
|
3942
|
+
const personId = useCurrentPersonId();
|
|
3943
|
+
const entries = useRecentlyVisited({
|
|
3944
|
+
env,
|
|
3945
|
+
personId
|
|
3946
|
+
});
|
|
3947
|
+
const [count, setCount] = React.useState(RECENTLY_VISITED_PAGE_SIZE);
|
|
3948
|
+
const shown = React.useMemo(() => entries.slice(0, count), [entries, count]);
|
|
3949
|
+
const hydrated = useHydratedRecentlyVisited(shown, env);
|
|
3950
|
+
// Resources show up once they're found. Ones that can't be found anymore,
|
|
3951
|
+
// or that the person can no longer see, are left out.
|
|
3952
|
+
const visits = hydrated.filter(isShown);
|
|
3953
|
+
const loading = hydrated.some(({
|
|
3954
|
+
status
|
|
3955
|
+
}) => status === "loading");
|
|
3956
|
+
const showMore = React.useCallback(() => setCount(current => current + RECENTLY_VISITED_PAGE_SIZE), []);
|
|
3957
|
+
return {
|
|
3958
|
+
count,
|
|
3959
|
+
hasMore: entries.length > count,
|
|
3960
|
+
loading,
|
|
3961
|
+
showMore,
|
|
3962
|
+
visits
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
function isShown(visit) {
|
|
3966
|
+
return visit.status === "page" || visit.status === "resolved";
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
function open(url, {
|
|
3970
|
+
newTab
|
|
3971
|
+
}) {
|
|
3972
|
+
if (newTab) {
|
|
3973
|
+
window.open(url, "_blank", "noopener");
|
|
3974
|
+
} else {
|
|
3975
|
+
window.location.assign(url);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
// With text selected, ⌘C should copy that text as usual, not the link.
|
|
3979
|
+
function hasSelectedText(element) {
|
|
3980
|
+
var _a, _b;
|
|
3981
|
+
const selectedInField = element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
3982
|
+
return selectedInField || ((_b = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "") !== "";
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
const COPIED_FEEDBACK_MS = 1500;
|
|
3986
|
+
// `copiedUrl` stays set briefly after a copy so the UI can confirm it.
|
|
3987
|
+
function useCopyLink() {
|
|
3988
|
+
const [copiedUrl, setCopiedUrl] = React.useState(null);
|
|
3989
|
+
React.useEffect(() => {
|
|
3990
|
+
if (copiedUrl) {
|
|
3991
|
+
const timeout = setTimeout(() => setCopiedUrl(null), COPIED_FEEDBACK_MS);
|
|
3992
|
+
return () => clearTimeout(timeout);
|
|
3993
|
+
} else {
|
|
3994
|
+
return undefined;
|
|
3995
|
+
}
|
|
3996
|
+
}, [copiedUrl]);
|
|
3997
|
+
const copy = React.useCallback(url => {
|
|
3998
|
+
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
3999
|
+
}, []);
|
|
4000
|
+
return {
|
|
4001
|
+
copiedUrl,
|
|
4002
|
+
copy
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
// Remembering which list the index belongs to puts the highlight back on the
|
|
4007
|
+
// first item whenever a new list replaces it, without an effect to reset it.
|
|
4008
|
+
// A list that grows or fills in under the same `list` keeps its highlight.
|
|
4009
|
+
function useResultNavigation(items, list) {
|
|
4010
|
+
const [highlight, setHighlight] = React.useState({
|
|
4011
|
+
index: 0,
|
|
4012
|
+
list
|
|
4013
|
+
});
|
|
4014
|
+
const highlightedIndex = highlight.list === list ? highlight.index : 0;
|
|
4015
|
+
const highlighted = items[highlightedIndex];
|
|
4016
|
+
const {
|
|
4017
|
+
copiedUrl,
|
|
4018
|
+
copy
|
|
4019
|
+
} = useCopyLink();
|
|
4020
|
+
const highlightIndex = React.useCallback(index => setHighlight(current => current.list === list && current.index === index ? current : {
|
|
4021
|
+
index,
|
|
4022
|
+
list
|
|
4023
|
+
}), [list]);
|
|
4024
|
+
const handleKeyDown = event => {
|
|
4025
|
+
const step = verticalStep(event.key);
|
|
4026
|
+
if (step && items.length > 0) {
|
|
4027
|
+
event.preventDefault();
|
|
4028
|
+
highlightIndex((highlightedIndex + step + items.length) % items.length);
|
|
4029
|
+
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
4030
|
+
event.preventDefault();
|
|
4031
|
+
open(highlighted.url, {
|
|
4032
|
+
newTab: hasCommandKey(event)
|
|
4033
|
+
});
|
|
4034
|
+
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
4035
|
+
event.preventDefault();
|
|
4036
|
+
copy(highlighted.url);
|
|
4037
|
+
}
|
|
4038
|
+
};
|
|
4039
|
+
return {
|
|
4040
|
+
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.url),
|
|
4041
|
+
handleKeyDown,
|
|
4042
|
+
highlightIndex,
|
|
4043
|
+
highlighted,
|
|
4044
|
+
highlightedIndex: highlighted ? highlightedIndex : null
|
|
4045
|
+
};
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
function groupSections(groups = []) {
|
|
4049
|
+
return groups.map(({
|
|
4050
|
+
results,
|
|
4051
|
+
type
|
|
4052
|
+
}) => ({
|
|
4053
|
+
heading: type,
|
|
4054
|
+
items: results.map(resultItem),
|
|
4055
|
+
key: type
|
|
4056
|
+
}));
|
|
4057
|
+
}
|
|
4058
|
+
function resultItem({
|
|
4059
|
+
attributes,
|
|
4060
|
+
id
|
|
4061
|
+
}) {
|
|
4062
|
+
return {
|
|
4063
|
+
key: id,
|
|
4064
|
+
subtitle: summarize(attributes.metadata),
|
|
4065
|
+
title: attributes.title,
|
|
4066
|
+
url: attributes.url
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
function visitItem(visit) {
|
|
4070
|
+
return {
|
|
4071
|
+
key: entryKey(visit.entry),
|
|
4072
|
+
...describeVisit(visit)
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
function summarize(metadata) {
|
|
4076
|
+
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
4077
|
+
}
|
|
4078
|
+
// Unlike search results, recents come from every product, so each one names
|
|
4079
|
+
// its product and type.
|
|
4080
|
+
function describeVisit(visit) {
|
|
4081
|
+
var _a, _b;
|
|
4082
|
+
const subtitle = (...parts) => parts.filter(Boolean).join(" · ");
|
|
4083
|
+
if (visit.status === "page") {
|
|
4084
|
+
const {
|
|
4085
|
+
app,
|
|
4086
|
+
metadata,
|
|
4087
|
+
title
|
|
4088
|
+
} = visit.entry;
|
|
4089
|
+
return {
|
|
4090
|
+
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 : ""),
|
|
4091
|
+
title,
|
|
4092
|
+
url: visit.url
|
|
4093
|
+
};
|
|
4094
|
+
} else {
|
|
4095
|
+
const {
|
|
4096
|
+
metadata,
|
|
4097
|
+
product,
|
|
4098
|
+
title,
|
|
4099
|
+
type
|
|
4100
|
+
} = visit.result.attributes;
|
|
4101
|
+
return {
|
|
4102
|
+
subtitle: subtitle(productName(product), type, summarize(metadata)),
|
|
4103
|
+
title,
|
|
4104
|
+
url: visit.url
|
|
4105
|
+
};
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
// check-ins → Check-Ins
|
|
4109
|
+
function productName(slug) {
|
|
4110
|
+
return slug.split("-").map(capitalize).join("-");
|
|
4111
|
+
}
|
|
4112
|
+
function capitalize(word) {
|
|
4113
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
const STORAGE_KEY = "topbar.product_search.last_product";
|
|
4117
|
+
function readLastProduct() {
|
|
4118
|
+
try {
|
|
4119
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
4120
|
+
} catch {
|
|
4121
|
+
return null;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
function saveLastProduct(slug) {
|
|
4125
|
+
try {
|
|
4126
|
+
localStorage.setItem(STORAGE_KEY, slug);
|
|
4127
|
+
} catch {
|
|
4128
|
+
// Storage can be full or blocked; search still works without it.
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
function initialProduct(products) {
|
|
4132
|
+
var _a, _b, _c;
|
|
4133
|
+
const lastProduct = readLastProduct();
|
|
4134
|
+
const bySlug = slug => products.find(product => product.slug === slug);
|
|
4135
|
+
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];
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
function KeyboardHints({
|
|
4139
|
+
copied
|
|
4140
|
+
}) {
|
|
4141
|
+
const command = commandKeyLabel();
|
|
4142
|
+
return /*#__PURE__*/React.createElement("p", null, "\u2191\u2193 Select \u00B7 \u21B5 Open \u00B7 ", command, "\u21B5 Open in new tab \u00B7 ", command, "C", " ", /*#__PURE__*/React.createElement("span", {
|
|
4143
|
+
role: "status"
|
|
4144
|
+
}, copied ? "Copied!" : "Copy link"), " \u00B7 ", command, "K Open search");
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
var styles$d = {"tab":"tab-dz8ki"};
|
|
4148
|
+
|
|
4149
|
+
function ProductTabs({
|
|
4150
|
+
children,
|
|
4151
|
+
onKeyDown,
|
|
4152
|
+
onSelect,
|
|
4153
|
+
products,
|
|
4154
|
+
selected
|
|
4155
|
+
}) {
|
|
4156
|
+
const id = useInstanceId("product-tabs");
|
|
4157
|
+
const tabId = slug => `${id}-tab-${slug}`;
|
|
4158
|
+
const tabRefs = React.useRef(new Map());
|
|
4159
|
+
const handleKeyDown = event => {
|
|
4160
|
+
var _a;
|
|
4161
|
+
const offset = horizontalStep(event.key);
|
|
4162
|
+
if (offset) {
|
|
4163
|
+
event.preventDefault();
|
|
4164
|
+
const index = products.indexOf(selected);
|
|
4165
|
+
const next = products[(index + offset + products.length) % products.length];
|
|
4166
|
+
onSelect(next.slug);
|
|
4167
|
+
(_a = tabRefs.current.get(next.slug)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4168
|
+
} else {
|
|
4169
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
4170
|
+
}
|
|
4171
|
+
};
|
|
4172
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
4173
|
+
"aria-label": "Product",
|
|
4174
|
+
onKeyDown: handleKeyDown,
|
|
4175
|
+
role: "tablist"
|
|
4176
|
+
}, products.map(product => (/*#__PURE__*/React.createElement("button", {
|
|
4177
|
+
"aria-controls": `${id}-panel`,
|
|
4178
|
+
"aria-selected": product === selected,
|
|
4179
|
+
className: styles$d.tab,
|
|
4180
|
+
id: tabId(product.slug),
|
|
4181
|
+
key: product.slug,
|
|
4182
|
+
onClick: () => onSelect(product.slug),
|
|
4183
|
+
ref: element => {
|
|
4184
|
+
if (element) {
|
|
4185
|
+
tabRefs.current.set(product.slug, element);
|
|
4186
|
+
} else {
|
|
4187
|
+
tabRefs.current.delete(product.slug);
|
|
4188
|
+
}
|
|
4189
|
+
},
|
|
4190
|
+
role: "tab",
|
|
4191
|
+
tabIndex: product === selected ? 0 : -1,
|
|
4192
|
+
type: "button"
|
|
4193
|
+
}, product.appName)))), /*#__PURE__*/React.createElement("div", {
|
|
4194
|
+
"aria-labelledby": tabId(selected.slug),
|
|
4195
|
+
id: `${id}-panel`,
|
|
4196
|
+
role: "tabpanel"
|
|
4197
|
+
}, children));
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
function EndOfResultsObserver({
|
|
4201
|
+
onVisible
|
|
4202
|
+
}) {
|
|
4203
|
+
const ref = React.useRef(null);
|
|
4204
|
+
React.useEffect(() => {
|
|
4205
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
4206
|
+
if (entry.isIntersecting) onVisible();
|
|
4207
|
+
});
|
|
4208
|
+
if (ref.current) observer.observe(ref.current);
|
|
4209
|
+
return () => observer.disconnect();
|
|
4210
|
+
}, [onVisible]);
|
|
4211
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
4212
|
+
ref: ref
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
4217
|
+
|
|
4218
|
+
var styles$c = {"result":"result-Thq2C"};
|
|
4219
|
+
|
|
4220
|
+
function SearchResult({
|
|
4221
|
+
highlighted,
|
|
4222
|
+
id,
|
|
4223
|
+
onHighlight,
|
|
4224
|
+
subtitle,
|
|
4225
|
+
title,
|
|
4226
|
+
url
|
|
4227
|
+
}) {
|
|
4228
|
+
const ref = React.useRef(null);
|
|
4229
|
+
React.useEffect(() => {
|
|
4230
|
+
var _a;
|
|
4231
|
+
if (highlighted) (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
4232
|
+
block: "nearest"
|
|
4233
|
+
});
|
|
4234
|
+
}, [highlighted]);
|
|
4235
|
+
// Focus stays in the search input, which points at the highlighted result
|
|
4236
|
+
// with aria-activedescendant, so results are kept out of the tab order.
|
|
4237
|
+
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
4238
|
+
// pointer, and that shouldn't steal the highlight.
|
|
4239
|
+
return /*#__PURE__*/React.createElement("a", {
|
|
4240
|
+
"aria-selected": highlighted,
|
|
4241
|
+
className: styles$c.result,
|
|
4242
|
+
href: url,
|
|
4243
|
+
id: id,
|
|
4244
|
+
onMouseMove: onHighlight,
|
|
4245
|
+
ref: ref,
|
|
4246
|
+
role: "option",
|
|
4247
|
+
tabIndex: -1
|
|
4248
|
+
}, /*#__PURE__*/React.createElement("div", null, title), /*#__PURE__*/React.createElement("div", null, subtitle));
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
// Sections with a heading render as labelled groups; ones without render
|
|
4252
|
+
// their items straight into the listbox. Indexes run across every section.
|
|
4253
|
+
function ResultListbox({
|
|
4254
|
+
busy,
|
|
4255
|
+
highlightedIndex,
|
|
4256
|
+
id,
|
|
4257
|
+
label,
|
|
4258
|
+
labelledBy,
|
|
4259
|
+
onHighlight,
|
|
4260
|
+
sections
|
|
4261
|
+
}) {
|
|
4262
|
+
let index = 0;
|
|
4263
|
+
const renderItem = item => {
|
|
4264
|
+
const itemIndex = index++;
|
|
4265
|
+
return /*#__PURE__*/React.createElement(SearchResult, {
|
|
4266
|
+
highlighted: itemIndex === highlightedIndex,
|
|
4267
|
+
id: resultId(id, itemIndex),
|
|
4268
|
+
key: item.key,
|
|
4269
|
+
onHighlight: () => onHighlight(itemIndex),
|
|
4270
|
+
subtitle: item.subtitle,
|
|
4271
|
+
title: item.title,
|
|
4272
|
+
url: item.url
|
|
4273
|
+
});
|
|
4274
|
+
};
|
|
4275
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
4276
|
+
"aria-busy": busy,
|
|
4277
|
+
"aria-label": label,
|
|
4278
|
+
"aria-labelledby": labelledBy,
|
|
4279
|
+
id: id,
|
|
4280
|
+
role: "listbox"
|
|
4281
|
+
}, sections.map((section, sectionIndex) => {
|
|
4282
|
+
if (section.heading === undefined) {
|
|
4283
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
4284
|
+
key: section.key
|
|
4285
|
+
}, section.items.map(renderItem));
|
|
4286
|
+
}
|
|
4287
|
+
const headingId = `${id}-group-${sectionIndex}`;
|
|
4288
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
4289
|
+
"aria-labelledby": headingId,
|
|
4290
|
+
key: section.key,
|
|
4291
|
+
role: "group"
|
|
4292
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
4293
|
+
id: headingId
|
|
4294
|
+
}, section.heading), section.items.map(renderItem));
|
|
4295
|
+
}));
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
function RecentlyVisited({
|
|
4299
|
+
count,
|
|
4300
|
+
hasMore,
|
|
4301
|
+
items,
|
|
4302
|
+
loading,
|
|
4303
|
+
showMore,
|
|
4304
|
+
...navigation
|
|
4305
|
+
}) {
|
|
4306
|
+
const headingId = `${navigation.id}-heading`;
|
|
4307
|
+
if (items.length === 0 && !hasMore && !loading) {
|
|
4308
|
+
return /*#__PURE__*/React.createElement("p", null, "Pages you visit will show up here.");
|
|
4309
|
+
} else {
|
|
4310
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
4311
|
+
id: headingId
|
|
4312
|
+
}, "Recently visited"), /*#__PURE__*/React.createElement(ResultListbox, {
|
|
4313
|
+
labelledBy: headingId,
|
|
4314
|
+
sections: [{
|
|
4315
|
+
items,
|
|
4316
|
+
key: "recents"
|
|
4317
|
+
}],
|
|
4318
|
+
...navigation
|
|
4319
|
+
}), hasMore && /*#__PURE__*/React.createElement(EndOfResultsObserver, {
|
|
4320
|
+
key: count,
|
|
4321
|
+
onVisible: showMore
|
|
4322
|
+
}));
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
// A combobox: focus stays here while aria-activedescendant points screen
|
|
4327
|
+
// readers at the highlighted result in the listbox it controls.
|
|
4328
|
+
function SearchInput({
|
|
4329
|
+
expanded,
|
|
4330
|
+
highlightedIndex,
|
|
4331
|
+
inputRef,
|
|
4332
|
+
listboxId,
|
|
4333
|
+
onChange,
|
|
4334
|
+
onFocus,
|
|
4335
|
+
onKeyDown,
|
|
4336
|
+
value
|
|
4337
|
+
}) {
|
|
4338
|
+
return /*#__PURE__*/React.createElement("input", {
|
|
4339
|
+
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
4340
|
+
"aria-autocomplete": "list",
|
|
4341
|
+
"aria-controls": expanded ? listboxId : undefined,
|
|
4342
|
+
"aria-expanded": expanded,
|
|
4343
|
+
"aria-label": "Search",
|
|
4344
|
+
autoFocus: true,
|
|
4345
|
+
onChange: event => onChange(event.target.value),
|
|
4346
|
+
onFocus: onFocus,
|
|
4347
|
+
onKeyDown: onKeyDown,
|
|
4348
|
+
placeholder: "Search",
|
|
4349
|
+
ref: inputRef,
|
|
4350
|
+
role: "combobox",
|
|
4351
|
+
type: "search",
|
|
4352
|
+
value: value
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
var styles$b = {"visuallyHidden":"visuallyHidden-S9VG5"};
|
|
4357
|
+
|
|
4358
|
+
function SearchResults({
|
|
4359
|
+
label,
|
|
4360
|
+
sections,
|
|
4361
|
+
status,
|
|
4362
|
+
term,
|
|
4363
|
+
...navigation
|
|
4364
|
+
}) {
|
|
4365
|
+
switch (status) {
|
|
4366
|
+
case "idle":
|
|
4367
|
+
return null;
|
|
4368
|
+
case "loading":
|
|
4369
|
+
if (sections.length > 0) {
|
|
4370
|
+
return /*#__PURE__*/React.createElement(ResultListbox, {
|
|
4371
|
+
busy: true,
|
|
4372
|
+
label: label,
|
|
4373
|
+
sections: sections,
|
|
4374
|
+
...navigation
|
|
4375
|
+
});
|
|
4376
|
+
}
|
|
4377
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4378
|
+
role: "status"
|
|
4379
|
+
}, "Searching\u2026");
|
|
4380
|
+
case "error":
|
|
4381
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4382
|
+
role: "alert"
|
|
4383
|
+
}, "Search isn\u2019t working right now. Try again.");
|
|
4384
|
+
case "done":
|
|
4385
|
+
if (sections.length === 0) {
|
|
4386
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4387
|
+
role: "status"
|
|
4388
|
+
}, "No results for \u201C", term.trim(), "\u201D");
|
|
4389
|
+
}
|
|
4390
|
+
return /*#__PURE__*/React.createElement(ResultListbox, {
|
|
4391
|
+
busy: false,
|
|
4392
|
+
label: label,
|
|
4393
|
+
sections: sections,
|
|
4394
|
+
...navigation
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4399
|
+
// Keys the recents list for navigation, so the highlight
|
|
4400
|
+
// stays put as more recents load and as their lookups answer.
|
|
4401
|
+
const RECENTS = "recents";
|
|
4402
|
+
function SearchPanel({
|
|
4403
|
+
env,
|
|
4404
|
+
products
|
|
4405
|
+
}) {
|
|
4406
|
+
var _a, _b;
|
|
4407
|
+
const listboxId = useInstanceId("product-search-results");
|
|
4408
|
+
const [term, setTerm] = React.useState("");
|
|
4409
|
+
const [selectedSlug, setSelectedSlug] = React.useState(() => initialProduct(products).slug);
|
|
4410
|
+
const product = (_a = products.find(({
|
|
4411
|
+
slug
|
|
4412
|
+
}) => slug === selectedSlug)) !== null && _a !== void 0 ? _a : products[0];
|
|
4413
|
+
const hasTerm = term.trim() !== "";
|
|
4414
|
+
const search = useProductSearch(product.slug, term);
|
|
4415
|
+
const groups = "groups" in search ? search.groups : undefined;
|
|
4416
|
+
const sections = React.useMemo(() => groupSections(groups), [groups]);
|
|
4417
|
+
const {
|
|
4418
|
+
visits,
|
|
4419
|
+
...recents
|
|
4420
|
+
} = useRecentVisits(env);
|
|
4421
|
+
const recentItems = visits.map(visitItem);
|
|
4422
|
+
const items = hasTerm ? sections.flatMap(section => section.items) : recentItems;
|
|
4423
|
+
const navigation = useResultNavigation(items, hasTerm ? sections : RECENTS);
|
|
4424
|
+
const {
|
|
4425
|
+
highlightedIndex
|
|
4426
|
+
} = navigation;
|
|
4427
|
+
const inputRef = React.useRef(null);
|
|
4428
|
+
const [announceHighlight, setAnnounceHighlight] = React.useState(false);
|
|
4429
|
+
// The tabs keep focus while arrowing through results, but only the focused
|
|
4430
|
+
// input can point screen readers at the highlight, so from the tabs it's
|
|
4431
|
+
// announced instead. Typing goes back to the input, keystroke and all.
|
|
4432
|
+
const handleTabsKeyDown = event => {
|
|
4433
|
+
var _a;
|
|
4434
|
+
if (isTypingKey(event)) {
|
|
4435
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4436
|
+
} else if (verticalStep(event.key)) {
|
|
4437
|
+
setAnnounceHighlight(true);
|
|
4438
|
+
navigation.handleKeyDown(event);
|
|
4439
|
+
} else {
|
|
4440
|
+
navigation.handleKeyDown(event);
|
|
4441
|
+
}
|
|
4442
|
+
};
|
|
4443
|
+
React.useEffect(() => {
|
|
4444
|
+
if (hasTerm) saveLastProduct(product.slug);
|
|
4445
|
+
}, [hasTerm, product.slug]);
|
|
4446
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SearchInput, {
|
|
4447
|
+
expanded: items.length > 0,
|
|
4448
|
+
highlightedIndex: highlightedIndex,
|
|
4449
|
+
inputRef: inputRef,
|
|
4450
|
+
listboxId: listboxId,
|
|
4451
|
+
onChange: setTerm,
|
|
4452
|
+
onFocus: () => setAnnounceHighlight(false),
|
|
4453
|
+
onKeyDown: navigation.handleKeyDown,
|
|
4454
|
+
value: term
|
|
4455
|
+
}), /*#__PURE__*/React.createElement(ProductTabs, {
|
|
4456
|
+
onKeyDown: handleTabsKeyDown,
|
|
4457
|
+
onSelect: setSelectedSlug,
|
|
4458
|
+
products: products,
|
|
4459
|
+
selected: product
|
|
4460
|
+
}, hasTerm ? (/*#__PURE__*/React.createElement(SearchResults, {
|
|
4461
|
+
highlightedIndex: highlightedIndex,
|
|
4462
|
+
id: listboxId,
|
|
4463
|
+
label: `${product.appName} results`,
|
|
4464
|
+
onHighlight: navigation.highlightIndex,
|
|
4465
|
+
sections: sections,
|
|
4466
|
+
status: search.status,
|
|
4467
|
+
term: term
|
|
4468
|
+
})) : (/*#__PURE__*/React.createElement(RecentlyVisited, {
|
|
4469
|
+
highlightedIndex: highlightedIndex,
|
|
4470
|
+
id: listboxId,
|
|
4471
|
+
items: recentItems,
|
|
4472
|
+
onHighlight: navigation.highlightIndex,
|
|
4473
|
+
...recents
|
|
4474
|
+
}))), /*#__PURE__*/React.createElement("div", {
|
|
4475
|
+
"aria-live": "polite",
|
|
4476
|
+
className: styles$b.visuallyHidden
|
|
4477
|
+
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.title : null), /*#__PURE__*/React.createElement(KeyboardHints, {
|
|
4478
|
+
copied: navigation.copied
|
|
4479
|
+
}));
|
|
3974
4480
|
}
|
|
3975
|
-
|
|
3976
|
-
|
|
4481
|
+
|
|
4482
|
+
// Products open modals from many libraries, not all of which mark themselves
|
|
4483
|
+
// with roles, so a modal is recognized by what it does: cover the page.
|
|
4484
|
+
function isModalOpen() {
|
|
4485
|
+
const {
|
|
4486
|
+
clientHeight,
|
|
4487
|
+
clientWidth
|
|
4488
|
+
} = document.documentElement;
|
|
4489
|
+
const element = document.elementFromPoint(clientWidth / 2, clientHeight / 2);
|
|
4490
|
+
return element !== null && (isInNativeDialog(element) || hasBackdrop(element, clientWidth, clientHeight));
|
|
3977
4491
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
return `page:${entry.product}:${entry.path}`;
|
|
3983
|
-
}
|
|
4492
|
+
// A native modal's backdrop is drawn by the browser rather than being an
|
|
4493
|
+
// element; hit-testing it lands on the dialog itself.
|
|
4494
|
+
function isInNativeDialog(element) {
|
|
4495
|
+
return element.closest("dialog[open]") !== null;
|
|
3984
4496
|
}
|
|
3985
|
-
function
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3990
|
-
} else if (value.type === "page") {
|
|
3991
|
-
return isNonBlankString(value.product) && isNonBlankString(value.title) && isNonBlankString(value.path) && typeof value.details === "string";
|
|
3992
|
-
} else {
|
|
3993
|
-
return false;
|
|
4497
|
+
function hasBackdrop(element, width, height) {
|
|
4498
|
+
let current = element;
|
|
4499
|
+
while (current && !isBackdrop(current, width, height)) {
|
|
4500
|
+
current = current.parentElement;
|
|
3994
4501
|
}
|
|
4502
|
+
return current !== null;
|
|
3995
4503
|
}
|
|
3996
|
-
//
|
|
3997
|
-
|
|
3998
|
-
|
|
4504
|
+
// Some backdrops start below the topbar rather than at the top of the screen,
|
|
4505
|
+
// so the layer only has to span the width and reach from the center to the
|
|
4506
|
+
// bottom. Its own box has to reach the center: a bar along the bottom can
|
|
4507
|
+
// hold a panel that pops up over the center, like Services' media player.
|
|
4508
|
+
// react-aria leaves a closed modal's backdrop up while it fades out, search's
|
|
4509
|
+
// own included, so one that's on its way out doesn't count.
|
|
4510
|
+
function isBackdrop(element, width, height) {
|
|
4511
|
+
const rect = element.getBoundingClientRect();
|
|
4512
|
+
return !element.matches("[data-exiting]") && getComputedStyle(element).position === "fixed" && rect.width >= width - SCROLLBAR_SLACK && rect.top <= height / 2 && rect.bottom >= height - ROUNDING_SLACK;
|
|
3999
4513
|
}
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4514
|
+
// react-aria keeps a classic scrollbar's gutter reserved while its modals lock
|
|
4515
|
+
// scrolling, so their backdrops stop short of the right edge by its width.
|
|
4516
|
+
const SCROLLBAR_SLACK = 24;
|
|
4517
|
+
// Rects are fractional at non-100% zoom, but clientWidth and clientHeight are
|
|
4518
|
+
// rounded to whole pixels.
|
|
4519
|
+
const ROUNDING_SLACK = 1;
|
|
4520
|
+
|
|
4521
|
+
function useSearchShortcut(enabled, open, setOpen) {
|
|
4522
|
+
React.useEffect(() => {
|
|
4523
|
+
if (!enabled) return undefined;
|
|
4524
|
+
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
4525
|
+
// window and stopping propagation keeps them from ever seeing it. Under a
|
|
4526
|
+
// product's modal, search stays shut rather than stacking a second modal,
|
|
4527
|
+
// and the keystroke goes on to the product.
|
|
4528
|
+
const handleKeyDown = event => {
|
|
4529
|
+
if (open && isSearchShortcut(event)) {
|
|
4530
|
+
event.preventDefault();
|
|
4531
|
+
event.stopPropagation();
|
|
4532
|
+
setOpen(false);
|
|
4533
|
+
} else if (!open && (isSearchShortcut(event) || isLegacySearchShortcut(event)) && !isModalOpen()) {
|
|
4534
|
+
event.preventDefault();
|
|
4535
|
+
event.stopPropagation();
|
|
4536
|
+
setOpen(true);
|
|
4537
|
+
}
|
|
4008
4538
|
};
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
4017
|
-
} catch {
|
|
4018
|
-
parsed = null;
|
|
4019
|
-
}
|
|
4020
|
-
if (isRecord(parsed) && parsed.v === FORMAT_VERSION && parsed.person === personId && Array.isArray(parsed.entries)) {
|
|
4021
|
-
return parsed.entries.filter(isEntry);
|
|
4022
|
-
} else {
|
|
4023
|
-
return [];
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
function addVisit(entries, visit) {
|
|
4027
|
-
const key = entryKey(visit);
|
|
4028
|
-
return [clampEntry(visit), ...entries.filter(entry => entryKey(entry) !== key)].slice(0, MAX_ENTRIES);
|
|
4539
|
+
window.addEventListener("keydown", handleKeyDown, {
|
|
4540
|
+
capture: true
|
|
4541
|
+
});
|
|
4542
|
+
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
4543
|
+
capture: true
|
|
4544
|
+
});
|
|
4545
|
+
}, [enabled, open, setOpen]);
|
|
4029
4546
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4547
|
+
|
|
4548
|
+
const SEARCHABLE_PRODUCTS = [{
|
|
4549
|
+
appName: "Calendar",
|
|
4550
|
+
slug: "calendar"
|
|
4551
|
+
}, {
|
|
4552
|
+
appName: "Check-Ins",
|
|
4553
|
+
slug: "check-ins"
|
|
4554
|
+
}, {
|
|
4555
|
+
appName: "Giving",
|
|
4556
|
+
slug: "giving"
|
|
4557
|
+
}, {
|
|
4558
|
+
appName: "Groups",
|
|
4559
|
+
slug: "groups"
|
|
4560
|
+
}, {
|
|
4561
|
+
appName: "People",
|
|
4562
|
+
slug: "people"
|
|
4563
|
+
}, {
|
|
4564
|
+
appName: "Publishing",
|
|
4565
|
+
slug: "publishing"
|
|
4566
|
+
}, {
|
|
4567
|
+
appName: "Registrations",
|
|
4568
|
+
slug: "registrations"
|
|
4569
|
+
}, {
|
|
4570
|
+
appName: "Services",
|
|
4571
|
+
slug: "services"
|
|
4572
|
+
}];
|
|
4573
|
+
|
|
4574
|
+
const PERMISSION_CHECKS = {
|
|
4575
|
+
"check-ins": () => fetchMe("check-ins", "permission").then(({
|
|
4576
|
+
permission
|
|
4577
|
+
}) => permission !== null),
|
|
4578
|
+
giving: personId => {
|
|
4579
|
+
if (personId === undefined) {
|
|
4580
|
+
return Promise.reject(new Error("No person to check Giving access for"));
|
|
4581
|
+
} else {
|
|
4582
|
+
return fetchAttributes(`giving/v2/person_permissions/${personId}`, {
|
|
4583
|
+
"fields[PersonPermissions]": "can_search"
|
|
4584
|
+
}).then(({
|
|
4585
|
+
can_search
|
|
4586
|
+
}) => can_search);
|
|
4587
|
+
}
|
|
4588
|
+
},
|
|
4589
|
+
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
4590
|
+
people_permissions,
|
|
4591
|
+
site_administrator
|
|
4592
|
+
}) => site_administrator || people_permissions !== null),
|
|
4593
|
+
registrations: () => fetchMe("registrations", "can_search").then(({
|
|
4594
|
+
can_search
|
|
4595
|
+
}) => can_search),
|
|
4596
|
+
services: () => fetchMe("services", "can_search").then(({
|
|
4597
|
+
can_search
|
|
4598
|
+
}) => can_search)
|
|
4599
|
+
};
|
|
4600
|
+
function fetchMe(product, fields) {
|
|
4601
|
+
return fetchAttributes(`${product}/v2/me`, {
|
|
4602
|
+
"fields[Person]": fields
|
|
4035
4603
|
});
|
|
4036
4604
|
}
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4605
|
+
function fetchAttributes(path, params) {
|
|
4606
|
+
const url = `${window.location.origin}/~api/${path}?${new URLSearchParams(params)}`;
|
|
4607
|
+
return apiRequest(defaultFetch, url, {
|
|
4608
|
+
headers: apiVersionHeaders(url)
|
|
4609
|
+
}).then(({
|
|
4610
|
+
json,
|
|
4611
|
+
ok
|
|
4612
|
+
}) => {
|
|
4613
|
+
if (!ok || json === null) throw new Error("Permission check failed");
|
|
4614
|
+
return json.data.attributes;
|
|
4615
|
+
});
|
|
4044
4616
|
}
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4617
|
+
// Permissions rarely change mid-session, so a successful check is kept until
|
|
4618
|
+
// the page reloads. A failed one is dropped so a remount tries again.
|
|
4619
|
+
const permissionCache = new Map();
|
|
4620
|
+
function canSearch(slug, personId) {
|
|
4621
|
+
const check = PERMISSION_CHECKS[slug];
|
|
4622
|
+
const cached = permissionCache.get(slug);
|
|
4623
|
+
if (!check) {
|
|
4624
|
+
return Promise.resolve(true);
|
|
4625
|
+
} else if (cached) {
|
|
4626
|
+
return cached;
|
|
4627
|
+
} else {
|
|
4628
|
+
const promise = check(personId).catch(() => {
|
|
4629
|
+
permissionCache.delete(slug);
|
|
4630
|
+
return false;
|
|
4631
|
+
});
|
|
4632
|
+
permissionCache.set(slug, promise);
|
|
4633
|
+
return promise;
|
|
4052
4634
|
}
|
|
4053
4635
|
}
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
const
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4636
|
+
|
|
4637
|
+
function useSearchableProducts(apps) {
|
|
4638
|
+
// Keyed by name because providers hand over a fresh `apps` array on renders
|
|
4639
|
+
// where nothing changed.
|
|
4640
|
+
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
4641
|
+
slug
|
|
4642
|
+
}) => slug).join(",");
|
|
4643
|
+
const personId = useCurrentPersonId();
|
|
4644
|
+
const [settled, setSettled] = React.useState();
|
|
4645
|
+
React.useEffect(() => {
|
|
4646
|
+
let current = true;
|
|
4647
|
+
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
4648
|
+
Promise.all(accessible.map(({
|
|
4649
|
+
slug
|
|
4650
|
+
}) => canSearch(slug, personId))).then(allowed => {
|
|
4651
|
+
if (current) {
|
|
4652
|
+
setSettled({
|
|
4653
|
+
accessibleKey,
|
|
4654
|
+
personId,
|
|
4655
|
+
products: accessible.filter((_, index) => allowed[index])
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
});
|
|
4659
|
+
return () => {
|
|
4660
|
+
current = false;
|
|
4066
4661
|
};
|
|
4067
|
-
}
|
|
4662
|
+
}, [accessibleKey, personId]);
|
|
4663
|
+
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey && settled.personId === personId) {
|
|
4068
4664
|
return {
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
product,
|
|
4072
|
-
title,
|
|
4073
|
-
type: "page"
|
|
4665
|
+
products: settled.products,
|
|
4666
|
+
status: "done"
|
|
4074
4667
|
};
|
|
4075
4668
|
} else {
|
|
4076
|
-
return
|
|
4669
|
+
return {
|
|
4670
|
+
status: "loading"
|
|
4671
|
+
};
|
|
4077
4672
|
}
|
|
4078
4673
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
// panel always shows what actually gets stored.
|
|
4082
|
-
function useCurrentVisit() {
|
|
4083
|
-
const {
|
|
4084
|
-
recently_visitable: title,
|
|
4085
|
-
resource
|
|
4086
|
-
} = usePcoMeta();
|
|
4087
|
-
const metadata = useRecentlyVisitableMetadata();
|
|
4088
|
-
return React.useMemo(() => visitFromPage({
|
|
4089
|
-
metadata,
|
|
4090
|
-
resource,
|
|
4091
|
-
title
|
|
4092
|
-
}), [metadata, resource, title]);
|
|
4093
|
-
}
|
|
4094
|
-
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
4095
|
-
// and several pass it as a number.
|
|
4096
|
-
function useCurrentPersonId() {
|
|
4097
|
-
const {
|
|
4098
|
-
userId
|
|
4099
|
-
} = useStore();
|
|
4100
|
-
return userId == null ? undefined : String(userId);
|
|
4674
|
+
function inSwitcherOrder(apps) {
|
|
4675
|
+
return groupApps(apps).restProducts.flatMap(app => SEARCHABLE_PRODUCTS.filter(product => product.appName === app.name));
|
|
4101
4676
|
}
|
|
4102
4677
|
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
function storageKey({
|
|
4107
|
-
env,
|
|
4108
|
-
personId
|
|
4678
|
+
function ProductSearch({
|
|
4679
|
+
apps,
|
|
4680
|
+
env
|
|
4109
4681
|
}) {
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
try {
|
|
4127
|
-
localStorage.setItem(storageKey(scope), serializeRecentlyVisited(addVisit(readRecentlyVisited(scope), visit), scope.personId));
|
|
4128
|
-
changes.setState({
|
|
4129
|
-
version: changes.getState().version + 1
|
|
4130
|
-
});
|
|
4131
|
-
} catch {
|
|
4132
|
-
// Storage can be full or blocked; recents are disposable.
|
|
4133
|
-
}
|
|
4134
|
-
}
|
|
4682
|
+
const searchable = useSearchableProducts(apps);
|
|
4683
|
+
const [open, setOpen] = React.useState(false);
|
|
4684
|
+
const available = searchable.status === "done" && searchable.products.length > 0;
|
|
4685
|
+
useSearchShortcut(available, open, setOpen);
|
|
4686
|
+
if (!available) return null;
|
|
4687
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SearchButton, {
|
|
4688
|
+
onClick: () => setOpen(true)
|
|
4689
|
+
}), /*#__PURE__*/React.createElement(tapestry.Modal, {
|
|
4690
|
+
"aria-label": "Search",
|
|
4691
|
+
dismissible: true,
|
|
4692
|
+
onClose: () => setOpen(false),
|
|
4693
|
+
open: open
|
|
4694
|
+
}, /*#__PURE__*/React.createElement(SearchPanel, {
|
|
4695
|
+
env: env,
|
|
4696
|
+
products: searchable.products
|
|
4697
|
+
})));
|
|
4135
4698
|
}
|
|
4136
4699
|
|
|
4137
4700
|
function useRecordRecentlyVisited({
|
|
@@ -4176,9 +4739,9 @@ true;
|
|
|
4176
4739
|
`), children);
|
|
4177
4740
|
}
|
|
4178
4741
|
|
|
4179
|
-
var styles$
|
|
4742
|
+
var styles$a = {"route":"route--8BWe","is-active":"is-active-FsU9Y","apps-routes-separator":"apps-routes-separator-Nlm-N"};
|
|
4180
4743
|
|
|
4181
|
-
var styles$
|
|
4744
|
+
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"};
|
|
4182
4745
|
|
|
4183
4746
|
const UserMenu = ({
|
|
4184
4747
|
appName,
|
|
@@ -4197,7 +4760,7 @@ true;
|
|
|
4197
4760
|
component: Outsider,
|
|
4198
4761
|
onOutsideClick: () => setUserMenuVisible(false),
|
|
4199
4762
|
cleanup: !userMenuVisible,
|
|
4200
|
-
className: styles$
|
|
4763
|
+
className: styles$9.popup,
|
|
4201
4764
|
isVisible: userMenuVisible
|
|
4202
4765
|
}, /*#__PURE__*/React.createElement("div", null, hasConnectedPeople ? (/*#__PURE__*/React.createElement("div", {
|
|
4203
4766
|
style: {
|
|
@@ -4341,11 +4904,11 @@ true;
|
|
|
4341
4904
|
userMenuVisible
|
|
4342
4905
|
}) => {
|
|
4343
4906
|
return /*#__PURE__*/React.createElement("div", {
|
|
4344
|
-
className: styles$
|
|
4907
|
+
className: styles$9["trigger-wrap"],
|
|
4345
4908
|
"data-pendo": "topbar-org-switcher"
|
|
4346
4909
|
}, /*#__PURE__*/React.createElement(Hoverable, {
|
|
4347
|
-
className: classnames(styles$
|
|
4348
|
-
[styles$
|
|
4910
|
+
className: classnames(styles$9["trigger"], {
|
|
4911
|
+
[styles$9["is-visible"]]: userMenuVisible
|
|
4349
4912
|
}),
|
|
4350
4913
|
component: "button",
|
|
4351
4914
|
type: "button",
|
|
@@ -4357,7 +4920,7 @@ true;
|
|
|
4357
4920
|
requestConnectedPeopleFetch();
|
|
4358
4921
|
}
|
|
4359
4922
|
}, showOrgName && (/*#__PURE__*/React.createElement("span", {
|
|
4360
|
-
className: styles$
|
|
4923
|
+
className: styles$9["trigger-orgname"]
|
|
4361
4924
|
}, orgName)), /*#__PURE__*/React.createElement(DisclosureChevronIcon, null)));
|
|
4362
4925
|
};
|
|
4363
4926
|
|
|
@@ -4417,13 +4980,14 @@ true;
|
|
|
4417
4980
|
colors: props.colors,
|
|
4418
4981
|
requestAppsFetch: props.requestAppsFetch
|
|
4419
4982
|
})), /*#__PURE__*/React.createElement("div", {
|
|
4420
|
-
className: styles$
|
|
4983
|
+
className: styles$a["apps-routes-separator"]
|
|
4421
4984
|
}), routesVisible && props.routes, /*#__PURE__*/React.createElement("div", {
|
|
4422
4985
|
style: {
|
|
4423
4986
|
margin: "auto"
|
|
4424
4987
|
}
|
|
4425
4988
|
}), !notificationsEnabled && notifications(), productSearchEnabled ? (/*#__PURE__*/React.createElement(ProductSearch, {
|
|
4426
|
-
apps: props.apps
|
|
4989
|
+
apps: props.apps,
|
|
4990
|
+
env: props.env
|
|
4427
4991
|
})) : search({
|
|
4428
4992
|
hideRoutes: () => setRoutesVisible(false),
|
|
4429
4993
|
showRoutes: () => setRoutesVisible(true)
|
|
@@ -4457,12 +5021,12 @@ true;
|
|
|
4457
5021
|
...nativeProps
|
|
4458
5022
|
}) => {
|
|
4459
5023
|
return /*#__PURE__*/React.createElement("a", {
|
|
4460
|
-
className: classnames(styles$
|
|
5024
|
+
className: classnames(styles$a["route"], className, active && styles$a["is-active"]),
|
|
4461
5025
|
...nativeProps
|
|
4462
5026
|
});
|
|
4463
5027
|
};
|
|
4464
5028
|
|
|
4465
|
-
var styles$
|
|
5029
|
+
var styles$8 = {"avatar":"avatar-tW-wn"};
|
|
4466
5030
|
|
|
4467
5031
|
function Avatar({
|
|
4468
5032
|
env,
|
|
@@ -4472,7 +5036,7 @@ true;
|
|
|
4472
5036
|
}) {
|
|
4473
5037
|
const url = incomingUrl || `${pcoUrl$1(env)("people")}/static/no_photo_thumbnail_gray.svg`;
|
|
4474
5038
|
return /*#__PURE__*/React.createElement("div", {
|
|
4475
|
-
className: styles$
|
|
5039
|
+
className: styles$8.avatar,
|
|
4476
5040
|
style: {
|
|
4477
5041
|
backgroundImage: `url(${url})`,
|
|
4478
5042
|
...style
|
|
@@ -32231,7 +32795,7 @@ true;
|
|
|
32231
32795
|
|
|
32232
32796
|
var reactDomExports = requireReactDom();
|
|
32233
32797
|
|
|
32234
|
-
var styles$
|
|
32798
|
+
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"};
|
|
32235
32799
|
|
|
32236
32800
|
const AppsSwitcher = ({
|
|
32237
32801
|
appName,
|
|
@@ -32259,8 +32823,8 @@ true;
|
|
|
32259
32823
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32260
32824
|
"aria-current": name === appName ? "page" : undefined,
|
|
32261
32825
|
"aria-label": name,
|
|
32262
|
-
className: classnames(styles$
|
|
32263
|
-
[styles$
|
|
32826
|
+
className: classnames(styles$7.link, {
|
|
32827
|
+
[styles$7.current]: name === appName
|
|
32264
32828
|
}),
|
|
32265
32829
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32266
32830
|
"data-turbolinks": false,
|
|
@@ -32272,48 +32836,48 @@ true;
|
|
|
32272
32836
|
"aria-expanded": visible,
|
|
32273
32837
|
"aria-haspopup": "true",
|
|
32274
32838
|
"aria-label": `App switcher: ${appName}`,
|
|
32275
|
-
className: styles$
|
|
32839
|
+
className: styles$7.trigger,
|
|
32276
32840
|
onClick: onTriggerClick,
|
|
32277
32841
|
type: "button"
|
|
32278
32842
|
}, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
32279
32843
|
appName: appName.replace(/[\s-]/, "")
|
|
32280
32844
|
})), /*#__PURE__*/reactDomExports.createPortal(/*#__PURE__*/React.createElement("div", {
|
|
32281
32845
|
ref: containerRef,
|
|
32282
|
-
className: classnames(styles$
|
|
32283
|
-
[styles$
|
|
32846
|
+
className: classnames(styles$7.layer, {
|
|
32847
|
+
[styles$7["layer--visible"]]: visible
|
|
32284
32848
|
}),
|
|
32285
32849
|
onClick: toggle,
|
|
32286
32850
|
onKeyDown: handleKeyDown
|
|
32287
32851
|
}, /*#__PURE__*/React.createElement("nav", {
|
|
32288
32852
|
"aria-label": "Switch app",
|
|
32289
32853
|
"aria-modal": visible,
|
|
32290
|
-
className: styles$
|
|
32854
|
+
className: styles$7.panel,
|
|
32291
32855
|
"data-pendo": "app-switcher",
|
|
32292
32856
|
onClick: event => event.stopPropagation(),
|
|
32293
32857
|
role: "dialog"
|
|
32294
32858
|
}, /*#__PURE__*/React.createElement("div", {
|
|
32295
|
-
className: styles$
|
|
32859
|
+
className: styles$7.header
|
|
32296
32860
|
}, /*#__PURE__*/React.createElement("span", {
|
|
32297
|
-
className: styles$
|
|
32861
|
+
className: styles$7.heading
|
|
32298
32862
|
}, "Switch to"), /*#__PURE__*/React.createElement("button", {
|
|
32299
32863
|
"aria-label": "Close",
|
|
32300
|
-
className: styles$
|
|
32864
|
+
className: styles$7.close,
|
|
32301
32865
|
onClick: toggle,
|
|
32302
32866
|
type: "button"
|
|
32303
32867
|
}, /*#__PURE__*/React.createElement(X, {
|
|
32304
32868
|
"aria-hidden": "true",
|
|
32305
32869
|
fill: "var(--t-icon-color-dim)"
|
|
32306
32870
|
}))), /*#__PURE__*/React.createElement("ul", {
|
|
32307
|
-
className: styles$
|
|
32871
|
+
className: styles$7.list,
|
|
32308
32872
|
role: "list"
|
|
32309
32873
|
}, homeProduct && productLink(homeProduct.name), restProducts.map(({
|
|
32310
32874
|
name
|
|
32311
32875
|
}) => productLink(name)), accountsProduct && (/*#__PURE__*/React.createElement("li", {
|
|
32312
|
-
className: styles$
|
|
32876
|
+
className: styles$7["accounts-item"]
|
|
32313
32877
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32314
32878
|
"aria-current": appName === "Accounts" ? "page" : undefined,
|
|
32315
32879
|
"aria-label": "Account settings",
|
|
32316
|
-
className: classnames(styles$
|
|
32880
|
+
className: classnames(styles$7.link, styles$7["accounts-link"]),
|
|
32317
32881
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
32318
32882
|
"data-turbolinks": false,
|
|
32319
32883
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -32336,14 +32900,14 @@ true;
|
|
|
32336
32900
|
method: "localStorage"
|
|
32337
32901
|
};
|
|
32338
32902
|
|
|
32339
|
-
var styles$
|
|
32903
|
+
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"};
|
|
32340
32904
|
|
|
32341
32905
|
function AccountSettingsLink({
|
|
32342
32906
|
env
|
|
32343
32907
|
}) {
|
|
32344
32908
|
const accountsName = "accounts";
|
|
32345
32909
|
return /*#__PURE__*/React.createElement("a", {
|
|
32346
|
-
className: classnames(styles$
|
|
32910
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--bottom"]),
|
|
32347
32911
|
"data-pendo": `app-switcher--change-app--${accountsName}`,
|
|
32348
32912
|
"data-turbolinks": false,
|
|
32349
32913
|
href: pcoUrl$1(env)(accountsName) + getAppsSwitchProductPath(accountsName)
|
|
@@ -32366,7 +32930,7 @@ true;
|
|
|
32366
32930
|
}) => (/*#__PURE__*/React.createElement("button", {
|
|
32367
32931
|
"aria-label": "Close menu",
|
|
32368
32932
|
type: "button",
|
|
32369
|
-
className: styles$
|
|
32933
|
+
className: styles$6["appmenu-close-button"],
|
|
32370
32934
|
style: style
|
|
32371
32935
|
}, /*#__PURE__*/React.createElement(X, {
|
|
32372
32936
|
fill: color,
|
|
@@ -32427,7 +32991,7 @@ true;
|
|
|
32427
32991
|
"aria-haspopup": "true",
|
|
32428
32992
|
"aria-label": `App switcher: ${appName}`,
|
|
32429
32993
|
type: "button",
|
|
32430
|
-
className: styles$
|
|
32994
|
+
className: styles$6["appmenu-button"],
|
|
32431
32995
|
onClick: onTriggerClick
|
|
32432
32996
|
}, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
32433
32997
|
appName: appName.replace(/[\s-]/, "")
|
|
@@ -32435,7 +32999,7 @@ true;
|
|
|
32435
32999
|
item: "Topbar:Small:Menus:Products:visibility",
|
|
32436
33000
|
render: () => (/*#__PURE__*/React.createElement("div", {
|
|
32437
33001
|
ref: appMenuRef,
|
|
32438
|
-
className: styles$
|
|
33002
|
+
className: styles$6["appmenu-layer"],
|
|
32439
33003
|
style: {
|
|
32440
33004
|
...(visible && {
|
|
32441
33005
|
display: "block"
|
|
@@ -32452,37 +33016,37 @@ true;
|
|
|
32452
33016
|
e.stopPropagation();
|
|
32453
33017
|
}
|
|
32454
33018
|
},
|
|
32455
|
-
className: styles$
|
|
33019
|
+
className: styles$6["appmenu-container"]
|
|
32456
33020
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32457
33021
|
"aria-current": appName === (homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) ? "page" : undefined,
|
|
32458
33022
|
"aria-label": homeProduct ? homeProduct.name : appName,
|
|
32459
33023
|
"data-pendo": `app-switcher--change-app--${homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name}`,
|
|
32460
33024
|
"data-turbolinks": false,
|
|
32461
|
-
className: classnames(styles$
|
|
33025
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--top"]),
|
|
32462
33026
|
href: homeProduct ? pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) : undefined
|
|
32463
33027
|
}, /*#__PURE__*/React.createElement(UserMenuAppLockup, {
|
|
32464
33028
|
appName: homeProduct ? homeProduct.name : appName
|
|
32465
33029
|
})), /*#__PURE__*/React.createElement("div", {
|
|
32466
|
-
className: styles$
|
|
33030
|
+
className: styles$6["appmenu-scroll"]
|
|
32467
33031
|
}, /*#__PURE__*/React.createElement("ul", {
|
|
32468
33032
|
"data-pendo": "app-switcher",
|
|
32469
|
-
className: styles$
|
|
33033
|
+
className: styles$6["appmenu-list"]
|
|
32470
33034
|
}, appsMenuFormatter(restProducts).map(({
|
|
32471
33035
|
name
|
|
32472
33036
|
}) => (/*#__PURE__*/React.createElement("li", {
|
|
32473
33037
|
key: name,
|
|
32474
|
-
className: styles$
|
|
33038
|
+
className: styles$6["appmenu-list-li"]
|
|
32475
33039
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32476
33040
|
"aria-current": name === appName ? "page" : undefined,
|
|
32477
33041
|
"aria-label": name,
|
|
32478
33042
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32479
33043
|
"data-turbolinks": false,
|
|
32480
|
-
className: styles$
|
|
33044
|
+
className: styles$6["appmenu-list-a"],
|
|
32481
33045
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(name)
|
|
32482
33046
|
}, /*#__PURE__*/React.createElement(UserMenuAppLockup, {
|
|
32483
33047
|
appName: name
|
|
32484
33048
|
}))))), accountsProduct && (/*#__PURE__*/React.createElement("li", {
|
|
32485
|
-
className: styles$
|
|
33049
|
+
className: styles$6["appmenu-list-li"]
|
|
32486
33050
|
}, /*#__PURE__*/React.createElement(AccountSettingsLink, {
|
|
32487
33051
|
env: env
|
|
32488
33052
|
}))))))))
|
|
@@ -32546,19 +33110,19 @@ true;
|
|
|
32546
33110
|
toggle: toggleProductSwitcher,
|
|
32547
33111
|
visible: productSwitcherVisible
|
|
32548
33112
|
})), routesMenuVisible && (/*#__PURE__*/React.createElement("div", {
|
|
32549
|
-
className: styles$
|
|
33113
|
+
className: styles$6["routes"]
|
|
32550
33114
|
}, props.routes)), /*#__PURE__*/React.createElement("button", {
|
|
32551
33115
|
type: "button",
|
|
32552
|
-
className: classnames(styles$
|
|
32553
|
-
[styles$
|
|
33116
|
+
className: classnames(styles$6["current-route"], {
|
|
33117
|
+
[styles$6["is-active"]]: routesMenuVisible
|
|
32554
33118
|
}),
|
|
32555
33119
|
onClick: props.routes.length > 0 ? () => {
|
|
32556
33120
|
setProductSwitcherVisible(false);
|
|
32557
33121
|
setRoutesMenuVisible(visible => !visible);
|
|
32558
33122
|
} : null
|
|
32559
|
-
}, /*#__PURE__*/React.createElement(CurrentRouteComponent, null, capitalize(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React.createElement("button", {
|
|
33123
|
+
}, /*#__PURE__*/React.createElement(CurrentRouteComponent, null, capitalize$1(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React.createElement("button", {
|
|
32560
33124
|
type: "button",
|
|
32561
|
-
className: styles$
|
|
33125
|
+
className: styles$6["profile-button"],
|
|
32562
33126
|
onClick: () => {
|
|
32563
33127
|
var _a, _b;
|
|
32564
33128
|
props.requestConnectedPeopleFetch();
|
|
@@ -32574,7 +33138,7 @@ true;
|
|
|
32574
33138
|
className,
|
|
32575
33139
|
...props
|
|
32576
33140
|
}) => (/*#__PURE__*/React.createElement("a", {
|
|
32577
|
-
className: classnames(styles$
|
|
33141
|
+
className: classnames(styles$6["route"], className, active && styles$6["is-active"]),
|
|
32578
33142
|
...props
|
|
32579
33143
|
}));
|
|
32580
33144
|
|
|
@@ -32713,7 +33277,7 @@ true;
|
|
|
32713
33277
|
id: "topbar"
|
|
32714
33278
|
};
|
|
32715
33279
|
|
|
32716
|
-
var styles$
|
|
33280
|
+
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"};
|
|
32717
33281
|
|
|
32718
33282
|
function isPresent(value) {
|
|
32719
33283
|
return value !== undefined && value.trim() !== "";
|
|
@@ -32742,15 +33306,31 @@ true;
|
|
|
32742
33306
|
value
|
|
32743
33307
|
}) {
|
|
32744
33308
|
return /*#__PURE__*/React.createElement("div", {
|
|
32745
|
-
className: styles$
|
|
33309
|
+
className: styles$5["debug-pco-resource-field"]
|
|
32746
33310
|
}, /*#__PURE__*/React.createElement("span", {
|
|
32747
|
-
className: styles$
|
|
33311
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
32748
33312
|
}, label), /*#__PURE__*/React.createElement("span", {
|
|
32749
|
-
className: styles$
|
|
33313
|
+
className: styles$5["debug-pco-resource-field-value"]
|
|
32750
33314
|
}, value));
|
|
32751
33315
|
}
|
|
33316
|
+
// Only staff ghosting or impersonating a person get pco:staff-access-level,
|
|
33317
|
+
// which keeps the panel away from regular users on staging.
|
|
32752
33318
|
function DebugPcoResource({
|
|
32753
33319
|
env
|
|
33320
|
+
}) {
|
|
33321
|
+
const {
|
|
33322
|
+
"staff-access-level": staffAccessLevel
|
|
33323
|
+
} = usePcoMeta();
|
|
33324
|
+
if (isPresent(staffAccessLevel)) {
|
|
33325
|
+
return /*#__PURE__*/React.createElement(DebugPanel, {
|
|
33326
|
+
env: env
|
|
33327
|
+
});
|
|
33328
|
+
} else {
|
|
33329
|
+
return null;
|
|
33330
|
+
}
|
|
33331
|
+
}
|
|
33332
|
+
function DebugPanel({
|
|
33333
|
+
env
|
|
32754
33334
|
}) {
|
|
32755
33335
|
const {
|
|
32756
33336
|
recently_visitable: recentlyVisitable,
|
|
@@ -32759,12 +33339,17 @@ true;
|
|
|
32759
33339
|
const [lookup, setLookup] = React.useState({
|
|
32760
33340
|
status: "idle"
|
|
32761
33341
|
});
|
|
32762
|
-
const
|
|
33342
|
+
const visit = useCurrentVisit();
|
|
33343
|
+
const personId = useCurrentPersonId();
|
|
33344
|
+
const recentlyVisited = useRecentlyVisited({
|
|
33345
|
+
env,
|
|
33346
|
+
personId
|
|
33347
|
+
});
|
|
33348
|
+
const pcrl = (visit === null || visit === void 0 ? void 0 : visit.type) === "resource" ? visit.pcrl : null;
|
|
32763
33349
|
const hasRecentlyVisitable = isPresent(recentlyVisitable);
|
|
33350
|
+
const hasInvalidResource = isPresent(resource) && !isPcrl(resource);
|
|
32764
33351
|
React.useEffect(() => {
|
|
32765
|
-
|
|
32766
|
-
// tags are present.
|
|
32767
|
-
const url = hasResource ? pcrlLookupUrl(resource, env) : null;
|
|
33352
|
+
const url = pcrl ? pcrlLookupUrl(pcrl, env) : null;
|
|
32768
33353
|
if (!url) {
|
|
32769
33354
|
setLookup({
|
|
32770
33355
|
status: "idle"
|
|
@@ -32796,37 +33381,45 @@ true;
|
|
|
32796
33381
|
return () => {
|
|
32797
33382
|
cancelled = true;
|
|
32798
33383
|
};
|
|
32799
|
-
}, [
|
|
33384
|
+
}, [pcrl, env]);
|
|
32800
33385
|
return /*#__PURE__*/React.createElement("div", {
|
|
32801
|
-
className: classnames("staff-only", styles$
|
|
33386
|
+
className: classnames("staff-only", styles$5["debug-pco-resource"])
|
|
32802
33387
|
}, /*#__PURE__*/React.createElement("div", {
|
|
32803
|
-
className: styles$
|
|
33388
|
+
className: styles$5["debug-pco-resource-header"]
|
|
32804
33389
|
}, /*#__PURE__*/React.createElement("span", {
|
|
32805
|
-
className: styles$
|
|
33390
|
+
className: styles$5["debug-pco-resource-badge"]
|
|
32806
33391
|
}, "Staff"), /*#__PURE__*/React.createElement("span", {
|
|
32807
|
-
className: styles$
|
|
33392
|
+
className: styles$5["debug-pco-resource-title"]
|
|
32808
33393
|
}, "Recently Visited debug")), /*#__PURE__*/React.createElement(Field, {
|
|
32809
33394
|
label: "pco:resource",
|
|
32810
33395
|
value: describeTag("resource", resource)
|
|
32811
33396
|
}), /*#__PURE__*/React.createElement(Field, {
|
|
32812
33397
|
label: "pco:recently_visitable",
|
|
32813
33398
|
value: describeTag("recently_visitable", recentlyVisitable)
|
|
32814
|
-
}),
|
|
32815
|
-
className: styles$
|
|
32816
|
-
}, "
|
|
32817
|
-
className: styles$
|
|
32818
|
-
}, "
|
|
32819
|
-
className: styles$
|
|
32820
|
-
}, "
|
|
32821
|
-
className: styles$
|
|
32822
|
-
},
|
|
32823
|
-
className: styles$
|
|
32824
|
-
},
|
|
32825
|
-
className: styles$
|
|
32826
|
-
},
|
|
32827
|
-
|
|
32828
|
-
|
|
32829
|
-
|
|
33399
|
+
}), hasInvalidResource && (/*#__PURE__*/React.createElement("p", {
|
|
33400
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33401
|
+
}, "pco:resource isn't a PCRL (product/v2/type/id), so Recently Visited ignores it.")), visit === null && (/*#__PURE__*/React.createElement("p", {
|
|
33402
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33403
|
+
}, "This page is not eligible for Recently Visited")), pcrl && hasRecentlyVisitable && (/*#__PURE__*/React.createElement("p", {
|
|
33404
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33405
|
+
}, "Both tags are present \u2014 pco:resource takes precedence for the Recently Visited entry.")), pcrl && lookup.status === "loading" && (/*#__PURE__*/React.createElement("p", {
|
|
33406
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33407
|
+
}, "Looking up resource\u2026")), pcrl && lookup.status === "error" && (/*#__PURE__*/React.createElement("p", {
|
|
33408
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33409
|
+
}, lookup.message)), pcrl && lookup.status === "loaded" && (/*#__PURE__*/React.createElement("pre", {
|
|
33410
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33411
|
+
}, JSON.stringify(lookup.json, null, 2))), (visit === null || visit === void 0 ? void 0 : visit.type) === "page" && (/*#__PURE__*/React.createElement("p", {
|
|
33412
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33413
|
+
}, "Recently Visited title: ", visit.title)), /*#__PURE__*/React.createElement("span", {
|
|
33414
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
33415
|
+
}, "Recently Visited"), personId ? (/*#__PURE__*/React.createElement("pre", {
|
|
33416
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33417
|
+
}, JSON.stringify(recentlyVisited, null, 2))) : (/*#__PURE__*/React.createElement("p", {
|
|
33418
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33419
|
+
}, "No current person, so nothing is recorded.")));
|
|
33420
|
+
}
|
|
33421
|
+
|
|
33422
|
+
var styles$4 = {"drawer-wrap":"drawer-wrap-J0rSL","is-visible":"is-visible-KTME8","drawer":"drawer-j7f4l"};
|
|
32830
33423
|
|
|
32831
33424
|
function Drawer({
|
|
32832
33425
|
children,
|
|
@@ -32834,7 +33427,7 @@ true;
|
|
|
32834
33427
|
}) {
|
|
32835
33428
|
return /*#__PURE__*/React.createElement("div", {
|
|
32836
33429
|
id: id,
|
|
32837
|
-
className: styles$
|
|
33430
|
+
className: styles$4["drawer"]
|
|
32838
33431
|
}, children);
|
|
32839
33432
|
}
|
|
32840
33433
|
function DrawerWrap({
|
|
@@ -32844,7 +33437,7 @@ true;
|
|
|
32844
33437
|
...props
|
|
32845
33438
|
}) {
|
|
32846
33439
|
return /*#__PURE__*/React.createElement("div", {
|
|
32847
|
-
className: classnames(styles$
|
|
33440
|
+
className: classnames(styles$4["drawer-wrap"], className, isVisible && styles$4["is-visible"]),
|
|
32848
33441
|
...props
|
|
32849
33442
|
}, children);
|
|
32850
33443
|
}
|
|
@@ -33028,15 +33621,50 @@ true;
|
|
|
33028
33621
|
}));
|
|
33029
33622
|
};
|
|
33030
33623
|
|
|
33031
|
-
|
|
33624
|
+
const collections = {
|
|
33625
|
+
general: general__namespace,
|
|
33626
|
+
toolbar: toolbar__namespace
|
|
33627
|
+
};
|
|
33628
|
+
const paths = collections;
|
|
33629
|
+
function Icon({
|
|
33630
|
+
name,
|
|
33631
|
+
size = 16,
|
|
33632
|
+
style,
|
|
33633
|
+
theme,
|
|
33634
|
+
title,
|
|
33635
|
+
...props
|
|
33636
|
+
}) {
|
|
33637
|
+
const [collection, symbol] = name.split(".");
|
|
33638
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
33639
|
+
"aria-hidden": title ? undefined : true,
|
|
33640
|
+
fill: "currentColor",
|
|
33641
|
+
height: size,
|
|
33642
|
+
role: title ? "img" : undefined,
|
|
33643
|
+
style: {
|
|
33644
|
+
color: theme && `var(${theme})`,
|
|
33645
|
+
flexShrink: 0,
|
|
33646
|
+
...style
|
|
33647
|
+
},
|
|
33648
|
+
viewBox: "0 0 16 16",
|
|
33649
|
+
width: size,
|
|
33650
|
+
...props
|
|
33651
|
+
}, title && /*#__PURE__*/React.createElement("title", null, title), /*#__PURE__*/React.createElement("path", {
|
|
33652
|
+
d: paths[collection][symbol]
|
|
33653
|
+
}));
|
|
33654
|
+
}
|
|
33655
|
+
|
|
33656
|
+
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"};
|
|
33032
33657
|
|
|
33033
33658
|
const COLOR_MODES = [{
|
|
33659
|
+
icon: "toolbar.sun",
|
|
33034
33660
|
label: "Light",
|
|
33035
33661
|
value: "light"
|
|
33036
33662
|
}, {
|
|
33663
|
+
icon: "toolbar.moon",
|
|
33037
33664
|
label: "Dark",
|
|
33038
33665
|
value: "dark"
|
|
33039
33666
|
}, {
|
|
33667
|
+
icon: "toolbar.display",
|
|
33040
33668
|
label: "System",
|
|
33041
33669
|
value: "system"
|
|
33042
33670
|
}];
|
|
@@ -33048,14 +33676,15 @@ true;
|
|
|
33048
33676
|
const updateStore = useUpdateStore();
|
|
33049
33677
|
const [counter] = React.useState(() => COLOR_MODE_INSTANCE_COUNTER++);
|
|
33050
33678
|
return /*#__PURE__*/React.createElement("fieldset", {
|
|
33051
|
-
className: styles$
|
|
33679
|
+
className: styles$3["color-mode-switcher"]
|
|
33052
33680
|
}, /*#__PURE__*/React.createElement("legend", {
|
|
33053
|
-
className: styles$
|
|
33681
|
+
className: styles$3["color-mode-switcher-label"]
|
|
33054
33682
|
}, "Appearance", " "), /*#__PURE__*/React.createElement("div", {
|
|
33055
|
-
className: styles$
|
|
33683
|
+
className: styles$3["color-mode-switcher-modes"]
|
|
33056
33684
|
}, COLOR_MODES.map(({
|
|
33057
|
-
|
|
33058
|
-
label
|
|
33685
|
+
icon,
|
|
33686
|
+
label,
|
|
33687
|
+
value
|
|
33059
33688
|
}) => (/*#__PURE__*/React.createElement("label", {
|
|
33060
33689
|
key: value
|
|
33061
33690
|
}, /*#__PURE__*/React.createElement("input", {
|
|
@@ -33072,8 +33701,9 @@ true;
|
|
|
33072
33701
|
document.documentElement.setAttribute(RESOLVED_COLOR_MODE_ATTRIBUTE, resolveColorMode(colorMode));
|
|
33073
33702
|
updateColorMode(colorMode);
|
|
33074
33703
|
}
|
|
33075
|
-
}), /*#__PURE__*/React.createElement(
|
|
33076
|
-
|
|
33704
|
+
}), /*#__PURE__*/React.createElement(Icon, {
|
|
33705
|
+
name: icon,
|
|
33706
|
+
size: 15
|
|
33077
33707
|
}), label)))));
|
|
33078
33708
|
}
|
|
33079
33709
|
function updateColorMode(mode) {
|
|
@@ -33088,28 +33718,6 @@ true;
|
|
|
33088
33718
|
method: "POST"
|
|
33089
33719
|
});
|
|
33090
33720
|
}
|
|
33091
|
-
const COLOR_MODE_ICON_PATHS = {
|
|
33092
|
-
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",
|
|
33093
|
-
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",
|
|
33094
|
-
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"
|
|
33095
|
-
};
|
|
33096
|
-
function ColorModeIcon({
|
|
33097
|
-
mode
|
|
33098
|
-
}) {
|
|
33099
|
-
return /*#__PURE__*/React.createElement("svg", {
|
|
33100
|
-
width: "16",
|
|
33101
|
-
height: "16",
|
|
33102
|
-
viewBox: "0 0 14 14",
|
|
33103
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
33104
|
-
"aria-hidden": "true"
|
|
33105
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
33106
|
-
d: COLOR_MODE_ICON_PATHS[mode],
|
|
33107
|
-
strokeWidth: "1.5",
|
|
33108
|
-
fill: "none",
|
|
33109
|
-
strokeLinecap: "round",
|
|
33110
|
-
strokeLinejoin: "round"
|
|
33111
|
-
}));
|
|
33112
|
-
}
|
|
33113
33721
|
|
|
33114
33722
|
const requestClearConnectedPeopleCache = () => remove();
|
|
33115
33723
|
function UserProfile({
|
|
@@ -33172,10 +33780,10 @@ true;
|
|
|
33172
33780
|
alignment: "center",
|
|
33173
33781
|
spacing: 0.5,
|
|
33174
33782
|
width: "100%"
|
|
33175
|
-
}, /*#__PURE__*/React.createElement(
|
|
33783
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
33176
33784
|
name: mfaConfigured ? "general.lock" : "general.unlock",
|
|
33177
|
-
size:
|
|
33178
|
-
|
|
33785
|
+
size: 12,
|
|
33786
|
+
theme: mfaConfigured ? "--t-icon-color-status-success" : "--t-icon-color-status-error"
|
|
33179
33787
|
}), /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
33180
33788
|
size: 4,
|
|
33181
33789
|
weight: 400,
|
|
@@ -33223,9 +33831,10 @@ true;
|
|
|
33223
33831
|
}, /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
33224
33832
|
color: "var(--text-color-default-secondary)",
|
|
33225
33833
|
size: 3
|
|
33226
|
-
}, "Log out"), /*#__PURE__*/React.createElement(
|
|
33834
|
+
}, "Log out"), /*#__PURE__*/React.createElement(Icon, {
|
|
33227
33835
|
name: "general.intoBox",
|
|
33228
|
-
|
|
33836
|
+
size: 14,
|
|
33837
|
+
theme: "--t-icon-color-secondary"
|
|
33229
33838
|
})))), env !== "production" && /*#__PURE__*/React.createElement(DebugPcoResource, {
|
|
33230
33839
|
env: env
|
|
33231
33840
|
}), hasConnectedPeople && (/*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
@@ -33287,26 +33896,42 @@ true;
|
|
|
33287
33896
|
|
|
33288
33897
|
var ActionType;
|
|
33289
33898
|
(function (ActionType) {
|
|
33899
|
+
ActionType["clearPending"] = "clearPending";
|
|
33290
33900
|
ActionType["close"] = "close";
|
|
33291
33901
|
ActionType["mount"] = "mount";
|
|
33292
33902
|
ActionType["open"] = "open";
|
|
33903
|
+
ActionType["setPending"] = "setPending";
|
|
33293
33904
|
ActionType["unmount"] = "unmount";
|
|
33294
33905
|
})(ActionType || (ActionType = {}));
|
|
33295
33906
|
const initialState = {
|
|
33296
33907
|
currentContent: null,
|
|
33297
|
-
open: false
|
|
33908
|
+
open: false,
|
|
33909
|
+
pending: null
|
|
33298
33910
|
};
|
|
33299
33911
|
const reducer = (state, action) => {
|
|
33912
|
+
var _a, _b;
|
|
33300
33913
|
switch (action.type) {
|
|
33301
33914
|
case ActionType.mount:
|
|
33302
33915
|
return {
|
|
33303
33916
|
...state,
|
|
33304
|
-
currentContent: action.payload.content
|
|
33917
|
+
currentContent: action.payload.content,
|
|
33918
|
+
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
|
|
33305
33919
|
};
|
|
33306
33920
|
case ActionType.unmount:
|
|
33307
33921
|
return {
|
|
33308
33922
|
...state,
|
|
33309
|
-
currentContent: null
|
|
33923
|
+
currentContent: null,
|
|
33924
|
+
pending: null
|
|
33925
|
+
};
|
|
33926
|
+
case ActionType.setPending:
|
|
33927
|
+
return {
|
|
33928
|
+
...state,
|
|
33929
|
+
pending: action.payload
|
|
33930
|
+
};
|
|
33931
|
+
case ActionType.clearPending:
|
|
33932
|
+
return {
|
|
33933
|
+
...state,
|
|
33934
|
+
pending: null
|
|
33310
33935
|
};
|
|
33311
33936
|
case ActionType.open:
|
|
33312
33937
|
return {
|
|
@@ -33325,13 +33950,181 @@ true;
|
|
|
33325
33950
|
...overrides
|
|
33326
33951
|
});
|
|
33327
33952
|
|
|
33328
|
-
var styles$
|
|
33953
|
+
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"};
|
|
33329
33954
|
|
|
33330
|
-
|
|
33955
|
+
// PCO Box serves TLS by default, and a bare http:// frame inside an https
|
|
33956
|
+
// product page is blocked as mixed content, so follow the page's scheme.
|
|
33957
|
+
const iframeSrc = () => `${window.location.protocol}//pco.test/iframe`;
|
|
33958
|
+
const STAFF_PINK = "var(--t-fill-color-product-staff)";
|
|
33959
|
+
// The pco.test domain is only ever served by a local PCO Box, so the tool
|
|
33960
|
+
// stays hidden in staging and production — and on Topbar's own dev server,
|
|
33961
|
+
// which runs on localhost and has no pco.test to frame.
|
|
33962
|
+
function isPcoTestHost() {
|
|
33963
|
+
const {
|
|
33964
|
+
hostname
|
|
33965
|
+
} = window.location;
|
|
33966
|
+
return hostname === "pco.test" || hostname.endsWith(".pco.test");
|
|
33967
|
+
}
|
|
33968
|
+
// Flattened from the source artwork's two outline paths — its gradients,
|
|
33969
|
+
// filters and clip paths carry nothing at icon size. Those paths are authored
|
|
33970
|
+
// on a 128px canvas, hence the transform: it fits the pair into the same ~24px
|
|
33971
|
+
// optical box the sibling toolbar icons occupy. Filled explicitly rather than
|
|
33972
|
+
// with `currentColor` so the mark stays staff pink instead of inheriting the
|
|
33973
|
+
// toolbar's text color and its hover/active states.
|
|
33974
|
+
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";
|
|
33975
|
+
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";
|
|
33976
|
+
function ToolsIcon() {
|
|
33977
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
33978
|
+
"aria-hidden": "true",
|
|
33979
|
+
width: "32",
|
|
33980
|
+
height: "32",
|
|
33981
|
+
viewBox: "0 0 32 32",
|
|
33982
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
33983
|
+
}, /*#__PURE__*/React.createElement("g", {
|
|
33984
|
+
fill: STAFF_PINK,
|
|
33985
|
+
transform: "translate(2.92 2.66) scale(.204)"
|
|
33986
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
33987
|
+
d: WRENCH
|
|
33988
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
33989
|
+
d: SCREWDRIVER
|
|
33990
|
+
})));
|
|
33991
|
+
}
|
|
33992
|
+
// A cross-origin frame's own history object is unreachable (touching it throws
|
|
33993
|
+
// a SecurityError), but the frame's navigations do land in the top-level joint
|
|
33994
|
+
// session history, so the *parent's* back/forward traverse them while leaving
|
|
33995
|
+
// the host page in place. Counting frame loads tracks how deep we are, which is
|
|
33996
|
+
// what keeps a click on Back from stepping past the frame's first page and
|
|
33997
|
+
// navigating the host app instead. Loads we caused are attributed to the
|
|
33998
|
+
// traversal that requested them rather than counted as new navigations.
|
|
33999
|
+
function useFrameHistory() {
|
|
34000
|
+
const [steps, setSteps] = React.useState({
|
|
34001
|
+
back: 0,
|
|
34002
|
+
forward: 0
|
|
34003
|
+
});
|
|
34004
|
+
// Traversal has to read the current counts outside of render, so the ref is
|
|
34005
|
+
// the source of truth and state exists to paint the buttons.
|
|
34006
|
+
const stepsRef = React.useRef(steps);
|
|
34007
|
+
const pendingTraversal = React.useRef(null);
|
|
34008
|
+
const frameIsLoaded = React.useRef(false);
|
|
34009
|
+
const moveTo = React.useCallback(next => {
|
|
34010
|
+
stepsRef.current = next;
|
|
34011
|
+
setSteps(next);
|
|
34012
|
+
}, []);
|
|
34013
|
+
const onFrameLoad = React.useCallback(() => {
|
|
34014
|
+
if (!frameIsLoaded.current) {
|
|
34015
|
+
// The frame's first page is the floor, not a step.
|
|
34016
|
+
frameIsLoaded.current = true;
|
|
34017
|
+
} else {
|
|
34018
|
+
const traversal = pendingTraversal.current;
|
|
34019
|
+
pendingTraversal.current = null;
|
|
34020
|
+
const {
|
|
34021
|
+
back,
|
|
34022
|
+
forward
|
|
34023
|
+
} = stepsRef.current;
|
|
34024
|
+
if (traversal === "back") {
|
|
34025
|
+
moveTo({
|
|
34026
|
+
back: back - 1,
|
|
34027
|
+
forward: forward + 1
|
|
34028
|
+
});
|
|
34029
|
+
} else if (traversal === "forward") {
|
|
34030
|
+
moveTo({
|
|
34031
|
+
back: back + 1,
|
|
34032
|
+
forward: forward - 1
|
|
34033
|
+
});
|
|
34034
|
+
} else {
|
|
34035
|
+
moveTo({
|
|
34036
|
+
back: back + 1,
|
|
34037
|
+
forward: 0
|
|
34038
|
+
});
|
|
34039
|
+
}
|
|
34040
|
+
}
|
|
34041
|
+
}, [moveTo]);
|
|
34042
|
+
// Guarded here rather than trusting the button's disabled state: traversing
|
|
34043
|
+
// with no frame step left would navigate the host app out from under us.
|
|
34044
|
+
const traverse = React.useCallback(direction => {
|
|
34045
|
+
if (stepsRef.current[direction] > 0) {
|
|
34046
|
+
pendingTraversal.current = direction;
|
|
34047
|
+
window.history[direction]();
|
|
34048
|
+
}
|
|
34049
|
+
}, []);
|
|
34050
|
+
return {
|
|
34051
|
+
onFrameLoad,
|
|
34052
|
+
steps,
|
|
34053
|
+
traverse
|
|
34054
|
+
};
|
|
34055
|
+
}
|
|
34056
|
+
function NavButton({
|
|
34057
|
+
direction,
|
|
34058
|
+
disabled,
|
|
34059
|
+
onClick
|
|
34060
|
+
}) {
|
|
34061
|
+
return /*#__PURE__*/React.createElement(tapestryReact.Button
|
|
34062
|
+
// Tapestry's `disabled` suppresses the click but emits no ARIA state.
|
|
34063
|
+
, {
|
|
34064
|
+
"aria-disabled": disabled,
|
|
34065
|
+
disabled: disabled,
|
|
34066
|
+
hover: {
|
|
34067
|
+
backgroundColor: "var(--t-fill-color-neutral-070)"
|
|
34068
|
+
},
|
|
34069
|
+
icon: {
|
|
34070
|
+
fill: "var(--t-icon-color-dim)",
|
|
34071
|
+
name: direction === "back" ? "general.leftChevron" : "general.rightChevron",
|
|
34072
|
+
size: "md"
|
|
34073
|
+
},
|
|
34074
|
+
onClick: onClick,
|
|
34075
|
+
opacity: disabled ? 0.35 : 1,
|
|
34076
|
+
theme: false,
|
|
34077
|
+
title: direction === "back" ? "Back" : "Forward"
|
|
34078
|
+
});
|
|
34079
|
+
}
|
|
34080
|
+
function DeveloperToolsComponent({
|
|
34081
|
+
closeDrawer
|
|
34082
|
+
}) {
|
|
34083
|
+
const {
|
|
34084
|
+
onFrameLoad,
|
|
34085
|
+
steps,
|
|
34086
|
+
traverse
|
|
34087
|
+
} = useFrameHistory();
|
|
34088
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
34089
|
+
closeDrawer: closeDrawer
|
|
34090
|
+
}, /*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
34091
|
+
alignment: "center",
|
|
34092
|
+
axis: "horizontal",
|
|
34093
|
+
spacing: 0.25
|
|
34094
|
+
}, /*#__PURE__*/React.createElement(NavButton, {
|
|
34095
|
+
direction: "back",
|
|
34096
|
+
disabled: steps.back === 0,
|
|
34097
|
+
onClick: () => traverse("back")
|
|
34098
|
+
}), /*#__PURE__*/React.createElement(NavButton, {
|
|
34099
|
+
direction: "forward",
|
|
34100
|
+
disabled: steps.forward === 0,
|
|
34101
|
+
onClick: () => traverse("forward")
|
|
34102
|
+
}), /*#__PURE__*/React.createElement(DrawerHeading, {
|
|
34103
|
+
paddingLeft: 0.5
|
|
34104
|
+
}, "Developer Tools"))), /*#__PURE__*/React.createElement(tapestryReact.Box, {
|
|
34105
|
+
as: "iframe",
|
|
34106
|
+
border: "none",
|
|
34107
|
+
flex: "1",
|
|
34108
|
+
minHeight: 0,
|
|
34109
|
+
onLoad: onFrameLoad,
|
|
34110
|
+
src: iframeSrc(),
|
|
34111
|
+
title: "Developer Tools",
|
|
34112
|
+
width: "100%"
|
|
34113
|
+
}));
|
|
34114
|
+
}
|
|
34115
|
+
const DeveloperTools = {
|
|
34116
|
+
component: DeveloperToolsComponent,
|
|
34117
|
+
icons: {
|
|
34118
|
+
component: ToolsIcon
|
|
34119
|
+
},
|
|
34120
|
+
label: "Developer Tools",
|
|
34121
|
+
// No space: `name` becomes an element id and a pendo attribute.
|
|
34122
|
+
name: "DeveloperTools"
|
|
34123
|
+
};
|
|
33331
34124
|
|
|
33332
|
-
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"};
|
|
34125
|
+
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"};
|
|
33333
34126
|
|
|
33334
|
-
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";
|
|
34127
|
+
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";
|
|
33335
34128
|
// Same stops as the AskAIBanner border gradient, so the two stay in sync.
|
|
33336
34129
|
const GRADIENT_STOPS = [{
|
|
33337
34130
|
color: "var(--t-fill-color-tag-teal-040)",
|
|
@@ -33380,7 +34173,7 @@ true;
|
|
|
33380
34173
|
stopColor: color
|
|
33381
34174
|
}
|
|
33382
34175
|
})))))), /*#__PURE__*/React.createElement("path", {
|
|
33383
|
-
d: SPARKLE_PATH,
|
|
34176
|
+
d: SPARKLE_PATH$1,
|
|
33384
34177
|
fill: isGradient ? `url(#${gradientId})` : "currentColor"
|
|
33385
34178
|
}));
|
|
33386
34179
|
}
|
|
@@ -33389,8 +34182,8 @@ true;
|
|
|
33389
34182
|
var _a, _b;
|
|
33390
34183
|
const isStacked = props.variant !== "inline";
|
|
33391
34184
|
return /*#__PURE__*/React.createElement("button", {
|
|
33392
|
-
className: classnames(styles.banner, {
|
|
33393
|
-
[styles.stacked]: isStacked
|
|
34185
|
+
className: classnames(styles$1.banner, {
|
|
34186
|
+
[styles$1.stacked]: isStacked
|
|
33394
34187
|
}),
|
|
33395
34188
|
onClick: () => props.onClick(),
|
|
33396
34189
|
type: "button"
|
|
@@ -33405,15 +34198,15 @@ true;
|
|
|
33405
34198
|
title
|
|
33406
34199
|
}) {
|
|
33407
34200
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
33408
|
-
className: styles.content
|
|
34201
|
+
className: styles$1.content
|
|
33409
34202
|
}, /*#__PURE__*/React.createElement(AskAIIcon, null), /*#__PURE__*/React.createElement("span", {
|
|
33410
|
-
className: styles.text
|
|
34203
|
+
className: styles$1.text
|
|
33411
34204
|
}, /*#__PURE__*/React.createElement("span", {
|
|
33412
|
-
className: styles.title
|
|
34205
|
+
className: styles$1.title
|
|
33413
34206
|
}, title), /*#__PURE__*/React.createElement("span", {
|
|
33414
|
-
className: styles.subtitle
|
|
34207
|
+
className: styles$1.subtitle
|
|
33415
34208
|
}, "Support answers tailored to you"))), /*#__PURE__*/React.createElement(tapestryReact.Icon, {
|
|
33416
|
-
className: styles.chevron,
|
|
34209
|
+
className: styles$1.chevron,
|
|
33417
34210
|
name: "general.rightChevron",
|
|
33418
34211
|
size: "16px"
|
|
33419
34212
|
}));
|
|
@@ -33423,18 +34216,18 @@ true;
|
|
|
33423
34216
|
title
|
|
33424
34217
|
}) {
|
|
33425
34218
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
33426
|
-
className: styles.inlineContent
|
|
34219
|
+
className: styles$1.inlineContent
|
|
33427
34220
|
}, /*#__PURE__*/React.createElement(SparkleIcon, {
|
|
33428
|
-
className: styles.sparkle,
|
|
34221
|
+
className: styles$1.sparkle,
|
|
33429
34222
|
fill: "currentColor"
|
|
33430
34223
|
}), /*#__PURE__*/React.createElement("span", {
|
|
33431
|
-
className: styles.inlineTitle
|
|
34224
|
+
className: styles$1.inlineTitle
|
|
33432
34225
|
}, title)), /*#__PURE__*/React.createElement("span", {
|
|
33433
|
-
className: styles.action
|
|
34226
|
+
className: styles$1.action
|
|
33434
34227
|
}, /*#__PURE__*/React.createElement("span", {
|
|
33435
|
-
className: styles.actionLabel
|
|
34228
|
+
className: styles$1.actionLabel
|
|
33436
34229
|
}, action), /*#__PURE__*/React.createElement(tapestryReact.Icon, {
|
|
33437
|
-
className: styles.actionChevron,
|
|
34230
|
+
className: styles$1.actionChevron,
|
|
33438
34231
|
name: "general.rightChevron",
|
|
33439
34232
|
size: "14px"
|
|
33440
34233
|
})));
|
|
@@ -33451,9 +34244,9 @@ true;
|
|
|
33451
34244
|
const borderA = `${uid}-border-a`;
|
|
33452
34245
|
const borderB = `${uid}-border-b`;
|
|
33453
34246
|
return /*#__PURE__*/React.createElement("span", {
|
|
33454
|
-
className: styles.icon
|
|
34247
|
+
className: styles$1.icon
|
|
33455
34248
|
}, /*#__PURE__*/React.createElement("svg", {
|
|
33456
|
-
className: styles.iconShape,
|
|
34249
|
+
className: styles$1.iconShape,
|
|
33457
34250
|
viewBox: "0 0 32 32",
|
|
33458
34251
|
fill: "none",
|
|
33459
34252
|
"aria-hidden": "true"
|
|
@@ -33624,7 +34417,7 @@ true;
|
|
|
33624
34417
|
stopColor: "#D48B91",
|
|
33625
34418
|
stopOpacity: "0"
|
|
33626
34419
|
})))), /*#__PURE__*/React.createElement("svg", {
|
|
33627
|
-
className: styles.iconSparkle,
|
|
34420
|
+
className: styles$1.iconSparkle,
|
|
33628
34421
|
viewBox: "0 0 19.4995 17.9999",
|
|
33629
34422
|
"aria-hidden": "true"
|
|
33630
34423
|
}, /*#__PURE__*/React.createElement("path", {
|
|
@@ -33633,6 +34426,23 @@ true;
|
|
|
33633
34426
|
})));
|
|
33634
34427
|
}
|
|
33635
34428
|
|
|
34429
|
+
var styles = {"button":"button-2NVI1","sparkle":"sparkle-ruJGP","label":"label-4NUGB"};
|
|
34430
|
+
|
|
34431
|
+
function AskAIButton({
|
|
34432
|
+
onClick,
|
|
34433
|
+
text = "Ask AI"
|
|
34434
|
+
}) {
|
|
34435
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
34436
|
+
className: styles.button,
|
|
34437
|
+
onClick: () => onClick(),
|
|
34438
|
+
type: "button"
|
|
34439
|
+
}, /*#__PURE__*/React.createElement(SparkleIcon, {
|
|
34440
|
+
className: styles.sparkle
|
|
34441
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
34442
|
+
className: styles.label
|
|
34443
|
+
}, text));
|
|
34444
|
+
}
|
|
34445
|
+
|
|
33636
34446
|
// The article heading lives in the deepest populated hierarchy level (lvl0 is
|
|
33637
34447
|
// always "Planning Center").
|
|
33638
34448
|
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) || "";
|
|
@@ -33788,16 +34598,24 @@ true;
|
|
|
33788
34598
|
// not the path, so updating it is a same-document navigation that doesn't
|
|
33789
34599
|
// reload the iframe, but also lets the route survive a forced reload (e.g.
|
|
33790
34600
|
// Turbo re-parenting the topbar).
|
|
33791
|
-
|
|
34601
|
+
//
|
|
34602
|
+
// `panel` loads picobot as its own toolbar panel rather than inside Helpdesk.
|
|
34603
|
+
function buildPicobotSrc(origin, path, {
|
|
34604
|
+
panel = false
|
|
34605
|
+
} = {}) {
|
|
33792
34606
|
const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
|
|
33793
34607
|
const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
|
|
33794
34608
|
const fragment = queryPart !== undefined ? `${safePath}?${queryPart}` : safePath;
|
|
33795
|
-
const
|
|
34609
|
+
const url = new URL("topbar", origin);
|
|
34610
|
+
if (panel) url.searchParams.set("panel", "1");
|
|
34611
|
+
const base = url.toString();
|
|
33796
34612
|
return fragment ? `${base}#${fragment}` : base;
|
|
33797
34613
|
}
|
|
33798
34614
|
|
|
33799
34615
|
const LOAD_TIMEOUT_MS = 3000;
|
|
33800
|
-
function createPicobotIframe(picobotOrigin
|
|
34616
|
+
function createPicobotIframe(picobotOrigin, {
|
|
34617
|
+
panel = false
|
|
34618
|
+
} = {}) {
|
|
33801
34619
|
return /*#__PURE__*/React.forwardRef(function PicobotIframe({
|
|
33802
34620
|
currentPath: iframePath,
|
|
33803
34621
|
onChangePath: onIframeChangePath,
|
|
@@ -33853,7 +34671,9 @@ true;
|
|
|
33853
34671
|
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, picobotOrigin);
|
|
33854
34672
|
}
|
|
33855
34673
|
}), []);
|
|
33856
|
-
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath
|
|
34674
|
+
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath, {
|
|
34675
|
+
panel
|
|
34676
|
+
});
|
|
33857
34677
|
return /*#__PURE__*/React.createElement("iframe", {
|
|
33858
34678
|
ref: iframeRef,
|
|
33859
34679
|
src: iframeSrc,
|
|
@@ -33892,22 +34712,28 @@ true;
|
|
|
33892
34712
|
currentPath,
|
|
33893
34713
|
env,
|
|
33894
34714
|
helpdeskConfig,
|
|
33895
|
-
|
|
34715
|
+
isPicobotLoading,
|
|
34716
|
+
onChangePath,
|
|
34717
|
+
openPicobot
|
|
33896
34718
|
}) {
|
|
33897
34719
|
const PicobotIframe = React.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot")), [env]);
|
|
34720
|
+
const loadingFallback = /*#__PURE__*/React.createElement(StackView$1, {
|
|
34721
|
+
flex: 1,
|
|
34722
|
+
axis: "vertical"
|
|
34723
|
+
}, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
34724
|
+
closeDrawer: closeDrawer
|
|
34725
|
+
}, /*#__PURE__*/React.createElement(StackView$1, {
|
|
34726
|
+
axis: "horizontal",
|
|
34727
|
+
spacing: 1
|
|
34728
|
+
}, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null))));
|
|
34729
|
+
// The add-on reads its args once, so mounting before the gate answers
|
|
34730
|
+
// would leave out openPicobot for good.
|
|
34731
|
+
if (isPicobotLoading) return loadingFallback;
|
|
33898
34732
|
return /*#__PURE__*/React.createElement(addOns.UiExtensionFirstPartyComponent, {
|
|
33899
34733
|
grow: 1,
|
|
33900
34734
|
display: "flex",
|
|
33901
34735
|
extensionId: env === "staging" ? "42" : "11",
|
|
33902
|
-
loadingFallback:
|
|
33903
|
-
flex: 1,
|
|
33904
|
-
axis: "vertical"
|
|
33905
|
-
}, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
33906
|
-
closeDrawer: closeDrawer
|
|
33907
|
-
}, /*#__PURE__*/React.createElement(StackView$1, {
|
|
33908
|
-
axis: "horizontal",
|
|
33909
|
-
spacing: 1
|
|
33910
|
-
}, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null)))),
|
|
34736
|
+
loadingFallback: loadingFallback,
|
|
33911
34737
|
additionalComponents: {
|
|
33912
34738
|
AskAIBanner: {
|
|
33913
34739
|
component: AskAIBanner,
|
|
@@ -33927,6 +34753,18 @@ true;
|
|
|
33927
34753
|
}
|
|
33928
34754
|
}
|
|
33929
34755
|
},
|
|
34756
|
+
AskAIButton: {
|
|
34757
|
+
component: AskAIButton,
|
|
34758
|
+
properties: {
|
|
34759
|
+
onClick: {
|
|
34760
|
+
event: false,
|
|
34761
|
+
type: Function
|
|
34762
|
+
},
|
|
34763
|
+
text: {
|
|
34764
|
+
type: String
|
|
34765
|
+
}
|
|
34766
|
+
}
|
|
34767
|
+
},
|
|
33930
34768
|
CalendlyWidget: {
|
|
33931
34769
|
component: CalendlyWidget,
|
|
33932
34770
|
properties: {
|
|
@@ -34005,7 +34843,11 @@ true;
|
|
|
34005
34843
|
currentPageParams: location.search,
|
|
34006
34844
|
currentPath,
|
|
34007
34845
|
helpdeskConfig,
|
|
34008
|
-
onChangePath
|
|
34846
|
+
onChangePath,
|
|
34847
|
+
// Its presence tells helpdesk that Picobot has its own panel.
|
|
34848
|
+
...(openPicobot && {
|
|
34849
|
+
openPicobot
|
|
34850
|
+
})
|
|
34009
34851
|
}
|
|
34010
34852
|
});
|
|
34011
34853
|
}
|
|
@@ -34552,6 +35394,142 @@ true;
|
|
|
34552
35394
|
name: "Notifications"
|
|
34553
35395
|
};
|
|
34554
35396
|
|
|
35397
|
+
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";
|
|
35398
|
+
// Helpdesk reads this context off the email path to open its email form the
|
|
35399
|
+
// same way a handoff inside its own drawer does.
|
|
35400
|
+
function emailPathWithContext(context = {}) {
|
|
35401
|
+
const params = new URLSearchParams();
|
|
35402
|
+
for (const key of ["conversationId", "source", "turnIndex"]) {
|
|
35403
|
+
const value = context[key];
|
|
35404
|
+
if (value !== null && value !== undefined) params.set(key, String(value));
|
|
35405
|
+
}
|
|
35406
|
+
return `email?${params}`;
|
|
35407
|
+
}
|
|
35408
|
+
function PicobotComponent({
|
|
35409
|
+
clearPending,
|
|
35410
|
+
closeDrawer,
|
|
35411
|
+
currentPath,
|
|
35412
|
+
env,
|
|
35413
|
+
onChangePath,
|
|
35414
|
+
openTool,
|
|
35415
|
+
pending
|
|
35416
|
+
}) {
|
|
35417
|
+
const PicobotIframe = React.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot"), {
|
|
35418
|
+
panel: true
|
|
35419
|
+
}), [env]);
|
|
35420
|
+
const picobotRef = React.useRef(null);
|
|
35421
|
+
const [isPicobotReady, setIsPicobotReady] = React.useState(false);
|
|
35422
|
+
// Picobot has no listener until it sends READY, so openPicobot's draft or
|
|
35423
|
+
// conversation waits for it.
|
|
35424
|
+
React.useEffect(function handOverPendingOncePicobotIsReady() {
|
|
35425
|
+
var _a, _b;
|
|
35426
|
+
if (!pending || !isPicobotReady) return;
|
|
35427
|
+
const {
|
|
35428
|
+
conversationId,
|
|
35429
|
+
draft,
|
|
35430
|
+
ticketExternalId
|
|
35431
|
+
} = pending;
|
|
35432
|
+
if (conversationId) {
|
|
35433
|
+
(_a = picobotRef.current) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
35434
|
+
payload: {
|
|
35435
|
+
conversation_id: conversationId,
|
|
35436
|
+
// Picobot asks whether this resolved the ticket, and closes it.
|
|
35437
|
+
...(ticketExternalId && {
|
|
35438
|
+
external_id: ticketExternalId,
|
|
35439
|
+
source: "ticket_submitted"
|
|
35440
|
+
})
|
|
35441
|
+
},
|
|
35442
|
+
type: "LOAD_CONVERSATION"
|
|
35443
|
+
});
|
|
35444
|
+
} else if (draft) {
|
|
35445
|
+
// Not both: Picobot cancels a conversation load when a prefill
|
|
35446
|
+
// starts a new conversation.
|
|
35447
|
+
(_b = picobotRef.current) === null || _b === void 0 ? void 0 : _b.postMessage({
|
|
35448
|
+
payload: {
|
|
35449
|
+
source: "email_form",
|
|
35450
|
+
text: draft
|
|
35451
|
+
},
|
|
35452
|
+
type: "PREFILL_COMPOSER"
|
|
35453
|
+
});
|
|
35454
|
+
}
|
|
35455
|
+
clearPending();
|
|
35456
|
+
}, [pending, isPicobotReady, clearPending]);
|
|
35457
|
+
const [iframeErrored, setIframeErrored] = React.useState(false);
|
|
35458
|
+
const [mountKey, setMountKey] = React.useState(0);
|
|
35459
|
+
const handleRetry = React.useCallback(() => {
|
|
35460
|
+
setIframeErrored(false);
|
|
35461
|
+
setIsPicobotReady(false);
|
|
35462
|
+
setMountKey(key => key + 1);
|
|
35463
|
+
}, []);
|
|
35464
|
+
const handleMessage = React.useCallback(data => {
|
|
35465
|
+
var _a;
|
|
35466
|
+
const message = data;
|
|
35467
|
+
switch (message === null || message === void 0 ? void 0 : message.type) {
|
|
35468
|
+
case "CLOSE_DRAWER":
|
|
35469
|
+
return closeDrawer();
|
|
35470
|
+
case "READY":
|
|
35471
|
+
return setIsPicobotReady(true);
|
|
35472
|
+
case "PICOBOT_SUPPORT_HANDOFF":
|
|
35473
|
+
return openTool("helpdesk", ((_a = message.payload) === null || _a === void 0 ? void 0 : _a.method) === "zoom" ? "phone" : emailPathWithContext(message.payload));
|
|
35474
|
+
case "IFRAME_LOAD_ERROR":
|
|
35475
|
+
console.warn("topbar: Picobot iframe failed to load");
|
|
35476
|
+
return setIframeErrored(true);
|
|
35477
|
+
}
|
|
35478
|
+
}, [closeDrawer, openTool]);
|
|
35479
|
+
if (iframeErrored) {
|
|
35480
|
+
return /*#__PURE__*/React.createElement(LoadError, {
|
|
35481
|
+
onContactSupport: () => openTool("helpdesk", "email"),
|
|
35482
|
+
onRetry: handleRetry
|
|
35483
|
+
});
|
|
35484
|
+
}
|
|
35485
|
+
return /*#__PURE__*/React.createElement(PicobotIframe, {
|
|
35486
|
+
key: mountKey,
|
|
35487
|
+
ref: picobotRef,
|
|
35488
|
+
currentPath: currentPath,
|
|
35489
|
+
onChangePath: onChangePath,
|
|
35490
|
+
onMessage: handleMessage
|
|
35491
|
+
});
|
|
35492
|
+
}
|
|
35493
|
+
function LoadError({
|
|
35494
|
+
onContactSupport,
|
|
35495
|
+
onRetry
|
|
35496
|
+
}) {
|
|
35497
|
+
return /*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
35498
|
+
axis: "vertical",
|
|
35499
|
+
flex: 1,
|
|
35500
|
+
alignment: "center",
|
|
35501
|
+
distribution: "center",
|
|
35502
|
+
padding: 3,
|
|
35503
|
+
spacing: 1
|
|
35504
|
+
}, /*#__PURE__*/React.createElement(tapestryReact.Heading, {
|
|
35505
|
+
level: 2
|
|
35506
|
+
}, "Picobot is temporarily unavailable"), /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
35507
|
+
size: 5,
|
|
35508
|
+
color: "var(--t-text-color-secondary)"
|
|
35509
|
+
}, "Try again or contact support."), /*#__PURE__*/React.createElement(tapestryReact.Box, {
|
|
35510
|
+
paddingTop: 2
|
|
35511
|
+
}, /*#__PURE__*/React.createElement(tapestryReact.Button, {
|
|
35512
|
+
onClick: onRetry,
|
|
35513
|
+
iconLeft: "general.refresh",
|
|
35514
|
+
theme: "primary",
|
|
35515
|
+
variant: "fill",
|
|
35516
|
+
title: "Retry"
|
|
35517
|
+
})), /*#__PURE__*/React.createElement(tapestryReact.Button, {
|
|
35518
|
+
onClick: onContactSupport,
|
|
35519
|
+
variant: "naked",
|
|
35520
|
+
theme: "primary",
|
|
35521
|
+
title: "Contact support"
|
|
35522
|
+
}));
|
|
35523
|
+
}
|
|
35524
|
+
const Picobot = {
|
|
35525
|
+
component: PicobotComponent,
|
|
35526
|
+
icons: {
|
|
35527
|
+
staticDotPath: SPARKLE_PATH,
|
|
35528
|
+
staticPath: SPARKLE_PATH
|
|
35529
|
+
},
|
|
35530
|
+
name: "Picobot"
|
|
35531
|
+
};
|
|
35532
|
+
|
|
34555
35533
|
const {
|
|
34556
35534
|
LoadingIndicator,
|
|
34557
35535
|
StackView
|
|
@@ -34947,6 +35925,41 @@ true;
|
|
|
34947
35925
|
};
|
|
34948
35926
|
};
|
|
34949
35927
|
|
|
35928
|
+
// Picobot's API rejects requests without a version header, and apiRequest
|
|
35929
|
+
// sets its own headers, so the version rides in on the fetch.
|
|
35930
|
+
const picobotFetch = (url, opts) => defaultFetch(url, {
|
|
35931
|
+
...opts,
|
|
35932
|
+
headers: {
|
|
35933
|
+
...(opts === null || opts === void 0 ? void 0 : opts.headers),
|
|
35934
|
+
"X-PCO-API-Version": "2026-08-11"
|
|
35935
|
+
}
|
|
35936
|
+
});
|
|
35937
|
+
// Routes wait on the gate, so a slow Picobot can't hold them up for long.
|
|
35938
|
+
const GATE_TIMEOUT_MS = 3000;
|
|
35939
|
+
const usePicobotConfig = ({
|
|
35940
|
+
isEnabled
|
|
35941
|
+
}) => {
|
|
35942
|
+
// Undefined until Picobot answers.
|
|
35943
|
+
const [panelEnabled, setPanelEnabled] = React.useState();
|
|
35944
|
+
React.useEffect(() => {
|
|
35945
|
+
if (!isEnabled) return undefined;
|
|
35946
|
+
// Whatever settles first wins, so a late answer can't reopen the gate.
|
|
35947
|
+
const settle = enabled => setPanelEnabled(current => current !== null && current !== void 0 ? current : enabled);
|
|
35948
|
+
const timeout = setTimeout(() => settle(false), GATE_TIMEOUT_MS);
|
|
35949
|
+
apiRequest(picobotFetch, `${window.location.origin}/~api/picobot/v2/me`).then(({
|
|
35950
|
+
json
|
|
35951
|
+
}) => {
|
|
35952
|
+
var _a, _b;
|
|
35953
|
+
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);
|
|
35954
|
+
}).catch(() => settle(false)).finally(() => clearTimeout(timeout));
|
|
35955
|
+
return () => clearTimeout(timeout);
|
|
35956
|
+
}, [isEnabled]);
|
|
35957
|
+
return {
|
|
35958
|
+
displayPicobotIcon: panelEnabled === true,
|
|
35959
|
+
isLoading: isEnabled && panelEnabled === undefined
|
|
35960
|
+
};
|
|
35961
|
+
};
|
|
35962
|
+
|
|
34950
35963
|
const Profile = {
|
|
34951
35964
|
component: UserProfile,
|
|
34952
35965
|
icons: {
|
|
@@ -34957,10 +35970,16 @@ true;
|
|
|
34957
35970
|
};
|
|
34958
35971
|
|
|
34959
35972
|
const DURATION = 100;
|
|
35973
|
+
// Exempt paths match regardless of query, so `email?conversationId=…` is
|
|
35974
|
+
// exempt along with `email`.
|
|
34960
35975
|
function isToolPathExemptFromStorage(toolName, path) {
|
|
34961
35976
|
var _a, _b;
|
|
34962
35977
|
const tool = toolsRouteMap[toolName];
|
|
34963
|
-
|
|
35978
|
+
const [pathWithoutQuery] = path.split("?");
|
|
35979
|
+
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;
|
|
35980
|
+
}
|
|
35981
|
+
function isConversationPath(path) {
|
|
35982
|
+
return Boolean(path === null || path === void 0 ? void 0 : path.startsWith("conversations/"));
|
|
34964
35983
|
}
|
|
34965
35984
|
function scrollToolDrawerToTop(toolName) {
|
|
34966
35985
|
var _a;
|
|
@@ -34973,6 +35992,7 @@ true;
|
|
|
34973
35992
|
Chat,
|
|
34974
35993
|
Helpdesk,
|
|
34975
35994
|
Notifications,
|
|
35995
|
+
Picobot,
|
|
34976
35996
|
Profile,
|
|
34977
35997
|
Tasks
|
|
34978
35998
|
};
|
|
@@ -35000,7 +36020,8 @@ true;
|
|
|
35000
36020
|
});
|
|
35001
36021
|
const {
|
|
35002
36022
|
currentContent,
|
|
35003
|
-
open
|
|
36023
|
+
open,
|
|
36024
|
+
pending
|
|
35004
36025
|
} = state;
|
|
35005
36026
|
const mounted = !!currentContent;
|
|
35006
36027
|
const storeData = useStore();
|
|
@@ -35013,6 +36034,15 @@ true;
|
|
|
35013
36034
|
...(env === "development" ? _storeDataOverride !== null && _storeDataOverride !== void 0 ? _storeDataOverride : {} : {})
|
|
35014
36035
|
};
|
|
35015
36036
|
const updateStore = useUpdateStore();
|
|
36037
|
+
const {
|
|
36038
|
+
displayPicobotIcon,
|
|
36039
|
+
isLoading: isPicobotGateLoading
|
|
36040
|
+
} = usePicobotConfig({
|
|
36041
|
+
isEnabled: Boolean(userId)
|
|
36042
|
+
});
|
|
36043
|
+
// Picobot is a kill-switched tool: until its gate says the panel is
|
|
36044
|
+
// enabled, its routes are held, and if it never does, they're ignored.
|
|
36045
|
+
const routableTool = React.useCallback(name => name === Picobot.name.toLowerCase() && !displayPicobotIcon ? undefined : toolsRouteMap[name], [displayPicobotIcon]);
|
|
35016
36046
|
React.useEffect(() => {
|
|
35017
36047
|
if (open) {
|
|
35018
36048
|
document.body.style.overflow = "hidden";
|
|
@@ -35100,7 +36130,9 @@ true;
|
|
|
35100
36130
|
});
|
|
35101
36131
|
}, [clearRoute]);
|
|
35102
36132
|
React.useEffect(() => {
|
|
35103
|
-
if (!toolbarFragmentRoute)
|
|
36133
|
+
if (!toolbarFragmentRoute || !routableTool(toolbarFragmentRoute.tool)) {
|
|
36134
|
+
return;
|
|
36135
|
+
}
|
|
35104
36136
|
const isExempt = isToolPathExemptFromStorage(toolbarFragmentRoute.tool, toolbarFragmentRoute.path);
|
|
35105
36137
|
if (isExempt) return;
|
|
35106
36138
|
const nextState = {
|
|
@@ -35110,12 +36142,32 @@ true;
|
|
|
35110
36142
|
if (!shallowCompare(nextState, toolStateCookie)) {
|
|
35111
36143
|
setToolStateCookie(nextState);
|
|
35112
36144
|
}
|
|
35113
|
-
}, [toolbarFragmentRoute, toolStateCookie]);
|
|
36145
|
+
}, [toolbarFragmentRoute, toolStateCookie, routableTool]);
|
|
35114
36146
|
React.useEffect(() => {
|
|
35115
|
-
|
|
35116
|
-
|
|
36147
|
+
// Helpdesk conversations may belong to Picobot, so they wait for its gate
|
|
36148
|
+
// rather than flashing open in Helpdesk first.
|
|
36149
|
+
const mayRedirectToPicobot = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path) && (isPicobotGateLoading || displayPicobotIcon);
|
|
36150
|
+
const tool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && !mayRedirectToPicobot && routableTool(toolbarFragmentRoute.tool);
|
|
36151
|
+
if (tool && (currentContent !== tool || !open)) {
|
|
36152
|
+
setToolAndOpenDrawer(tool);
|
|
35117
36153
|
}
|
|
35118
|
-
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, currentContent, open]);
|
|
36154
|
+
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path, currentContent, open, routableTool, isPicobotGateLoading, displayPicobotIcon]);
|
|
36155
|
+
const openTool = React.useCallback((name, path, pendingValue) => {
|
|
36156
|
+
const tool = routableTool(name.toLowerCase());
|
|
36157
|
+
if (!tool) return;
|
|
36158
|
+
dispatch({
|
|
36159
|
+
payload: pendingValue === undefined ? null : {
|
|
36160
|
+
tool: tool.name,
|
|
36161
|
+
value: pendingValue
|
|
36162
|
+
},
|
|
36163
|
+
type: ActionType.setPending
|
|
36164
|
+
});
|
|
36165
|
+
onChangeToolbarFragmentRoute(tool.name.toLowerCase(), path);
|
|
36166
|
+
scrollToolDrawerToTop(tool.name);
|
|
36167
|
+
}, [onChangeToolbarFragmentRoute, routableTool]);
|
|
36168
|
+
const clearPending = React.useCallback(() => dispatch({
|
|
36169
|
+
type: ActionType.clearPending
|
|
36170
|
+
}), []);
|
|
35119
36171
|
const toggleDrawer = content => {
|
|
35120
36172
|
if (!open) {
|
|
35121
36173
|
return openDrawer(content);
|
|
@@ -35132,12 +36184,52 @@ true;
|
|
|
35132
36184
|
joltPromise: storeData === null || storeData === void 0 ? void 0 : storeData.joltPromise,
|
|
35133
36185
|
userId
|
|
35134
36186
|
});
|
|
35135
|
-
|
|
36187
|
+
// Picobot conversations moved from Helpdesk to the Picobot panel, so
|
|
36188
|
+
// send old links and saved paths there once the panel is enabled.
|
|
36189
|
+
React.useEffect(() => {
|
|
36190
|
+
if (!displayPicobotIcon) return;
|
|
36191
|
+
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path)) {
|
|
36192
|
+
onChangeToolbarFragmentRoute(Picobot.name.toLowerCase(), toolbarFragmentRoute.path);
|
|
36193
|
+
}
|
|
36194
|
+
}, [displayPicobotIcon, toolbarFragmentRoute, onChangeToolbarFragmentRoute]);
|
|
36195
|
+
React.useEffect(() => {
|
|
36196
|
+
if (!displayPicobotIcon) return;
|
|
36197
|
+
const {
|
|
36198
|
+
helpdesk,
|
|
36199
|
+
picobot
|
|
36200
|
+
} = toolStateCookie;
|
|
36201
|
+
if (!isConversationPath(helpdesk)) return;
|
|
36202
|
+
setToolStateCookie({
|
|
36203
|
+
...toolStateCookie,
|
|
36204
|
+
helpdesk: "",
|
|
36205
|
+
picobot: picobot || helpdesk
|
|
36206
|
+
});
|
|
36207
|
+
}, [displayPicobotIcon, toolStateCookie]);
|
|
36208
|
+
const openPicobot = React.useCallback(options => {
|
|
36209
|
+
var _a;
|
|
36210
|
+
// Moving an open Picobot off its path reloads or navigates the iframe,
|
|
36211
|
+
// which would race the options it's being handed.
|
|
36212
|
+
const path = currentContent === Picobot ? (_a = toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path) !== null && _a !== void 0 ? _a : "" : "";
|
|
36213
|
+
openTool(Picobot.name, path, options);
|
|
36214
|
+
}, [openTool, currentContent, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path]);
|
|
36215
|
+
React.useEffect(() => {
|
|
36216
|
+
if (!displayPicobotIcon) return undefined;
|
|
36217
|
+
window.topbar = window.topbar || {};
|
|
36218
|
+
window.topbar.openPicobot = openPicobot;
|
|
36219
|
+
return () => {
|
|
36220
|
+
delete window.topbar.openPicobot;
|
|
36221
|
+
};
|
|
36222
|
+
}, [displayPicobotIcon, openPicobot]);
|
|
36223
|
+
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat, displayPicobotIcon && Picobot, isPcoTestHost() && DeveloperTools].filter(Boolean);
|
|
35136
36224
|
const handleToolOnClick = tool => {
|
|
35137
36225
|
toggleDrawer(tool);
|
|
35138
36226
|
scrollToolDrawerToTop(tool.name);
|
|
35139
36227
|
};
|
|
35140
36228
|
const toolSpecificProps = {
|
|
36229
|
+
Helpdesk: {
|
|
36230
|
+
isPicobotLoading: isPicobotGateLoading,
|
|
36231
|
+
openPicobot: displayPicobotIcon ? openPicobot : undefined
|
|
36232
|
+
},
|
|
35141
36233
|
Profile: {
|
|
35142
36234
|
appName,
|
|
35143
36235
|
closeDrawer,
|
|
@@ -35160,6 +36252,7 @@ true;
|
|
|
35160
36252
|
const routeIsForCurrentTool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && toolsRouteMap[toolbarFragmentRoute.tool] === currentContent;
|
|
35161
36253
|
const currentToolProps = {
|
|
35162
36254
|
appName,
|
|
36255
|
+
clearPending,
|
|
35163
36256
|
closeDrawer,
|
|
35164
36257
|
currentPath: routeIsForCurrentTool ? toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path : null,
|
|
35165
36258
|
env,
|
|
@@ -35168,6 +36261,8 @@ true;
|
|
|
35168
36261
|
joltPromise,
|
|
35169
36262
|
onChangePath,
|
|
35170
36263
|
openDrawer: () => currentContent && openDrawer(currentContent),
|
|
36264
|
+
openTool,
|
|
36265
|
+
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,
|
|
35171
36266
|
userId,
|
|
35172
36267
|
...(currentContent ? toolSpecificProps[currentContent.name] : {})
|
|
35173
36268
|
};
|
|
@@ -35178,38 +36273,37 @@ true;
|
|
|
35178
36273
|
return /*#__PURE__*/React.createElement(tapestryReact.ThemeProvider, {
|
|
35179
36274
|
theme: topbarTheme
|
|
35180
36275
|
}, /*#__PURE__*/React.createElement(addOns.UiExtensionDataContext, null, /*#__PURE__*/React.createElement("div", {
|
|
35181
|
-
className: styles$
|
|
36276
|
+
className: styles$2["toolbar"],
|
|
35182
36277
|
"data-pendo": "toolbar-profile-support-wrapper"
|
|
35183
36278
|
}, /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
35184
36279
|
"aria-label": `Open ${userName}’s profile drawer`,
|
|
35185
36280
|
onClick: () => toggleDrawer(Profile),
|
|
35186
36281
|
"data-pendo": "toolbar-profile",
|
|
35187
|
-
className: styles$
|
|
36282
|
+
className: styles$2["toolbar-button--avatar"]
|
|
35188
36283
|
}, /*#__PURE__*/React.createElement(Avatar, {
|
|
35189
36284
|
env: env,
|
|
35190
36285
|
url: userAvatarPath
|
|
35191
36286
|
})), /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
35192
|
-
className: styles$
|
|
36287
|
+
className: styles$2["toolbar-button--home"],
|
|
35193
36288
|
onClick: () => closeDrawer()
|
|
35194
36289
|
}, /*#__PURE__*/React.createElement(tapestryReact.Icon, {
|
|
35195
36290
|
name: "toolbar.planningCenter",
|
|
35196
36291
|
size: "24px"
|
|
35197
36292
|
})), [HelpDeskTool, ...TOOLS].map(tool => {
|
|
36293
|
+
var _a;
|
|
35198
36294
|
const current = open && currentContent === tool;
|
|
35199
36295
|
const indicator = indicatorState[tool.name];
|
|
35200
36296
|
return /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
35201
36297
|
current: current,
|
|
35202
36298
|
onClick: () => handleToolOnClick(tool),
|
|
35203
36299
|
id: getDataPendoAttribute(tool.name),
|
|
35204
|
-
"aria-label": `Open ${tool.name} drawer`,
|
|
36300
|
+
"aria-label": `Open ${(_a = tool.label) !== null && _a !== void 0 ? _a : tool.name} drawer`,
|
|
35205
36301
|
"data-pendo": getDataPendoAttribute(tool.name),
|
|
35206
36302
|
key: tool.name
|
|
35207
|
-
},
|
|
35208
|
-
|
|
35209
|
-
|
|
35210
|
-
|
|
35211
|
-
dot: true
|
|
35212
|
-
})));
|
|
36303
|
+
}, /*#__PURE__*/React.createElement(ToolbarToolIcon, {
|
|
36304
|
+
icons: tool.icons,
|
|
36305
|
+
dot: !!indicator
|
|
36306
|
+
}));
|
|
35213
36307
|
})), /*#__PURE__*/React.createElement(DrawerWrap, {
|
|
35214
36308
|
isVisible: open
|
|
35215
36309
|
}, mounted && (/*#__PURE__*/React.createElement(Drawer, {
|
|
@@ -35227,12 +36321,25 @@ true;
|
|
|
35227
36321
|
}) => {
|
|
35228
36322
|
return /*#__PURE__*/React.createElement("button", {
|
|
35229
36323
|
type: "button",
|
|
35230
|
-
className: classnames(styles$
|
|
36324
|
+
className: classnames(styles$2["toolbar-button"], className, current && styles$2["is-current"]),
|
|
35231
36325
|
onClick: onClick,
|
|
35232
36326
|
"data-toolbar-link": "true",
|
|
35233
36327
|
...restProps
|
|
35234
36328
|
}, children);
|
|
35235
36329
|
};
|
|
36330
|
+
function ToolbarToolIcon({
|
|
36331
|
+
dot,
|
|
36332
|
+
icons
|
|
36333
|
+
}) {
|
|
36334
|
+
if ("component" in icons) {
|
|
36335
|
+
return /*#__PURE__*/React.createElement(icons.component, null);
|
|
36336
|
+
} else {
|
|
36337
|
+
return /*#__PURE__*/React.createElement(ToolbarButtonIcon, {
|
|
36338
|
+
path: dot ? icons.staticDotPath : icons.staticPath,
|
|
36339
|
+
dot: dot
|
|
36340
|
+
});
|
|
36341
|
+
}
|
|
36342
|
+
}
|
|
35236
36343
|
function ToolbarButtonIcon({
|
|
35237
36344
|
dot,
|
|
35238
36345
|
path
|