@planningcenter/topbar 12.3.0 → 12.4.0-qa-958.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/topbar.css +582 -17
- package/dist/topbar.css.map +1 -1
- package/dist/topbar.js +2358 -864
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +2362 -869
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +2362 -869
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +2360 -867
- package/dist/topbar.umd.js.map +1 -1
- package/package.json +4 -3
package/dist/topbar.js
CHANGED
|
@@ -7,11 +7,12 @@ var general = require('@planningcenter/icons/paths/general');
|
|
|
7
7
|
var Jolt = require('@planningcenter/jolt-client');
|
|
8
8
|
var shim = require('use-sync-external-store/shim');
|
|
9
9
|
require('react/jsx-runtime');
|
|
10
|
+
var toolbar = require('@planningcenter/icons/paths/toolbar');
|
|
11
|
+
var logomarks = require('@planningcenter/icons/paths/logomark');
|
|
10
12
|
var tapestryReact = require('@planningcenter/tapestry-react');
|
|
11
13
|
var addOns = require('@planningcenter/add-ons');
|
|
12
14
|
var churchCenter = require('@planningcenter/icons/paths/church-center');
|
|
13
15
|
var people = require('@planningcenter/icons/paths/people');
|
|
14
|
-
var toolbar = require('@planningcenter/icons/paths/toolbar');
|
|
15
16
|
var reactCalendly = require('react-calendly');
|
|
16
17
|
var algoliasearch = require('algoliasearch');
|
|
17
18
|
var reactInstantsearchCore = require('react-instantsearch-core');
|
|
@@ -37,11 +38,12 @@ function _interopNamespaceDefault(e) {
|
|
|
37
38
|
|
|
38
39
|
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
39
40
|
var general__namespace = /*#__PURE__*/_interopNamespaceDefault(general);
|
|
41
|
+
var toolbar__namespace = /*#__PURE__*/_interopNamespaceDefault(toolbar);
|
|
42
|
+
var logomarks__namespace = /*#__PURE__*/_interopNamespaceDefault(logomarks);
|
|
40
43
|
var churchCenter__namespace = /*#__PURE__*/_interopNamespaceDefault(churchCenter);
|
|
41
44
|
var people__namespace = /*#__PURE__*/_interopNamespaceDefault(people);
|
|
42
|
-
var toolbar__namespace = /*#__PURE__*/_interopNamespaceDefault(toolbar);
|
|
43
45
|
|
|
44
|
-
var styles$
|
|
46
|
+
var styles$s = {"accessibility-toolbar":"accessibility-toolbar-jgjUG"};
|
|
45
47
|
|
|
46
48
|
const AccessibilityToolbar = ({
|
|
47
49
|
children
|
|
@@ -53,7 +55,7 @@ const AccessibilityToolbar = ({
|
|
|
53
55
|
(_a = document.getElementById("main-content")) === null || _a === void 0 ? void 0 : _a.focus();
|
|
54
56
|
};
|
|
55
57
|
return /*#__PURE__*/React.createElement("div", {
|
|
56
|
-
className: styles$
|
|
58
|
+
className: styles$s["accessibility-toolbar"]
|
|
57
59
|
}, /*#__PURE__*/React.createElement(tapestry.Link, {
|
|
58
60
|
href: "#main-content",
|
|
59
61
|
kind: "primary",
|
|
@@ -180,12 +182,14 @@ function inferRailsCsrfToken() {
|
|
|
180
182
|
}
|
|
181
183
|
function apiRequest(fetch, url, {
|
|
182
184
|
method = "GET",
|
|
183
|
-
data = null
|
|
185
|
+
data = null,
|
|
186
|
+
headers = {}
|
|
184
187
|
} = {}) {
|
|
185
188
|
const options = {
|
|
186
189
|
headers: {
|
|
187
190
|
Accept: "application/json",
|
|
188
|
-
"Content-Type": "application/json"
|
|
191
|
+
"Content-Type": "application/json",
|
|
192
|
+
...headers
|
|
189
193
|
},
|
|
190
194
|
method,
|
|
191
195
|
redirect: "follow"
|
|
@@ -370,11 +374,11 @@ const X = ({
|
|
|
370
374
|
fill: fill
|
|
371
375
|
})));
|
|
372
376
|
|
|
373
|
-
var styles$
|
|
377
|
+
var styles$r = {"announcement":"announcement-pI5tM","announcement--platform":"announcement--platform-EcUn2","announcement--promo":"announcement--promo-FarUk","announcement-icon":"announcement-icon-Ov39-","announcement-content":"announcement-content-q2ad6","dismiss":"dismiss-wUAoU","dismiss--promo":"dismiss--promo-7zvO4"};
|
|
374
378
|
|
|
375
379
|
function AnnouncementIcon() {
|
|
376
380
|
return /*#__PURE__*/React.createElement("svg", {
|
|
377
|
-
className: styles$
|
|
381
|
+
className: styles$r["announcement-icon"],
|
|
378
382
|
width: "16",
|
|
379
383
|
height: "16",
|
|
380
384
|
viewBox: "0 0 16 16",
|
|
@@ -499,7 +503,7 @@ let StyledAnnouncement$1 = class StyledAnnouncement extends React.Component {
|
|
|
499
503
|
} = this.props;
|
|
500
504
|
const isPromo = variant === "promo";
|
|
501
505
|
return /*#__PURE__*/React.createElement("div", {
|
|
502
|
-
className: classnames(styles$
|
|
506
|
+
className: classnames(styles$r["announcement"], styles$r[`announcement--${variant !== null && variant !== void 0 ? variant : "platform"}`]),
|
|
503
507
|
"data-announcement-variant": variant !== null && variant !== void 0 ? variant : "platform",
|
|
504
508
|
style: isPromo ? {
|
|
505
509
|
backgroundImage: promoBackgroundUrl,
|
|
@@ -508,7 +512,7 @@ let StyledAnnouncement$1 = class StyledAnnouncement extends React.Component {
|
|
|
508
512
|
backgroundSize: "cover"
|
|
509
513
|
} : undefined
|
|
510
514
|
}, !isPromo && /*#__PURE__*/React.createElement(AnnouncementIcon, null), /*#__PURE__*/React.createElement("div", {
|
|
511
|
-
className: styles$
|
|
515
|
+
className: styles$r["announcement-content"],
|
|
512
516
|
style: style,
|
|
513
517
|
...platformProps
|
|
514
518
|
}));
|
|
@@ -557,7 +561,7 @@ function StyledDismissButton$1({
|
|
|
557
561
|
const isPromo = variant === "promo";
|
|
558
562
|
return /*#__PURE__*/React.createElement("button", {
|
|
559
563
|
type: "button",
|
|
560
|
-
className: classnames(styles$
|
|
564
|
+
className: classnames(styles$r.dismiss, isPromo && styles$r["dismiss--promo"]),
|
|
561
565
|
...props
|
|
562
566
|
}, /*#__PURE__*/React.createElement(X, {
|
|
563
567
|
fill: isPromo ? "currentColor" : "white",
|
|
@@ -637,9 +641,9 @@ class StyledAnnouncement extends React.Component {
|
|
|
637
641
|
...platformProps
|
|
638
642
|
} = this.props;
|
|
639
643
|
return /*#__PURE__*/React.createElement("div", {
|
|
640
|
-
className: styles$
|
|
644
|
+
className: styles$r["announcement"]
|
|
641
645
|
}, /*#__PURE__*/React.createElement(AnnouncementIcon, null), /*#__PURE__*/React.createElement("div", {
|
|
642
|
-
className: styles$
|
|
646
|
+
className: styles$r["announcement-content"],
|
|
643
647
|
style: style,
|
|
644
648
|
...platformProps
|
|
645
649
|
}));
|
|
@@ -650,7 +654,7 @@ function StyledDismissButton({
|
|
|
650
654
|
}) {
|
|
651
655
|
return /*#__PURE__*/React.createElement("button", {
|
|
652
656
|
type: "button",
|
|
653
|
-
className: styles$
|
|
657
|
+
className: styles$r.dismiss,
|
|
654
658
|
...props
|
|
655
659
|
}, /*#__PURE__*/React.createElement(X, {
|
|
656
660
|
fill: "white",
|
|
@@ -805,7 +809,7 @@ function useMediaQuery(query) {
|
|
|
805
809
|
return matches;
|
|
806
810
|
}
|
|
807
811
|
|
|
808
|
-
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
812
|
+
const capitalize$1 = s => s.charAt(0).toUpperCase() + s.slice(1);
|
|
809
813
|
const RESOLVED_COLOR_MODE_ATTRIBUTE = "data-resolved-color-mode";
|
|
810
814
|
function resolveColorMode(colorMode) {
|
|
811
815
|
if (colorMode === "system") {
|
|
@@ -1772,7 +1776,7 @@ function MonoAppIcon({
|
|
|
1772
1776
|
})) : (/*#__PURE__*/React.createElement("div", null, "appName not supported")));
|
|
1773
1777
|
}
|
|
1774
1778
|
|
|
1775
|
-
var styles$
|
|
1779
|
+
var styles$q = {"icon-slot":"icon-slot-m3ZIk","name":"name-UNqbW"};
|
|
1776
1780
|
|
|
1777
1781
|
const AppRow = ({
|
|
1778
1782
|
appName,
|
|
@@ -1781,24 +1785,24 @@ const AppRow = ({
|
|
|
1781
1785
|
}) => {
|
|
1782
1786
|
if (appName === "Accounts") {
|
|
1783
1787
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
1784
|
-
className: styles$
|
|
1788
|
+
className: styles$q["icon-slot"]
|
|
1785
1789
|
}, /*#__PURE__*/React.createElement(MonoAppIcon, {
|
|
1786
1790
|
appName: "Accounts",
|
|
1787
1791
|
colors: colors,
|
|
1788
1792
|
size: iconSize
|
|
1789
1793
|
})), /*#__PURE__*/React.createElement("span", {
|
|
1790
|
-
className: styles$
|
|
1794
|
+
className: styles$q.name
|
|
1791
1795
|
}, "Account settings"));
|
|
1792
1796
|
}
|
|
1793
1797
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
1794
1798
|
appName: appName.replace(/[\s-]/, ""),
|
|
1795
1799
|
size: iconSize
|
|
1796
1800
|
}), /*#__PURE__*/React.createElement("span", {
|
|
1797
|
-
className: styles$
|
|
1801
|
+
className: styles$q.name
|
|
1798
1802
|
}, appName));
|
|
1799
1803
|
};
|
|
1800
1804
|
|
|
1801
|
-
var styles$
|
|
1805
|
+
var styles$p = {"button":"button-XMbeQ","label":"label-884of","chevron":"chevron-RRg9k"};
|
|
1802
1806
|
|
|
1803
1807
|
const AppsButton = /*#__PURE__*/React.forwardRef(({
|
|
1804
1808
|
appName,
|
|
@@ -1810,10 +1814,10 @@ const AppsButton = /*#__PURE__*/React.forwardRef(({
|
|
|
1810
1814
|
...restProps,
|
|
1811
1815
|
ref: ref,
|
|
1812
1816
|
"aria-label": `App switcher: ${appName}`,
|
|
1813
|
-
className: styles$
|
|
1817
|
+
className: styles$p.button,
|
|
1814
1818
|
kind: "neutral-inline",
|
|
1815
1819
|
label: /*#__PURE__*/React.createElement("span", {
|
|
1816
|
-
className: styles$
|
|
1820
|
+
className: styles$p.label
|
|
1817
1821
|
}, isAccounts ? "Account settings" : appName),
|
|
1818
1822
|
prefix: isAccounts ? (/*#__PURE__*/React.createElement(MonoAppIcon, {
|
|
1819
1823
|
appName: "Accounts",
|
|
@@ -1824,13 +1828,13 @@ const AppsButton = /*#__PURE__*/React.forwardRef(({
|
|
|
1824
1828
|
size: 22
|
|
1825
1829
|
})),
|
|
1826
1830
|
suffix: /*#__PURE__*/React.createElement(DisclosureChevronIcon, {
|
|
1827
|
-
className: `${styles$
|
|
1831
|
+
className: `${styles$p.chevron} symbol`
|
|
1828
1832
|
})
|
|
1829
1833
|
});
|
|
1830
1834
|
});
|
|
1831
1835
|
AppsButton.displayName = "AppsButton";
|
|
1832
1836
|
|
|
1833
|
-
var styles$
|
|
1837
|
+
var styles$o = {"nav":"nav-y-6ML","popover-content":"popover-content-jOAAY","list":"list-jUxvp","link":"link-LMp2h","current":"current-4U4VL","accounts-item":"accounts-item-erJW0","accounts-link":"accounts-link-4jwPW"};
|
|
1834
1838
|
|
|
1835
1839
|
const AppsMenu = ({
|
|
1836
1840
|
appName,
|
|
@@ -1851,8 +1855,8 @@ const AppsMenu = ({
|
|
|
1851
1855
|
}, /*#__PURE__*/React.createElement("a", {
|
|
1852
1856
|
"aria-current": isCurrent ? "page" : undefined,
|
|
1853
1857
|
"aria-label": name,
|
|
1854
|
-
className: classnames(styles$
|
|
1855
|
-
[styles$
|
|
1858
|
+
className: classnames(styles$o.link, {
|
|
1859
|
+
[styles$o.current]: isCurrent
|
|
1856
1860
|
}),
|
|
1857
1861
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
1858
1862
|
"data-turbolinks": false,
|
|
@@ -1868,22 +1872,22 @@ const AppsMenu = ({
|
|
|
1868
1872
|
colors: colors
|
|
1869
1873
|
})), /*#__PURE__*/React.createElement(tapestry.PopoverContent, {
|
|
1870
1874
|
"aria-label": "Switch app",
|
|
1871
|
-
className: styles$
|
|
1875
|
+
className: styles$o["popover-content"],
|
|
1872
1876
|
placement: "bottom start"
|
|
1873
1877
|
}, /*#__PURE__*/React.createElement("nav", {
|
|
1874
|
-
className: styles$
|
|
1878
|
+
className: styles$o.nav,
|
|
1875
1879
|
"data-pendo": "app-switcher"
|
|
1876
1880
|
}, /*#__PURE__*/React.createElement("ul", {
|
|
1877
|
-
className: styles$
|
|
1881
|
+
className: styles$o.list,
|
|
1878
1882
|
role: "list"
|
|
1879
1883
|
}, homeProduct && renderProductLink(homeProduct.name), restProducts.map(({
|
|
1880
1884
|
name
|
|
1881
1885
|
}) => renderProductLink(name)), accountsProduct && (/*#__PURE__*/React.createElement("li", {
|
|
1882
|
-
className: styles$
|
|
1886
|
+
className: styles$o["accounts-item"]
|
|
1883
1887
|
}, /*#__PURE__*/React.createElement("a", {
|
|
1884
1888
|
"aria-current": appName === accountsProduct.name ? "page" : undefined,
|
|
1885
1889
|
"aria-label": "Account settings",
|
|
1886
|
-
className: classnames(styles$
|
|
1890
|
+
className: classnames(styles$o.link, styles$o["accounts-link"]),
|
|
1887
1891
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
1888
1892
|
"data-turbolinks": false,
|
|
1889
1893
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -2091,7 +2095,7 @@ function MonoAppName({
|
|
|
2091
2095
|
})) : (/*#__PURE__*/React.createElement("div", null, "appName not supported"));
|
|
2092
2096
|
}
|
|
2093
2097
|
|
|
2094
|
-
var styles$
|
|
2098
|
+
var styles$n = {"unbutton":"unbutton-lEbTR"};
|
|
2095
2099
|
|
|
2096
2100
|
function Unbutton({
|
|
2097
2101
|
className,
|
|
@@ -2100,7 +2104,7 @@ function Unbutton({
|
|
|
2100
2104
|
...nativeProps
|
|
2101
2105
|
}) {
|
|
2102
2106
|
return /*#__PURE__*/React.createElement("button", {
|
|
2103
|
-
className: classnames(styles$
|
|
2107
|
+
className: classnames(styles$n.unbutton, className),
|
|
2104
2108
|
style: {
|
|
2105
2109
|
...style
|
|
2106
2110
|
},
|
|
@@ -2109,7 +2113,7 @@ function Unbutton({
|
|
|
2109
2113
|
});
|
|
2110
2114
|
}
|
|
2111
2115
|
|
|
2112
|
-
var styles$
|
|
2116
|
+
var styles$m = {"button":"button-wsipc","button-accounts":"button-accounts-zQmBM","chevron":"chevron-obyBF","is-expanded":"is-expanded-PoCQ9"};
|
|
2113
2117
|
|
|
2114
2118
|
const LegacyAppsButton = ({
|
|
2115
2119
|
appName,
|
|
@@ -2124,8 +2128,8 @@ const LegacyAppsButton = ({
|
|
|
2124
2128
|
"aria-haspopup": "true",
|
|
2125
2129
|
"aria-label": `App switcher: ${appName}`,
|
|
2126
2130
|
onClick: onClick,
|
|
2127
|
-
className: classnames(styles$
|
|
2128
|
-
[styles$
|
|
2131
|
+
className: classnames(styles$m.button, {
|
|
2132
|
+
[styles$m["is-expanded"]]: expanded
|
|
2129
2133
|
}),
|
|
2130
2134
|
type: "button",
|
|
2131
2135
|
style: style,
|
|
@@ -2134,7 +2138,7 @@ const LegacyAppsButton = ({
|
|
|
2134
2138
|
appName: "Accounts",
|
|
2135
2139
|
colors: colors
|
|
2136
2140
|
}), /*#__PURE__*/React.createElement("div", {
|
|
2137
|
-
className: styles$
|
|
2141
|
+
className: styles$m["button-accounts"]
|
|
2138
2142
|
}, "Account settings"))) : (/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
2139
2143
|
appName: appName.replace(/[\s-]/, "")
|
|
2140
2144
|
}), /*#__PURE__*/React.createElement(MonoAppName, {
|
|
@@ -2142,7 +2146,7 @@ const LegacyAppsButton = ({
|
|
|
2142
2146
|
key: "mono-app-name",
|
|
2143
2147
|
size: 24
|
|
2144
2148
|
}))), /*#__PURE__*/React.createElement(DisclosureChevronIcon, {
|
|
2145
|
-
className: styles$
|
|
2149
|
+
className: styles$m.chevron,
|
|
2146
2150
|
key: "disclosure-chevron-icon"
|
|
2147
2151
|
}));
|
|
2148
2152
|
};
|
|
@@ -2275,7 +2279,7 @@ class Outsider extends React.Component {
|
|
|
2275
2279
|
}
|
|
2276
2280
|
}
|
|
2277
2281
|
|
|
2278
|
-
var styles$
|
|
2282
|
+
var styles$l = {"popup-root":"popup-root-SeL5u","popup":"popup-nQXYW","is-visible":"is-visible-3DAUP"};
|
|
2279
2283
|
|
|
2280
2284
|
const Popup = ({
|
|
2281
2285
|
className,
|
|
@@ -2283,8 +2287,8 @@ const Popup = ({
|
|
|
2283
2287
|
isVisible,
|
|
2284
2288
|
...nativeProps
|
|
2285
2289
|
}) => (/*#__PURE__*/React.createElement(C, {
|
|
2286
|
-
className: classnames(styles$
|
|
2287
|
-
[styles$
|
|
2290
|
+
className: classnames(styles$l.popup, {
|
|
2291
|
+
[styles$l["is-visible"]]: isVisible
|
|
2288
2292
|
}, className),
|
|
2289
2293
|
...nativeProps
|
|
2290
2294
|
}));
|
|
@@ -2292,11 +2296,11 @@ const Popup = ({
|
|
|
2292
2296
|
const PopupRoot = ({
|
|
2293
2297
|
...nativeProps
|
|
2294
2298
|
}) => (/*#__PURE__*/React.createElement("div", {
|
|
2295
|
-
className: styles$
|
|
2299
|
+
className: styles$l["popup-root"],
|
|
2296
2300
|
...nativeProps
|
|
2297
2301
|
}));
|
|
2298
2302
|
|
|
2299
|
-
var styles$
|
|
2303
|
+
var styles$k = {"popup":"popup-bVhSa"};
|
|
2300
2304
|
|
|
2301
2305
|
const LegacyAppsMenu = ({
|
|
2302
2306
|
appName,
|
|
@@ -2334,7 +2338,7 @@ const LegacyAppsMenu = ({
|
|
|
2334
2338
|
cleanup: !visible,
|
|
2335
2339
|
component: Outsider,
|
|
2336
2340
|
onOutsideClick: toggle,
|
|
2337
|
-
className: styles$
|
|
2341
|
+
className: styles$k.popup,
|
|
2338
2342
|
isVisible: visible
|
|
2339
2343
|
}, /*#__PURE__*/React.createElement("menu", {
|
|
2340
2344
|
ref: menuRef,
|
|
@@ -3244,29 +3248,121 @@ const useUpdateStore = () => React.useCallback(updates => {
|
|
|
3244
3248
|
});
|
|
3245
3249
|
}, []);
|
|
3246
3250
|
|
|
3247
|
-
const
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
const
|
|
3252
|
-
function
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3251
|
+
const collections = {
|
|
3252
|
+
general: general__namespace,
|
|
3253
|
+
toolbar: toolbar__namespace
|
|
3254
|
+
};
|
|
3255
|
+
const paths = collections;
|
|
3256
|
+
function Icon({
|
|
3257
|
+
name,
|
|
3258
|
+
size = 16,
|
|
3259
|
+
style,
|
|
3260
|
+
theme,
|
|
3261
|
+
title,
|
|
3262
|
+
...props
|
|
3263
|
+
}) {
|
|
3264
|
+
const [collection, symbol] = name.split(".");
|
|
3265
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
3266
|
+
"aria-hidden": title ? undefined : true,
|
|
3267
|
+
fill: "currentColor",
|
|
3268
|
+
height: size,
|
|
3269
|
+
role: title ? "img" : undefined,
|
|
3270
|
+
style: {
|
|
3271
|
+
color: theme && `var(${theme})`,
|
|
3272
|
+
flexShrink: 0,
|
|
3273
|
+
...style
|
|
3274
|
+
},
|
|
3275
|
+
viewBox: "0 0 16 16",
|
|
3276
|
+
width: size,
|
|
3277
|
+
...props
|
|
3278
|
+
}, title && /*#__PURE__*/React.createElement("title", null, title), /*#__PURE__*/React.createElement("path", {
|
|
3279
|
+
d: paths[collection][symbol]
|
|
3280
|
+
}));
|
|
3258
3281
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3282
|
+
|
|
3283
|
+
const SEARCHABLE_PRODUCTS = [{
|
|
3284
|
+
appName: "Calendar",
|
|
3285
|
+
slug: "calendar"
|
|
3286
|
+
}, {
|
|
3287
|
+
appName: "Check-Ins",
|
|
3288
|
+
slug: "check-ins"
|
|
3289
|
+
}, {
|
|
3290
|
+
appName: "Giving",
|
|
3291
|
+
slug: "giving"
|
|
3292
|
+
}, {
|
|
3293
|
+
appName: "Groups",
|
|
3294
|
+
slug: "groups"
|
|
3295
|
+
}, {
|
|
3296
|
+
appName: "People",
|
|
3297
|
+
slug: "people"
|
|
3298
|
+
}, {
|
|
3299
|
+
appName: "Publishing",
|
|
3300
|
+
slug: "publishing"
|
|
3301
|
+
}, {
|
|
3302
|
+
appName: "Registrations",
|
|
3303
|
+
slug: "registrations"
|
|
3304
|
+
}, {
|
|
3305
|
+
appName: "Services",
|
|
3306
|
+
slug: "services"
|
|
3307
|
+
}];
|
|
3308
|
+
const KEYBOARD_SHORTCUTS = {
|
|
3309
|
+
copyLink: {
|
|
3310
|
+
key: "c",
|
|
3311
|
+
label: "Copy link",
|
|
3312
|
+
modifierKey: true
|
|
3313
|
+
},
|
|
3314
|
+
openInNewTab: {
|
|
3315
|
+
label: "Open in new tab"},
|
|
3316
|
+
openSearch: {
|
|
3317
|
+
key: "k",
|
|
3318
|
+
label: "Open search",
|
|
3319
|
+
modifierKey: true
|
|
3320
|
+
},
|
|
3321
|
+
select: {
|
|
3322
|
+
label: "Select",
|
|
3323
|
+
modifierKey: false,
|
|
3324
|
+
next: "ArrowDown",
|
|
3325
|
+
previous: "ArrowUp"
|
|
3264
3326
|
}
|
|
3265
|
-
}
|
|
3327
|
+
};
|
|
3266
3328
|
|
|
3267
|
-
|
|
3329
|
+
const isMac = () => /Mac|iPhone|iPad|iPod/.test(navigator.platform);
|
|
3330
|
+
const hasCommandKey = event => isMac() ? event.metaKey : event.ctrlKey;
|
|
3331
|
+
const matchesShortcut = (shortcut, event) => event.key.toLowerCase() === shortcut.key && !event.altKey && !event.shiftKey && hasCommandKey(event) === shortcut.modifierKey;
|
|
3332
|
+
const isSearchShortcut = event => matchesShortcut(KEYBOARD_SHORTCUTS.openSearch, event);
|
|
3333
|
+
// Products opened their own search with `/` before this one replaced it.
|
|
3334
|
+
const isLegacySearchShortcut = event => event.key === "/" && !event.altKey && !event.ctrlKey && !event.metaKey && !isTypingIn(event.target);
|
|
3335
|
+
const isCopyShortcut = event => matchesShortcut(KEYBOARD_SHORTCUTS.copyLink, event);
|
|
3336
|
+
const isTypingIn = target => target instanceof Element && target.closest('input, select, textarea, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
3337
|
+
const isTypingKey = event => event.key.length === 1 && event.key !== " " && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
3338
|
+
const verticalStep = key => ({
|
|
3339
|
+
[KEYBOARD_SHORTCUTS.select.next]: 1,
|
|
3340
|
+
[KEYBOARD_SHORTCUTS.select.previous]: -1
|
|
3341
|
+
})[key];
|
|
3342
|
+
const horizontalStep = key => ({
|
|
3343
|
+
ArrowLeft: -1,
|
|
3344
|
+
ArrowRight: 1
|
|
3345
|
+
})[key];
|
|
3268
3346
|
|
|
3269
|
-
var styles$
|
|
3347
|
+
var styles$j = {"searchButton":"searchButton-fYa5-","mobile":"mobile-ksZyA"};
|
|
3348
|
+
|
|
3349
|
+
function SearchButton({
|
|
3350
|
+
mobile = false,
|
|
3351
|
+
onClick
|
|
3352
|
+
}) {
|
|
3353
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
3354
|
+
"aria-keyshortcuts": mobile ? undefined : `${isMac() ? "Meta" : "Control"}+${KEYBOARD_SHORTCUTS.openSearch.key.toUpperCase()} /`,
|
|
3355
|
+
"aria-label": "Search",
|
|
3356
|
+
className: classnames(styles$j.searchButton, {
|
|
3357
|
+
[styles$j.mobile]: mobile
|
|
3358
|
+
}),
|
|
3359
|
+
onClick: onClick,
|
|
3360
|
+
type: "button"
|
|
3361
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
3362
|
+
name: "general.search",
|
|
3363
|
+
size: mobile ? 18 : 14
|
|
3364
|
+
}));
|
|
3365
|
+
}
|
|
3270
3366
|
|
|
3271
3367
|
// useId isn't available on React 17, which our peer range still supports.
|
|
3272
3368
|
let instanceCount$1 = 0;
|
|
@@ -3275,173 +3371,33 @@ function useInstanceId(prefix) {
|
|
|
3275
3371
|
return id;
|
|
3276
3372
|
}
|
|
3277
3373
|
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
}
|
|
3303
|
-
};
|
|
3304
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
3305
|
-
"aria-label": "Product",
|
|
3306
|
-
onKeyDown: handleKeyDown,
|
|
3307
|
-
role: "tablist"
|
|
3308
|
-
}, products.map(product => (/*#__PURE__*/React.createElement("button", {
|
|
3309
|
-
"aria-controls": `${id}-panel`,
|
|
3310
|
-
"aria-selected": product === selected,
|
|
3311
|
-
className: styles$b.tab,
|
|
3312
|
-
id: tabId(product.slug),
|
|
3313
|
-
key: product.slug,
|
|
3314
|
-
onClick: () => onSelect(product.slug),
|
|
3315
|
-
ref: element => {
|
|
3316
|
-
if (element) {
|
|
3317
|
-
tabRefs.current.set(product.slug, element);
|
|
3318
|
-
} else {
|
|
3319
|
-
tabRefs.current.delete(product.slug);
|
|
3320
|
-
}
|
|
3321
|
-
},
|
|
3322
|
-
role: "tab",
|
|
3323
|
-
tabIndex: product === selected ? 0 : -1,
|
|
3324
|
-
type: "button"
|
|
3325
|
-
}, product.appName)))), /*#__PURE__*/React.createElement("div", {
|
|
3326
|
-
"aria-labelledby": tabId(selected.slug),
|
|
3327
|
-
id: `${id}-panel`,
|
|
3328
|
-
role: "tabpanel"
|
|
3329
|
-
}, children));
|
|
3330
|
-
}
|
|
3331
|
-
|
|
3332
|
-
var styles$a = {"result":"result-WCnxr"};
|
|
3333
|
-
|
|
3334
|
-
function SearchResult({
|
|
3335
|
-
highlighted,
|
|
3336
|
-
id,
|
|
3337
|
-
onHighlight,
|
|
3338
|
-
result
|
|
3339
|
-
}) {
|
|
3340
|
-
const {
|
|
3341
|
-
metadata,
|
|
3342
|
-
title,
|
|
3343
|
-
url
|
|
3344
|
-
} = result.attributes;
|
|
3345
|
-
const ref = React.useRef(null);
|
|
3346
|
-
React.useEffect(() => {
|
|
3347
|
-
var _a;
|
|
3348
|
-
if (highlighted) (_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
3349
|
-
block: "nearest"
|
|
3350
|
-
});
|
|
3351
|
-
}, [highlighted]);
|
|
3352
|
-
// Focus stays in the search input, which points at the highlighted result
|
|
3353
|
-
// with aria-activedescendant, so results are kept out of the tab order.
|
|
3354
|
-
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
3355
|
-
// pointer, and that shouldn't steal the highlight.
|
|
3356
|
-
return /*#__PURE__*/React.createElement("a", {
|
|
3357
|
-
"aria-selected": highlighted,
|
|
3358
|
-
className: styles$a.result,
|
|
3359
|
-
href: url,
|
|
3360
|
-
id: id,
|
|
3361
|
-
onMouseMove: onHighlight,
|
|
3362
|
-
ref: ref,
|
|
3363
|
-
role: "option",
|
|
3364
|
-
tabIndex: -1
|
|
3365
|
-
}, /*#__PURE__*/React.createElement("div", null, title), /*#__PURE__*/React.createElement("div", null, summarize(metadata)));
|
|
3366
|
-
}
|
|
3367
|
-
function summarize(metadata) {
|
|
3368
|
-
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
3369
|
-
}
|
|
3370
|
-
|
|
3371
|
-
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
3372
|
-
function SearchResults({
|
|
3373
|
-
search,
|
|
3374
|
-
term,
|
|
3375
|
-
...navigation
|
|
3376
|
-
}) {
|
|
3377
|
-
var _a;
|
|
3378
|
-
switch (search.status) {
|
|
3379
|
-
case "idle":
|
|
3380
|
-
return null;
|
|
3381
|
-
case "loading":
|
|
3382
|
-
if ((_a = search.groups) === null || _a === void 0 ? void 0 : _a.length) {
|
|
3383
|
-
return /*#__PURE__*/React.createElement(ResultGroups, {
|
|
3384
|
-
busy: true,
|
|
3385
|
-
groups: search.groups,
|
|
3386
|
-
...navigation
|
|
3387
|
-
});
|
|
3388
|
-
}
|
|
3389
|
-
return /*#__PURE__*/React.createElement("p", {
|
|
3390
|
-
role: "status"
|
|
3391
|
-
}, "Searching\u2026");
|
|
3392
|
-
case "error":
|
|
3393
|
-
return /*#__PURE__*/React.createElement("p", {
|
|
3394
|
-
role: "alert"
|
|
3395
|
-
}, "Search isn\u2019t working right now. Try again.");
|
|
3396
|
-
case "done":
|
|
3397
|
-
if (search.groups.length === 0) {
|
|
3398
|
-
return /*#__PURE__*/React.createElement("p", {
|
|
3399
|
-
role: "status"
|
|
3400
|
-
}, "No results for \u201C", term.trim(), "\u201D");
|
|
3401
|
-
}
|
|
3402
|
-
return /*#__PURE__*/React.createElement(ResultGroups, {
|
|
3403
|
-
groups: search.groups,
|
|
3404
|
-
...navigation
|
|
3405
|
-
});
|
|
3374
|
+
// The versions home-mobile pins, so web and mobile read the same response
|
|
3375
|
+
// shapes. Some endpoints only exist from a version on: Registrations' me,
|
|
3376
|
+
// product_search_results, and pcrl_lookups need 2025-05-01.
|
|
3377
|
+
const API_VERSIONS = {
|
|
3378
|
+
calendar: "2022-07-07",
|
|
3379
|
+
"check-ins": "2025-05-28",
|
|
3380
|
+
giving: "2019-10-18",
|
|
3381
|
+
groups: "2023-07-10",
|
|
3382
|
+
home: "2018-08-01",
|
|
3383
|
+
people: "2023-03-21",
|
|
3384
|
+
publishing: "2024-03-25",
|
|
3385
|
+
registrations: "2025-05-01",
|
|
3386
|
+
services: "2018-11-01"
|
|
3387
|
+
};
|
|
3388
|
+
// For a `/~api/{product}/v2/...` URL, the header that pins its product's
|
|
3389
|
+
// version.
|
|
3390
|
+
function apiVersionHeaders(url) {
|
|
3391
|
+
const [,, product] = new URL(url).pathname.split("/");
|
|
3392
|
+
const version = API_VERSIONS[product];
|
|
3393
|
+
if (version) {
|
|
3394
|
+
return {
|
|
3395
|
+
"X-PCO-API-Version": version
|
|
3396
|
+
};
|
|
3397
|
+
} else {
|
|
3398
|
+
return {};
|
|
3406
3399
|
}
|
|
3407
3400
|
}
|
|
3408
|
-
function ResultGroups({
|
|
3409
|
-
busy = false,
|
|
3410
|
-
groups,
|
|
3411
|
-
highlightedIndex,
|
|
3412
|
-
id,
|
|
3413
|
-
label,
|
|
3414
|
-
onHighlight
|
|
3415
|
-
}) {
|
|
3416
|
-
let index = 0;
|
|
3417
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
3418
|
-
"aria-busy": busy,
|
|
3419
|
-
"aria-label": label,
|
|
3420
|
-
id: id,
|
|
3421
|
-
role: "listbox"
|
|
3422
|
-
}, groups.map(({
|
|
3423
|
-
results,
|
|
3424
|
-
type
|
|
3425
|
-
}, groupIndex) => {
|
|
3426
|
-
const headingId = `${id}-group-${groupIndex}`;
|
|
3427
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
3428
|
-
"aria-labelledby": headingId,
|
|
3429
|
-
key: type,
|
|
3430
|
-
role: "group"
|
|
3431
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
3432
|
-
id: headingId
|
|
3433
|
-
}, type), results.map(result => {
|
|
3434
|
-
const resultIndex = index++;
|
|
3435
|
-
return /*#__PURE__*/React.createElement(SearchResult, {
|
|
3436
|
-
highlighted: resultIndex === highlightedIndex,
|
|
3437
|
-
id: resultId(id, resultIndex),
|
|
3438
|
-
key: result.id,
|
|
3439
|
-
onHighlight: () => onHighlight(resultIndex),
|
|
3440
|
-
result: result
|
|
3441
|
-
});
|
|
3442
|
-
}));
|
|
3443
|
-
}));
|
|
3444
|
-
}
|
|
3445
3401
|
|
|
3446
3402
|
const CACHE_TTL_MS = 60000;
|
|
3447
3403
|
const MAX_ENTRIES$1 = 50;
|
|
@@ -3458,7 +3414,9 @@ function cachedRequest(url) {
|
|
|
3458
3414
|
}
|
|
3459
3415
|
}
|
|
3460
3416
|
function fetchIntoCache(url) {
|
|
3461
|
-
const promise = apiRequest(defaultFetch, url
|
|
3417
|
+
const promise = apiRequest(defaultFetch, url, {
|
|
3418
|
+
headers: apiVersionHeaders(url)
|
|
3419
|
+
}).then(({
|
|
3462
3420
|
json,
|
|
3463
3421
|
ok
|
|
3464
3422
|
}) => {
|
|
@@ -3514,44 +3472,52 @@ function useCachedRequest(url) {
|
|
|
3514
3472
|
};
|
|
3515
3473
|
}
|
|
3516
3474
|
}
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
const
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
return {
|
|
3541
|
-
|
|
3542
|
-
status: "done"
|
|
3543
|
-
};
|
|
3544
|
-
} else if (request.status === "error") {
|
|
3545
|
-
return {
|
|
3546
|
-
status: "error"
|
|
3547
|
-
};
|
|
3548
|
-
} else {
|
|
3549
|
-
return {
|
|
3550
|
-
groups,
|
|
3551
|
-
status: "loading"
|
|
3475
|
+
// One request per URL, for callers that fan out to a varying number of
|
|
3476
|
+
// endpoints. Unlike `useCachedRequest`, a URL that hasn't settled yet has no
|
|
3477
|
+
// earlier data to fall back on.
|
|
3478
|
+
function useCachedRequests(urls) {
|
|
3479
|
+
const [settled, setSettled] = React.useState({});
|
|
3480
|
+
// Keyed by value because each settle re-renders the caller, and a caller
|
|
3481
|
+
// that builds a fresh `urls` array on render would otherwise loop forever.
|
|
3482
|
+
const key = urls.join("\n");
|
|
3483
|
+
React.useEffect(() => {
|
|
3484
|
+
let current = true;
|
|
3485
|
+
const settle = result => current && setSettled(prev => ({
|
|
3486
|
+
...prev,
|
|
3487
|
+
[result.url]: result
|
|
3488
|
+
}));
|
|
3489
|
+
for (const url of key.split("\n").filter(Boolean)) {
|
|
3490
|
+
cachedRequest(url).then(data => settle({
|
|
3491
|
+
data,
|
|
3492
|
+
url
|
|
3493
|
+
}), () => settle({
|
|
3494
|
+
failed: true,
|
|
3495
|
+
url
|
|
3496
|
+
}));
|
|
3497
|
+
}
|
|
3498
|
+
return () => {
|
|
3499
|
+
current = false;
|
|
3552
3500
|
};
|
|
3553
|
-
}
|
|
3501
|
+
}, [key]);
|
|
3502
|
+
return urls.map(url => {
|
|
3503
|
+
const result = settled[url];
|
|
3504
|
+
if (!result) {
|
|
3505
|
+
return {
|
|
3506
|
+
status: "loading"
|
|
3507
|
+
};
|
|
3508
|
+
} else if ("data" in result) {
|
|
3509
|
+
return {
|
|
3510
|
+
data: result.data,
|
|
3511
|
+
status: "done"
|
|
3512
|
+
};
|
|
3513
|
+
} else {
|
|
3514
|
+
return {
|
|
3515
|
+
status: "error"
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3554
3519
|
}
|
|
3520
|
+
|
|
3555
3521
|
function productSearchUrl(product, query) {
|
|
3556
3522
|
const params = new URLSearchParams({
|
|
3557
3523
|
per_page: "100",
|
|
@@ -3582,6 +3548,53 @@ function groupByType({
|
|
|
3582
3548
|
};
|
|
3583
3549
|
return [...groups.values()].sort((a, b) => rank(a.type) - rank(b.type));
|
|
3584
3550
|
}
|
|
3551
|
+
|
|
3552
|
+
function useDebouncedValue(value, delay) {
|
|
3553
|
+
const [debounced, setDebounced] = React.useState(value);
|
|
3554
|
+
React.useEffect(() => {
|
|
3555
|
+
const timeout = setTimeout(() => setDebounced(value), delay);
|
|
3556
|
+
return () => clearTimeout(timeout);
|
|
3557
|
+
}, [value, delay]);
|
|
3558
|
+
return debounced;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
3562
|
+
function useProductSearch(product, term) {
|
|
3563
|
+
const query = term.trim();
|
|
3564
|
+
const debouncedQuery = useDebouncedValue(query, SEARCH_DEBOUNCE_MS);
|
|
3565
|
+
const request = useCachedRequest(debouncedQuery ? productSearchUrl(product, debouncedQuery) : null);
|
|
3566
|
+
const response = "data" in request ? request.data : undefined;
|
|
3567
|
+
const settledProduct = useSettledProduct(product, request.status);
|
|
3568
|
+
// While a request loads, the previous response stays on screen, but only
|
|
3569
|
+
// if it was for this product: People results must never show under the
|
|
3570
|
+
// Services tab.
|
|
3571
|
+
const visibleResponse = request.status === "done" || settledProduct === product ? response : undefined;
|
|
3572
|
+
const groups = React.useMemo(() => visibleResponse && groupByType(visibleResponse), [visibleResponse]);
|
|
3573
|
+
if (!query) {
|
|
3574
|
+
return {
|
|
3575
|
+
status: "idle"
|
|
3576
|
+
};
|
|
3577
|
+
} else if (query !== debouncedQuery) {
|
|
3578
|
+
return {
|
|
3579
|
+
groups,
|
|
3580
|
+
status: "loading"
|
|
3581
|
+
};
|
|
3582
|
+
} else if (request.status === "done") {
|
|
3583
|
+
return {
|
|
3584
|
+
groups: groups !== null && groups !== void 0 ? groups : [],
|
|
3585
|
+
status: "done"
|
|
3586
|
+
};
|
|
3587
|
+
} else if (request.status === "error") {
|
|
3588
|
+
return {
|
|
3589
|
+
status: "error"
|
|
3590
|
+
};
|
|
3591
|
+
} else {
|
|
3592
|
+
return {
|
|
3593
|
+
groups,
|
|
3594
|
+
status: "loading"
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3585
3598
|
// The product the most recent finished request was for.
|
|
3586
3599
|
function useSettledProduct(product, status) {
|
|
3587
3600
|
const [settledProduct, setSettledProduct] = React.useState();
|
|
@@ -3590,29 +3603,489 @@ function useSettledProduct(product, status) {
|
|
|
3590
3603
|
}, [product, status]);
|
|
3591
3604
|
return settledProduct;
|
|
3592
3605
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3606
|
+
|
|
3607
|
+
function parseValue(element) {
|
|
3608
|
+
const datatype = element.getAttribute("datatype");
|
|
3609
|
+
switch (datatype) {
|
|
3610
|
+
case "boolean":
|
|
3611
|
+
return element.content === "true" || element.content === "1";
|
|
3612
|
+
default:
|
|
3613
|
+
return element.content;
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
function getAllPcoMeta() {
|
|
3617
|
+
const elements = document.head.querySelectorAll(`meta[property^="pco:"]`);
|
|
3618
|
+
const meta = {};
|
|
3619
|
+
elements.forEach(e => {
|
|
3620
|
+
const property = e.getAttribute("property");
|
|
3621
|
+
meta[property.substring("pco:".length)] = parseValue(e);
|
|
3622
|
+
});
|
|
3623
|
+
return meta;
|
|
3624
|
+
}
|
|
3625
|
+
// usePcoMeta only reads each tag's content. Recently Visited also needs the
|
|
3626
|
+
// pco:recently_visitable tag's `metadata` attribute, which can change while
|
|
3627
|
+
// the content stays the same, so it is tracked in its own store.
|
|
3628
|
+
function getRecentlyVisitableMetadata() {
|
|
3629
|
+
var _a, _b;
|
|
3630
|
+
return (_b = (_a = document.head.querySelector(`meta[property="pco:recently_visitable"]`)) === null || _a === void 0 ? void 0 : _a.getAttribute("metadata")) !== null && _b !== void 0 ? _b : null;
|
|
3631
|
+
}
|
|
3632
|
+
const store = createStore(getAllPcoMeta());
|
|
3633
|
+
const recentlyVisitableMetadataStore = createStore({
|
|
3634
|
+
metadata: getRecentlyVisitableMetadata()
|
|
3635
|
+
});
|
|
3636
|
+
function sync() {
|
|
3637
|
+
const nextState = getAllPcoMeta();
|
|
3638
|
+
if (!shallowCompare(store.getState(), nextState)) store.setState(nextState);
|
|
3639
|
+
const metadata = getRecentlyVisitableMetadata();
|
|
3640
|
+
if (recentlyVisitableMetadataStore.getState().metadata !== metadata) {
|
|
3641
|
+
recentlyVisitableMetadataStore.setState({
|
|
3642
|
+
metadata
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
const observer = new MutationObserver(sync);
|
|
3647
|
+
let hasTriggeredObserve = false;
|
|
3648
|
+
function observe() {
|
|
3649
|
+
if (hasTriggeredObserve) return;
|
|
3650
|
+
hasTriggeredObserve = true;
|
|
3651
|
+
// The module-load snapshot above can predate meta tags that appear later
|
|
3652
|
+
// in <head> (e.g. pco:resource, yielded right before </head>) if this
|
|
3653
|
+
// module's own script tag isn't deferred and runs mid-parse. Those tags
|
|
3654
|
+
// never mutate afterward, so the observer alone would never catch them.
|
|
3655
|
+
// Re-read once now, since observe() is only called once a consumer is
|
|
3656
|
+
// actually mounting, which is always after the document has fully parsed.
|
|
3657
|
+
sync();
|
|
3658
|
+
observer.observe(document.head, {
|
|
3659
|
+
attributes: true,
|
|
3660
|
+
childList: true,
|
|
3661
|
+
subtree: true
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
function usePcoMeta() {
|
|
3665
|
+
observe();
|
|
3666
|
+
return shim.useSyncExternalStore(store.subscribe, () => store.getState());
|
|
3667
|
+
}
|
|
3668
|
+
function useRecentlyVisitableMetadata() {
|
|
3669
|
+
observe();
|
|
3670
|
+
return shim.useSyncExternalStore(recentlyVisitableMetadataStore.subscribe, () => recentlyVisitableMetadataStore.getState().metadata);
|
|
3600
3671
|
}
|
|
3601
3672
|
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3673
|
+
// Entries match the ones home-mobile stores, so one list can sync across web
|
|
3674
|
+
// and mobile.
|
|
3675
|
+
const ENTRY_VERSION = 3;
|
|
3676
|
+
const MAX_ENTRIES = 50;
|
|
3677
|
+
const MAX_TEXT_LENGTH = 200;
|
|
3678
|
+
const PRODUCTS$1 = ["accounts", "calendar", "check-ins", "giving", "groups", "home", "login", "people", "publishing", "registrations", "services"];
|
|
3679
|
+
// Login has no pages worth returning to.
|
|
3680
|
+
const APPS = PRODUCTS$1.filter(product => product !== "login");
|
|
3681
|
+
function isId(value) {
|
|
3682
|
+
return value !== "" && Array.from(value).every(character => character >= "0" && character <= "9");
|
|
3683
|
+
}
|
|
3684
|
+
// A PCRL looks like `<product>/v2/<resource_type>/<id>`.
|
|
3685
|
+
function isPcrl(value) {
|
|
3686
|
+
const segments = value.split("/");
|
|
3687
|
+
return segments.length === 4 && PRODUCTS$1.some(product => product === segments[0]) && isId(segments[3]);
|
|
3688
|
+
}
|
|
3689
|
+
function isApp(value) {
|
|
3690
|
+
return APPS.some(app => app === value);
|
|
3691
|
+
}
|
|
3692
|
+
// Links are built by resolving the path against its app's origin, so a stored
|
|
3693
|
+
// path must not be able to resolve anywhere else (`//evil.com`, `/\evil.com`).
|
|
3694
|
+
function isPath(value) {
|
|
3695
|
+
const origin = "https://app.invalid";
|
|
3696
|
+
try {
|
|
3697
|
+
return typeof value === "string" && value.startsWith("/") && new URL(value, origin).origin === origin;
|
|
3698
|
+
} catch {
|
|
3699
|
+
return false;
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
function isNonBlankString(value) {
|
|
3703
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3704
|
+
}
|
|
3705
|
+
function isOptionalString(value) {
|
|
3706
|
+
return value === undefined || typeof value === "string";
|
|
3707
|
+
}
|
|
3708
|
+
function isRecord(value) {
|
|
3709
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3710
|
+
}
|
|
3711
|
+
function isPageMetadata(value) {
|
|
3712
|
+
return value === undefined || isRecord(value) && isOptionalString(value.details) && isOptionalString(value.type);
|
|
3713
|
+
}
|
|
3714
|
+
// The query string is part of the link but not of the page's identity, so
|
|
3715
|
+
// revisiting with other filters moves the existing entry instead of adding one.
|
|
3716
|
+
function entryKey(entry) {
|
|
3717
|
+
if (entry.type === "resource") {
|
|
3718
|
+
return `resource:${entry.pcrl}`;
|
|
3719
|
+
} else {
|
|
3720
|
+
return `page:${entry.app}:${entry.path.split("?")[0]}`;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
function isEntry(value) {
|
|
3724
|
+
if (!isRecord(value) || value.version !== ENTRY_VERSION || typeof value.visitedAt !== "string") {
|
|
3725
|
+
return false;
|
|
3726
|
+
} else if (value.type === "resource") {
|
|
3727
|
+
return typeof value.pcrl === "string" && isPcrl(value.pcrl);
|
|
3728
|
+
} else if (value.type === "page") {
|
|
3729
|
+
return isApp(value.app) && isPath(value.path) && isNonBlankString(value.title) && isPageMetadata(value.metadata);
|
|
3730
|
+
} else {
|
|
3731
|
+
return false;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
// Cuts on code points so a surrogate pair is never split.
|
|
3735
|
+
function clampText(value) {
|
|
3736
|
+
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
3737
|
+
}
|
|
3738
|
+
function clampMetadata({
|
|
3739
|
+
details,
|
|
3740
|
+
type
|
|
3741
|
+
}) {
|
|
3742
|
+
return {
|
|
3743
|
+
...(details && {
|
|
3744
|
+
details: clampText(details)
|
|
3745
|
+
}),
|
|
3746
|
+
...(type && {
|
|
3747
|
+
type: clampText(type)
|
|
3748
|
+
})
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3751
|
+
function clampVisit(visit) {
|
|
3752
|
+
if (visit.type === "resource") {
|
|
3753
|
+
return visit;
|
|
3754
|
+
} else {
|
|
3755
|
+
return {
|
|
3756
|
+
...visit,
|
|
3757
|
+
...(visit.metadata && {
|
|
3758
|
+
metadata: clampMetadata(visit.metadata)
|
|
3759
|
+
}),
|
|
3760
|
+
title: clampText(visit.title.trim())
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
function toEntry(visit, visitedAt) {
|
|
3765
|
+
return {
|
|
3766
|
+
...clampVisit(visit),
|
|
3767
|
+
version: ENTRY_VERSION,
|
|
3768
|
+
visitedAt: visitedAt.toISOString()
|
|
3769
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
// Recents are disposable: anything unexpected reads as an empty list (or, for
|
|
3772
|
+
// a single bad entry, a list without it) rather than throwing.
|
|
3773
|
+
function parseRecentlyVisited(value) {
|
|
3774
|
+
let parsed = null;
|
|
3775
|
+
try {
|
|
3776
|
+
parsed = JSON.parse(value !== null && value !== void 0 ? value : "null");
|
|
3777
|
+
} catch {
|
|
3778
|
+
parsed = null;
|
|
3779
|
+
}
|
|
3780
|
+
if (Array.isArray(parsed)) {
|
|
3781
|
+
return parsed.filter(isEntry);
|
|
3782
|
+
} else {
|
|
3783
|
+
return [];
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
function addVisit(entries, entry) {
|
|
3787
|
+
const key = entryKey(entry);
|
|
3788
|
+
return [entry, ...entries.filter(existing => entryKey(existing) !== key)].slice(0, MAX_ENTRIES);
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
// The app whose URL, in this environment, has the current page's host.
|
|
3792
|
+
// Reversing pcoUrl handles every environment @planningcenter/url knows about,
|
|
3793
|
+
// and guarantees the stored app rebuilds a working link.
|
|
3794
|
+
function currentApp() {
|
|
3795
|
+
var _a;
|
|
3796
|
+
return (_a = APPS.find(app => new URL(pcoUrl$1.pcoUrl(app)).hostname === window.location.hostname)) !== null && _a !== void 0 ? _a : null;
|
|
3797
|
+
}
|
|
3798
|
+
function isFilled(value) {
|
|
3799
|
+
return typeof value === "string" && value !== "";
|
|
3800
|
+
}
|
|
3801
|
+
// The tag's metadata is product-supplied JSON; only its string `type` and
|
|
3802
|
+
// `details` carry over, and an entry without either has no metadata at all.
|
|
3803
|
+
function metadataFromPage(metadata) {
|
|
3804
|
+
var _a;
|
|
3805
|
+
try {
|
|
3806
|
+
const {
|
|
3807
|
+
details,
|
|
3808
|
+
type
|
|
3809
|
+
} = (_a = JSON.parse(metadata !== null && metadata !== void 0 ? metadata : "{}")) !== null && _a !== void 0 ? _a : {};
|
|
3810
|
+
if (isFilled(details) || isFilled(type)) {
|
|
3811
|
+
return {
|
|
3812
|
+
...(isFilled(details) && {
|
|
3813
|
+
details
|
|
3814
|
+
}),
|
|
3815
|
+
...(isFilled(type) && {
|
|
3816
|
+
type
|
|
3817
|
+
})
|
|
3818
|
+
};
|
|
3819
|
+
} else {
|
|
3820
|
+
return undefined;
|
|
3821
|
+
}
|
|
3822
|
+
} catch {
|
|
3823
|
+
return undefined;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
// Staff impersonating someone browse under `/O<org id>/P<person id>/…`.
|
|
3827
|
+
function withoutImpersonation(segments) {
|
|
3828
|
+
const [org = "", person = ""] = segments;
|
|
3829
|
+
if (org.startsWith("O") && isId(org.slice(1)) && person.startsWith("P") && isId(person.slice(1))) {
|
|
3830
|
+
return segments.slice(2);
|
|
3831
|
+
} else {
|
|
3832
|
+
return segments;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
// Normalized the way home-mobile does it, so a page visited on web and on
|
|
3836
|
+
// mobile is one entry once the list syncs.
|
|
3837
|
+
function pathFromLocation({
|
|
3838
|
+
pathname,
|
|
3839
|
+
search
|
|
3840
|
+
}) {
|
|
3841
|
+
const segments = withoutImpersonation(pathname.split("/").filter(Boolean));
|
|
3842
|
+
if (segments.length === 0) {
|
|
3843
|
+
return "/";
|
|
3844
|
+
} else {
|
|
3845
|
+
return `/${segments.join("/")}${search.length > 1 ? search : ""}`;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
// pco:resource is the more specific signal, so it wins when both tags are
|
|
3849
|
+
// present. A resource that isn't a PCRL is ignored rather than recorded.
|
|
3850
|
+
function visitFromPage({
|
|
3851
|
+
metadata,
|
|
3852
|
+
resource,
|
|
3853
|
+
title
|
|
3854
|
+
}) {
|
|
3855
|
+
const app = currentApp();
|
|
3856
|
+
if (resource && isPcrl(resource)) {
|
|
3857
|
+
return {
|
|
3858
|
+
pcrl: resource,
|
|
3859
|
+
type: "resource"
|
|
3860
|
+
};
|
|
3861
|
+
} else if (title && title.trim() !== "" && app) {
|
|
3862
|
+
const pageMetadata = metadataFromPage(metadata);
|
|
3863
|
+
return {
|
|
3864
|
+
app,
|
|
3865
|
+
...(pageMetadata && {
|
|
3866
|
+
metadata: pageMetadata
|
|
3867
|
+
}),
|
|
3868
|
+
path: pathFromLocation(window.location),
|
|
3869
|
+
title,
|
|
3870
|
+
type: "page"
|
|
3871
|
+
};
|
|
3872
|
+
} else {
|
|
3873
|
+
return null;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
// The entry Recently Visited would record for the current page, or null when
|
|
3877
|
+
// the page isn't eligible. Shared by the recorder and the debug panel so the
|
|
3878
|
+
// panel always shows what actually gets stored.
|
|
3879
|
+
function useCurrentVisit() {
|
|
3880
|
+
const {
|
|
3881
|
+
recently_visitable: title,
|
|
3882
|
+
resource
|
|
3883
|
+
} = usePcoMeta();
|
|
3884
|
+
const metadata = useRecentlyVisitableMetadata();
|
|
3885
|
+
return React.useMemo(() => visitFromPage({
|
|
3886
|
+
metadata,
|
|
3887
|
+
resource,
|
|
3888
|
+
title
|
|
3889
|
+
}), [metadata, resource, title]);
|
|
3890
|
+
}
|
|
3891
|
+
// Products hand the person's ID to AppsProvider, not to the topbar itself,
|
|
3892
|
+
// and several pass it as a number.
|
|
3893
|
+
function useCurrentPersonId() {
|
|
3894
|
+
const {
|
|
3895
|
+
userId
|
|
3896
|
+
} = useStore();
|
|
3897
|
+
return userId == null ? undefined : String(userId);
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
// Per-origin localStorage is a stopgap: each product keeps its own list until
|
|
3901
|
+
// PCA-409 moves this to storage shared across product subdomains. Only this
|
|
3902
|
+
// file should need to change when it does.
|
|
3903
|
+
function storageKey({
|
|
3904
|
+
env,
|
|
3905
|
+
personId
|
|
3906
|
+
}) {
|
|
3907
|
+
return `topbar.recently_visited.${env}.${personId}`;
|
|
3908
|
+
}
|
|
3909
|
+
// localStorage doesn't notify the tab that wrote it, so readers in this tab
|
|
3910
|
+
// learn about new visits from this counter instead.
|
|
3911
|
+
const changes = createStore({
|
|
3912
|
+
version: 0
|
|
3913
|
+
});
|
|
3914
|
+
function readRecentlyVisited(scope) {
|
|
3915
|
+
try {
|
|
3916
|
+
return parseRecentlyVisited(localStorage.getItem(storageKey(scope)));
|
|
3917
|
+
} catch {
|
|
3918
|
+
return [];
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
function recordVisit(visit, scope) {
|
|
3922
|
+
const entry = toEntry(visit, new Date());
|
|
3923
|
+
if (isEntry(entry)) {
|
|
3924
|
+
try {
|
|
3925
|
+
localStorage.setItem(storageKey(scope), JSON.stringify(addVisit(readRecentlyVisited(scope), entry)));
|
|
3926
|
+
changes.setState({
|
|
3927
|
+
version: changes.getState().version + 1
|
|
3928
|
+
});
|
|
3929
|
+
} catch {
|
|
3930
|
+
// Storage can be full or blocked; recents are disposable.
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
function useRecentlyVisited({
|
|
3935
|
+
env,
|
|
3936
|
+
personId
|
|
3937
|
+
}) {
|
|
3938
|
+
const version = shim.useSyncExternalStore(changes.subscribe, () => changes.getState().version);
|
|
3939
|
+
// `version` isn't read, but listing it re-reads storage after each visit.
|
|
3940
|
+
return React.useMemo(() => personId ? readRecentlyVisited({
|
|
3941
|
+
env,
|
|
3942
|
+
personId
|
|
3943
|
+
}) : [], [env, personId, version]);
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
const pcoUrl = pcoUrl$1;
|
|
3947
|
+
|
|
3948
|
+
// Resolves resource entries with one pcrl_lookups request per product.
|
|
3949
|
+
// pcrl_lookups reports forbidden and deleted resources the same way, so both
|
|
3950
|
+
// become `unresolved`, as does every entry in a batch whose request failed.
|
|
3951
|
+
function useHydratedRecentlyVisited(entries, env) {
|
|
3952
|
+
const batches = React.useMemo(() => batchByProduct(entries), [entries]);
|
|
3953
|
+
const urls = React.useMemo(() => batches.map(([product, pcrls]) => pcrlLookupsUrl(product, pcrls)), [batches]);
|
|
3954
|
+
const requests = useCachedRequests(urls);
|
|
3955
|
+
const lookups = new Map();
|
|
3956
|
+
batches.forEach(([, pcrls], index) => {
|
|
3957
|
+
const request = requests[index];
|
|
3958
|
+
for (const pcrl of pcrls) lookups.set(pcrl, lookupFor(pcrl, request));
|
|
3613
3959
|
});
|
|
3614
|
-
|
|
3615
|
-
|
|
3960
|
+
return entries.map(entry => {
|
|
3961
|
+
var _a;
|
|
3962
|
+
if (entry.type === "page") {
|
|
3963
|
+
return {
|
|
3964
|
+
entry,
|
|
3965
|
+
status: "page",
|
|
3966
|
+
url: pageUrl(entry, env)
|
|
3967
|
+
};
|
|
3968
|
+
} else {
|
|
3969
|
+
const lookup = (_a = lookups.get(entry.pcrl)) !== null && _a !== void 0 ? _a : {
|
|
3970
|
+
status: "unresolved"
|
|
3971
|
+
};
|
|
3972
|
+
if (lookup.status === "resolved") {
|
|
3973
|
+
return {
|
|
3974
|
+
...lookup,
|
|
3975
|
+
entry,
|
|
3976
|
+
url: lookup.result.attributes.url
|
|
3977
|
+
};
|
|
3978
|
+
} else {
|
|
3979
|
+
return {
|
|
3980
|
+
entry,
|
|
3981
|
+
status: lookup.status
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
function lookupFor(pcrl, request) {
|
|
3988
|
+
if (request.status === "done") {
|
|
3989
|
+
const result = request.data.data.find(({
|
|
3990
|
+
attributes
|
|
3991
|
+
}) => attributes.pcrl === pcrl);
|
|
3992
|
+
return result ? {
|
|
3993
|
+
result,
|
|
3994
|
+
status: "resolved"
|
|
3995
|
+
} : {
|
|
3996
|
+
status: "unresolved"
|
|
3997
|
+
};
|
|
3998
|
+
} else if (request.status === "error") {
|
|
3999
|
+
return {
|
|
4000
|
+
status: "unresolved"
|
|
4001
|
+
};
|
|
4002
|
+
} else {
|
|
4003
|
+
return {
|
|
4004
|
+
status: "loading"
|
|
4005
|
+
};
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
const RECENTLY_VISITED_PAGE_SIZE = 10;
|
|
4009
|
+
// A PCRL can only be looked up through its own product's API, which may not be
|
|
4010
|
+
// the product it was visited in. Each page of entries is batched on its own,
|
|
4011
|
+
// so loading the next page leaves earlier pages' URLs, and so their cache
|
|
4012
|
+
// keys, alone. Sorting keeps them stable as a page reorders.
|
|
4013
|
+
function batchByProduct(entries) {
|
|
4014
|
+
var _a;
|
|
4015
|
+
const batches = [];
|
|
4016
|
+
for (let start = 0; start < entries.length; start += RECENTLY_VISITED_PAGE_SIZE) {
|
|
4017
|
+
const page = new Map();
|
|
4018
|
+
for (const entry of entries.slice(start, start + RECENTLY_VISITED_PAGE_SIZE)) {
|
|
4019
|
+
if (entry.type === "resource") {
|
|
4020
|
+
const [product] = entry.pcrl.split("/");
|
|
4021
|
+
page.set(product, [...((_a = page.get(product)) !== null && _a !== void 0 ? _a : []), entry.pcrl]);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
for (const [product, pcrls] of page) {
|
|
4025
|
+
batches.push([product, [...pcrls].sort()]);
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
return batches;
|
|
4029
|
+
}
|
|
4030
|
+
function pcrlLookupsUrl(product, pcrls) {
|
|
4031
|
+
const params = new URLSearchParams({
|
|
4032
|
+
"where[pcrl]": pcrls.join(",")
|
|
4033
|
+
});
|
|
4034
|
+
return `${window.location.origin}/~api/${product}/v2/pcrl_lookups?${params}`;
|
|
4035
|
+
}
|
|
4036
|
+
// Built from the app rather than the current origin, so it stays right once
|
|
4037
|
+
// PCA-409 shares one list across product subdomains.
|
|
4038
|
+
function pageUrl(entry, env) {
|
|
4039
|
+
return new URL(entry.path, pcoUrl(env)(entry.app)).href;
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
function useRecentVisits(env) {
|
|
4043
|
+
const personId = useCurrentPersonId();
|
|
4044
|
+
const entries = useRecentlyVisited({
|
|
4045
|
+
env,
|
|
4046
|
+
personId
|
|
4047
|
+
});
|
|
4048
|
+
const [count, setCount] = React.useState(RECENTLY_VISITED_PAGE_SIZE);
|
|
4049
|
+
const shown = React.useMemo(() => entries.slice(0, count), [entries, count]);
|
|
4050
|
+
const hydrated = useHydratedRecentlyVisited(shown, env);
|
|
4051
|
+
// Resources show up once they're found. Ones that can't be found anymore,
|
|
4052
|
+
// or that the person can no longer see, are left out.
|
|
4053
|
+
const visits = hydrated.filter(isShown);
|
|
4054
|
+
const loading = hydrated.some(({
|
|
4055
|
+
status
|
|
4056
|
+
}) => status === "loading");
|
|
4057
|
+
const showMore = React.useCallback(() => setCount(current => current + RECENTLY_VISITED_PAGE_SIZE), []);
|
|
4058
|
+
return {
|
|
4059
|
+
count,
|
|
4060
|
+
hasMore: entries.length > count,
|
|
4061
|
+
loading,
|
|
4062
|
+
showMore,
|
|
4063
|
+
visits
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
function isShown(visit) {
|
|
4067
|
+
return visit.status === "page" || visit.status === "resolved";
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
function open(url, {
|
|
4071
|
+
newTab
|
|
4072
|
+
}) {
|
|
4073
|
+
if (newTab) {
|
|
4074
|
+
window.open(url, "_blank", "noopener");
|
|
4075
|
+
} else {
|
|
4076
|
+
window.location.assign(url);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
// With text selected, ⌘C should copy that text as usual, not the link.
|
|
4080
|
+
function hasSelectedText(element) {
|
|
4081
|
+
var _a, _b;
|
|
4082
|
+
const selectedInField = element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
4083
|
+
return selectedInField || ((_b = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "") !== "";
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
const COPIED_FEEDBACK_MS = 1500;
|
|
4087
|
+
// `copiedUrl` stays set briefly after a copy so the UI can confirm it.
|
|
4088
|
+
function useCopyLink() {
|
|
3616
4089
|
const [copiedUrl, setCopiedUrl] = React.useState(null);
|
|
3617
4090
|
React.useEffect(() => {
|
|
3618
4091
|
if (copiedUrl) {
|
|
@@ -3622,201 +4095,721 @@ function useResultNavigation(results) {
|
|
|
3622
4095
|
return undefined;
|
|
3623
4096
|
}
|
|
3624
4097
|
}, [copiedUrl]);
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
4098
|
+
const copy = React.useCallback(url => {
|
|
4099
|
+
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
4100
|
+
}, []);
|
|
4101
|
+
return {
|
|
4102
|
+
copiedUrl,
|
|
4103
|
+
copy
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
// Remembering which list the index belongs to puts the highlight back on the
|
|
4108
|
+
// first item whenever a new list replaces it, without an effect to reset it.
|
|
4109
|
+
// A list that grows or fills in under the same `list` keeps its highlight.
|
|
4110
|
+
// Disabled, nothing is highlighted and the keys and pointer leave it alone.
|
|
4111
|
+
function useResultNavigation(items, list, enabled = true) {
|
|
4112
|
+
const [highlight, setHighlight] = React.useState({
|
|
4113
|
+
index: 0,
|
|
4114
|
+
list
|
|
4115
|
+
});
|
|
4116
|
+
const highlightedIndex = highlight.list === list ? highlight.index : 0;
|
|
4117
|
+
const highlighted = enabled ? items[highlightedIndex] : undefined;
|
|
4118
|
+
const {
|
|
4119
|
+
copiedUrl,
|
|
4120
|
+
copy
|
|
4121
|
+
} = useCopyLink();
|
|
4122
|
+
const highlightIndex = React.useCallback(index => {
|
|
4123
|
+
if (!enabled) return;
|
|
4124
|
+
setHighlight(current => current.list === list && current.index === index ? current : {
|
|
4125
|
+
index,
|
|
4126
|
+
list
|
|
4127
|
+
});
|
|
4128
|
+
}, [enabled, list]);
|
|
3629
4129
|
const handleKeyDown = event => {
|
|
4130
|
+
if (!enabled) return;
|
|
3630
4131
|
const step = verticalStep(event.key);
|
|
3631
|
-
if (step &&
|
|
4132
|
+
if (step && items.length > 0) {
|
|
3632
4133
|
event.preventDefault();
|
|
3633
|
-
highlightIndex((highlightedIndex + step +
|
|
4134
|
+
highlightIndex((highlightedIndex + step + items.length) % items.length);
|
|
3634
4135
|
} else if (event.key === "Enter" && highlighted && !event.nativeEvent.isComposing) {
|
|
3635
4136
|
event.preventDefault();
|
|
3636
|
-
open(highlighted.
|
|
4137
|
+
open(highlighted.url, {
|
|
3637
4138
|
newTab: hasCommandKey(event)
|
|
3638
4139
|
});
|
|
3639
4140
|
} else if (isCopyShortcut(event) && highlighted && !hasSelectedText(event.currentTarget)) {
|
|
3640
4141
|
event.preventDefault();
|
|
3641
|
-
|
|
3642
|
-
url
|
|
3643
|
-
} = highlighted.attributes;
|
|
3644
|
-
navigator.clipboard.writeText(url).then(() => setCopiedUrl(url));
|
|
4142
|
+
copy(highlighted.url);
|
|
3645
4143
|
}
|
|
3646
4144
|
};
|
|
3647
4145
|
return {
|
|
3648
|
-
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.
|
|
4146
|
+
copied: copiedUrl !== null && copiedUrl === (highlighted === null || highlighted === void 0 ? void 0 : highlighted.url),
|
|
3649
4147
|
handleKeyDown,
|
|
3650
4148
|
highlightIndex,
|
|
3651
4149
|
highlighted,
|
|
3652
4150
|
highlightedIndex: highlighted ? highlightedIndex : null
|
|
3653
4151
|
};
|
|
3654
4152
|
}
|
|
3655
|
-
|
|
3656
|
-
|
|
4153
|
+
|
|
4154
|
+
var styles$i = {"modal":"modal-NR3TO","mobile":"mobile-4e5rC","header":"header-c3SLe","visuallyHidden":"visuallyHidden-C28ju","panel":"panel-HlF7S"};
|
|
4155
|
+
|
|
4156
|
+
function groupSections(groups = []) {
|
|
4157
|
+
return groups.map(({
|
|
4158
|
+
results,
|
|
4159
|
+
type
|
|
4160
|
+
}) => ({
|
|
4161
|
+
heading: type,
|
|
4162
|
+
items: results.map(resultItem),
|
|
4163
|
+
key: type
|
|
4164
|
+
}));
|
|
4165
|
+
}
|
|
4166
|
+
function resultItem({
|
|
4167
|
+
attributes,
|
|
4168
|
+
id
|
|
3657
4169
|
}) {
|
|
3658
|
-
|
|
3659
|
-
|
|
4170
|
+
return {
|
|
4171
|
+
category: attributes.category,
|
|
4172
|
+
imageUrl: attributes.image_url,
|
|
4173
|
+
key: id,
|
|
4174
|
+
product: attributes.product,
|
|
4175
|
+
subtitle: summarize(attributes.metadata),
|
|
4176
|
+
title: attributes.title,
|
|
4177
|
+
url: attributes.url
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
function visitItem(visit) {
|
|
4181
|
+
return {
|
|
4182
|
+
key: entryKey(visit.entry),
|
|
4183
|
+
...describeVisit(visit)
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
function summarize(metadata) {
|
|
4187
|
+
return Object.values(metadata !== null && metadata !== void 0 ? metadata : {}).filter(value => typeof value === "string" && value !== "").join(" · ");
|
|
4188
|
+
}
|
|
4189
|
+
// Unlike search results, recents come from every product, so each one names
|
|
4190
|
+
// its product and type.
|
|
4191
|
+
function describeVisit(visit) {
|
|
4192
|
+
var _a, _b;
|
|
4193
|
+
const subtitle = (...parts) => parts.filter(Boolean).join(" · ");
|
|
4194
|
+
if (visit.status === "page") {
|
|
4195
|
+
const {
|
|
4196
|
+
app,
|
|
4197
|
+
metadata,
|
|
4198
|
+
title
|
|
4199
|
+
} = visit.entry;
|
|
4200
|
+
return {
|
|
4201
|
+
category: "generic",
|
|
4202
|
+
imageUrl: null,
|
|
4203
|
+
product: app,
|
|
4204
|
+
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 : ""),
|
|
4205
|
+
title,
|
|
4206
|
+
url: visit.url
|
|
4207
|
+
};
|
|
3660
4208
|
} else {
|
|
3661
|
-
|
|
4209
|
+
const {
|
|
4210
|
+
category,
|
|
4211
|
+
image_url,
|
|
4212
|
+
metadata,
|
|
4213
|
+
product,
|
|
4214
|
+
title,
|
|
4215
|
+
type
|
|
4216
|
+
} = visit.result.attributes;
|
|
4217
|
+
return {
|
|
4218
|
+
category,
|
|
4219
|
+
imageUrl: image_url,
|
|
4220
|
+
product,
|
|
4221
|
+
subtitle: subtitle(productName(product), type, summarize(metadata)),
|
|
4222
|
+
title,
|
|
4223
|
+
url: visit.url
|
|
4224
|
+
};
|
|
3662
4225
|
}
|
|
3663
4226
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
return
|
|
4227
|
+
// check-ins → Check-Ins
|
|
4228
|
+
function productName(slug) {
|
|
4229
|
+
return slug.split("-").map(capitalize).join("-");
|
|
4230
|
+
}
|
|
4231
|
+
function capitalize(word) {
|
|
4232
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
3670
4233
|
}
|
|
3671
4234
|
|
|
3672
|
-
const
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
4235
|
+
const STORAGE_KEY = "topbar.product_search.last_product";
|
|
4236
|
+
function readLastProduct() {
|
|
4237
|
+
try {
|
|
4238
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
4239
|
+
} catch {
|
|
4240
|
+
return null;
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
function saveLastProduct(slug) {
|
|
4244
|
+
try {
|
|
4245
|
+
localStorage.setItem(STORAGE_KEY, slug);
|
|
4246
|
+
} catch {
|
|
4247
|
+
// Storage can be full or blocked; search still works without it.
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
function initialProduct(products) {
|
|
4251
|
+
var _a, _b, _c;
|
|
4252
|
+
const lastProduct = readLastProduct();
|
|
4253
|
+
const bySlug = slug => products.find(product => product.slug === slug);
|
|
4254
|
+
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];
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
var styles$h = {"container":"container-YDdM1","hint":"hint-017Rd","keycap":"keycap-ENJM2","copied":"copied-DUFa1"};
|
|
4258
|
+
|
|
4259
|
+
const SYMBOLS = {
|
|
4260
|
+
command: {
|
|
4261
|
+
label: "⌘",
|
|
4262
|
+
paths: ["M7 9a2 2 0 1 1 2 -2v10a2 2 0 1 1 -2 -2h10a2 2 0 1 1 -2 2v-10a2 2 0 1 1 2 2h-10"]
|
|
4263
|
+
},
|
|
4264
|
+
down: {
|
|
4265
|
+
label: "↓",
|
|
4266
|
+
paths: ["M12 6l0 12", "M16 14l-4 4", "M8 14l4 4"]
|
|
4267
|
+
},
|
|
4268
|
+
return: {
|
|
4269
|
+
label: "↵",
|
|
4270
|
+
paths: ["M18 6v6a3 3 0 0 1 -3 3h-10l4 -4m0 8l-4 -4"]
|
|
4271
|
+
},
|
|
4272
|
+
up: {
|
|
4273
|
+
label: "↑",
|
|
4274
|
+
paths: ["M12 6l0 12", "M16 10l-4 -4", "M8 10l4 -4"]
|
|
4275
|
+
}
|
|
3690
4276
|
};
|
|
3691
|
-
function
|
|
3692
|
-
|
|
3693
|
-
|
|
4277
|
+
function Keycap(props) {
|
|
4278
|
+
return /*#__PURE__*/React.createElement("kbd", {
|
|
4279
|
+
className: styles$h.keycap
|
|
4280
|
+
}, "symbol" in props ? (/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("svg", {
|
|
4281
|
+
"aria-hidden": true,
|
|
4282
|
+
fill: "none",
|
|
4283
|
+
stroke: "currentColor",
|
|
4284
|
+
strokeLinecap: "round",
|
|
4285
|
+
strokeLinejoin: "round",
|
|
4286
|
+
viewBox: "0 0 24 24"
|
|
4287
|
+
}, SYMBOLS[props.symbol].paths.map(d => (/*#__PURE__*/React.createElement("path", {
|
|
4288
|
+
d: d,
|
|
4289
|
+
key: d
|
|
4290
|
+
})))), /*#__PURE__*/React.createElement("span", {
|
|
4291
|
+
className: styles$i.visuallyHidden
|
|
4292
|
+
}, SYMBOLS[props.symbol].label))) : props.label);
|
|
4293
|
+
}
|
|
4294
|
+
function CommandKey() {
|
|
4295
|
+
return isMac() ? /*#__PURE__*/React.createElement(Keycap, {
|
|
4296
|
+
symbol: "command"
|
|
4297
|
+
}) : /*#__PURE__*/React.createElement(Keycap, {
|
|
4298
|
+
label: "Ctrl"
|
|
3694
4299
|
});
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
4300
|
+
}
|
|
4301
|
+
function EnterKey() {
|
|
4302
|
+
return isMac() ? /*#__PURE__*/React.createElement(Keycap, {
|
|
4303
|
+
symbol: "return"
|
|
4304
|
+
}) : /*#__PURE__*/React.createElement(Keycap, {
|
|
4305
|
+
label: "Enter"
|
|
3701
4306
|
});
|
|
3702
4307
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
4308
|
+
const {
|
|
4309
|
+
copyLink,
|
|
4310
|
+
openInNewTab,
|
|
4311
|
+
openSearch,
|
|
4312
|
+
select
|
|
4313
|
+
} = KEYBOARD_SHORTCUTS;
|
|
4314
|
+
function KeyboardHints({
|
|
4315
|
+
copied
|
|
4316
|
+
}) {
|
|
4317
|
+
return /*#__PURE__*/React.createElement("ul", {
|
|
4318
|
+
"aria-label": "Keyboard shortcuts",
|
|
4319
|
+
className: styles$h.container
|
|
4320
|
+
}, /*#__PURE__*/React.createElement("li", {
|
|
4321
|
+
className: styles$h.hint
|
|
4322
|
+
}, /*#__PURE__*/React.createElement(tapestry.Flex, {
|
|
4323
|
+
gap: 0.5
|
|
4324
|
+
}, select.modifierKey, /*#__PURE__*/React.createElement(Keycap, {
|
|
4325
|
+
symbol: "up"
|
|
4326
|
+
}), /*#__PURE__*/React.createElement(Keycap, {
|
|
4327
|
+
symbol: "down"
|
|
4328
|
+
})), select.label), /*#__PURE__*/React.createElement("li", {
|
|
4329
|
+
className: styles$h.hint
|
|
4330
|
+
}, /*#__PURE__*/React.createElement(tapestry.Flex, {
|
|
4331
|
+
gap: 0.5
|
|
4332
|
+
}, /*#__PURE__*/React.createElement(CommandKey, null), /*#__PURE__*/React.createElement(EnterKey, null)), openInNewTab.label), /*#__PURE__*/React.createElement("li", {
|
|
4333
|
+
className: classnames(styles$h.hint, copied && styles$h.copied)
|
|
4334
|
+
}, /*#__PURE__*/React.createElement(tapestry.Flex, {
|
|
4335
|
+
gap: 0.5
|
|
4336
|
+
}, /*#__PURE__*/React.createElement(CommandKey, null), /*#__PURE__*/React.createElement(Keycap, {
|
|
4337
|
+
label: copyLink.key.toUpperCase()
|
|
4338
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
4339
|
+
role: "status"
|
|
4340
|
+
}, copied ? "Copied!" : copyLink.label)), /*#__PURE__*/React.createElement("li", {
|
|
4341
|
+
className: styles$h.hint
|
|
4342
|
+
}, /*#__PURE__*/React.createElement(tapestry.Flex, {
|
|
4343
|
+
gap: 0.5
|
|
4344
|
+
}, /*#__PURE__*/React.createElement(CommandKey, null), /*#__PURE__*/React.createElement(Keycap, {
|
|
4345
|
+
label: openSearch.key.toUpperCase()
|
|
4346
|
+
})), openSearch.label));
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
var styles$g = {"icon":"icon-XKLvN","squircle":"squircle-JCHr6"};
|
|
4350
|
+
|
|
4351
|
+
const PRODUCTS = {
|
|
4352
|
+
accounts: {
|
|
4353
|
+
gradient: "--t-fill-color-product-accounts-gradient-brand",
|
|
4354
|
+
path: logomarks__namespace.accounts
|
|
4355
|
+
},
|
|
4356
|
+
api: {
|
|
4357
|
+
gradient: "--t-fill-color-product-api-gradient-brand",
|
|
4358
|
+
path: logomarks__namespace.api
|
|
4359
|
+
},
|
|
4360
|
+
calendar: {
|
|
4361
|
+
gradient: "--t-fill-color-product-calendar-gradient-brand",
|
|
4362
|
+
path: logomarks__namespace.calendar
|
|
4363
|
+
},
|
|
4364
|
+
"check-ins": {
|
|
4365
|
+
gradient: "--t-fill-color-product-checkins-gradient-brand",
|
|
4366
|
+
path: logomarks__namespace.checkIns
|
|
4367
|
+
},
|
|
4368
|
+
"church-center": {
|
|
4369
|
+
gradient: "--t-fill-color-product-cc-gradient-brand",
|
|
4370
|
+
path: logomarks__namespace.churchCenter
|
|
4371
|
+
},
|
|
4372
|
+
giving: {
|
|
4373
|
+
gradient: "--t-fill-color-product-giving-gradient-brand",
|
|
4374
|
+
path: logomarks__namespace.giving
|
|
4375
|
+
},
|
|
4376
|
+
groups: {
|
|
4377
|
+
gradient: "--t-fill-color-product-groups-gradient-brand",
|
|
4378
|
+
path: logomarks__namespace.groups
|
|
4379
|
+
},
|
|
4380
|
+
home: {
|
|
4381
|
+
gradient: "--t-fill-color-product-home-gradient-brand",
|
|
4382
|
+
path: logomarks__namespace.home
|
|
4383
|
+
},
|
|
4384
|
+
"music-stand": {
|
|
4385
|
+
gradient: "--t-fill-color-product-musicstand-gradient-brand",
|
|
4386
|
+
path: logomarks__namespace.musicStand
|
|
4387
|
+
},
|
|
4388
|
+
people: {
|
|
4389
|
+
gradient: "--t-fill-color-product-people-gradient-brand",
|
|
4390
|
+
path: logomarks__namespace.people
|
|
4391
|
+
},
|
|
4392
|
+
publishing: {
|
|
4393
|
+
gradient: "--t-fill-color-product-publishing-gradient-brand",
|
|
4394
|
+
path: logomarks__namespace.publishing
|
|
4395
|
+
},
|
|
4396
|
+
registrations: {
|
|
4397
|
+
gradient: "--t-fill-color-product-registrations-gradient-brand",
|
|
4398
|
+
path: logomarks__namespace.registrations
|
|
4399
|
+
},
|
|
4400
|
+
services: {
|
|
4401
|
+
gradient: "--t-fill-color-product-services-gradient-brand",
|
|
4402
|
+
path: logomarks__namespace.services
|
|
3720
4403
|
}
|
|
4404
|
+
};
|
|
4405
|
+
const isProductIconSlug = slug => Object.prototype.hasOwnProperty.call(PRODUCTS, slug);
|
|
4406
|
+
const maskFor = path => `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="${path}"/></svg>`)}")`;
|
|
4407
|
+
function ProductIcon({
|
|
4408
|
+
size = 16,
|
|
4409
|
+
slug
|
|
4410
|
+
}) {
|
|
4411
|
+
const {
|
|
4412
|
+
gradient,
|
|
4413
|
+
path
|
|
4414
|
+
} = PRODUCTS[slug];
|
|
4415
|
+
const mask = maskFor(path);
|
|
4416
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
4417
|
+
"aria-hidden": true,
|
|
4418
|
+
className: styles$g.icon,
|
|
4419
|
+
style: {
|
|
4420
|
+
height: size,
|
|
4421
|
+
width: size
|
|
4422
|
+
}
|
|
4423
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
4424
|
+
className: styles$g.squircle,
|
|
4425
|
+
style: {
|
|
4426
|
+
WebkitMaskImage: mask,
|
|
4427
|
+
background: `var(${gradient})`,
|
|
4428
|
+
maskImage: mask
|
|
4429
|
+
}
|
|
4430
|
+
}));
|
|
3721
4431
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
4432
|
+
|
|
4433
|
+
var styles$f = {"picker":"picker-iuzzj","option":"option--7LXg"};
|
|
4434
|
+
|
|
4435
|
+
// Phones have no room for a tab per product, so they pick from a dropdown.
|
|
4436
|
+
// Complex mode renders each option's logomark, in the list and the trigger.
|
|
4437
|
+
function ProductSelect({
|
|
4438
|
+
children,
|
|
4439
|
+
onSelect,
|
|
4440
|
+
products,
|
|
4441
|
+
selected
|
|
4442
|
+
}) {
|
|
4443
|
+
const options = products.map(({
|
|
4444
|
+
appName,
|
|
3726
4445
|
slug
|
|
3727
|
-
}) =>
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
4446
|
+
}) => ({
|
|
4447
|
+
label: (/*#__PURE__*/React.createElement("span", {
|
|
4448
|
+
className: styles$f.option
|
|
4449
|
+
}, /*#__PURE__*/React.createElement(ProductIcon, {
|
|
4450
|
+
slug: slug
|
|
4451
|
+
}), appName)),
|
|
4452
|
+
textValue: appName,
|
|
4453
|
+
value: slug
|
|
4454
|
+
}));
|
|
4455
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
4456
|
+
className: styles$f.picker
|
|
4457
|
+
}, /*#__PURE__*/React.createElement(tapestry.Select, {
|
|
4458
|
+
"aria-label": "Product",
|
|
4459
|
+
complex: true,
|
|
4460
|
+
onChange: event => onSelect(event.target.value),
|
|
4461
|
+
options: options,
|
|
4462
|
+
value: selected.slug
|
|
4463
|
+
})), /*#__PURE__*/React.createElement("div", {
|
|
4464
|
+
className: styles$i.panel
|
|
4465
|
+
}, children));
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
var styles$e = {"tablist":"tablist-7ASvc","tab":"tab-dz8ki"};
|
|
4469
|
+
|
|
4470
|
+
function ProductTabs({
|
|
4471
|
+
children,
|
|
4472
|
+
onKeyDown,
|
|
4473
|
+
onSelect,
|
|
4474
|
+
products,
|
|
4475
|
+
selected
|
|
4476
|
+
}) {
|
|
4477
|
+
const id = useInstanceId("product-tabs");
|
|
4478
|
+
const tabId = slug => `${id}-tab-${slug}`;
|
|
4479
|
+
const tabRefs = React.useRef(new Map());
|
|
4480
|
+
const handleKeyDown = event => {
|
|
4481
|
+
var _a;
|
|
4482
|
+
const offset = horizontalStep(event.key);
|
|
4483
|
+
if (offset) {
|
|
4484
|
+
event.preventDefault();
|
|
4485
|
+
const index = products.indexOf(selected);
|
|
4486
|
+
const next = products[(index + offset + products.length) % products.length];
|
|
4487
|
+
onSelect(next.slug);
|
|
4488
|
+
(_a = tabRefs.current.get(next.slug)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4489
|
+
} else {
|
|
4490
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
4491
|
+
}
|
|
4492
|
+
};
|
|
4493
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
4494
|
+
"aria-label": "Product",
|
|
4495
|
+
className: styles$e.tablist,
|
|
4496
|
+
onKeyDown: handleKeyDown,
|
|
4497
|
+
role: "tablist"
|
|
4498
|
+
}, products.map(product => (/*#__PURE__*/React.createElement("button", {
|
|
4499
|
+
"aria-controls": `${id}-panel`,
|
|
4500
|
+
"aria-selected": product === selected,
|
|
4501
|
+
className: styles$e.tab,
|
|
4502
|
+
id: tabId(product.slug),
|
|
4503
|
+
key: product.slug,
|
|
4504
|
+
onClick: () => onSelect(product.slug),
|
|
4505
|
+
ref: element => {
|
|
4506
|
+
if (element) {
|
|
4507
|
+
tabRefs.current.set(product.slug, element);
|
|
4508
|
+
} else {
|
|
4509
|
+
tabRefs.current.delete(product.slug);
|
|
3740
4510
|
}
|
|
4511
|
+
},
|
|
4512
|
+
role: "tab",
|
|
4513
|
+
tabIndex: product === selected ? 0 : -1,
|
|
4514
|
+
type: "button"
|
|
4515
|
+
}, /*#__PURE__*/React.createElement(ProductIcon, {
|
|
4516
|
+
slug: product.slug
|
|
4517
|
+
}), product === selected ? product.appName : (/*#__PURE__*/React.createElement("span", {
|
|
4518
|
+
className: styles$i.visuallyHidden
|
|
4519
|
+
}, product.appName)))))), /*#__PURE__*/React.createElement("div", {
|
|
4520
|
+
"aria-labelledby": tabId(selected.slug),
|
|
4521
|
+
className: styles$i.panel,
|
|
4522
|
+
id: `${id}-panel`,
|
|
4523
|
+
role: "tabpanel"
|
|
4524
|
+
}, children));
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
function EndOfResultsObserver({
|
|
4528
|
+
onVisible
|
|
4529
|
+
}) {
|
|
4530
|
+
const ref = React.useRef(null);
|
|
4531
|
+
React.useEffect(() => {
|
|
4532
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
4533
|
+
if (entry.isIntersecting) onVisible();
|
|
3741
4534
|
});
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
products: settled.products,
|
|
3749
|
-
status: "done"
|
|
3750
|
-
};
|
|
3751
|
-
} else {
|
|
3752
|
-
return {
|
|
3753
|
-
status: "loading"
|
|
3754
|
-
};
|
|
3755
|
-
}
|
|
4535
|
+
if (ref.current) observer.observe(ref.current);
|
|
4536
|
+
return () => observer.disconnect();
|
|
4537
|
+
}, [onVisible]);
|
|
4538
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
4539
|
+
ref: ref
|
|
4540
|
+
});
|
|
3756
4541
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
4542
|
+
|
|
4543
|
+
const resultId = (listboxId, index) => `${listboxId}-result-${index}`;
|
|
4544
|
+
|
|
4545
|
+
var styles$d = {"result":"result-Thq2C","title":"title-KYUEn","subtitle":"subtitle-R2haq","image":"image-KVDor","avatar":"avatar-6Gh62","photo":"photo-FZR3Q","badge":"badge-trUKt"};
|
|
4546
|
+
|
|
4547
|
+
function SearchResult({
|
|
4548
|
+
category,
|
|
4549
|
+
highlighted,
|
|
4550
|
+
id,
|
|
4551
|
+
imageUrl,
|
|
4552
|
+
onHighlight,
|
|
4553
|
+
product,
|
|
4554
|
+
subtitle,
|
|
4555
|
+
title,
|
|
4556
|
+
url
|
|
4557
|
+
}) {
|
|
4558
|
+
const ref = React.useRef(null);
|
|
4559
|
+
// A hovered result is already under the pointer, and scrolling it clear of
|
|
4560
|
+
// the panel's scroll padding would slide the list out from under the mouse.
|
|
4561
|
+
const highlightedByPointer = React.useRef(false);
|
|
4562
|
+
React.useEffect(() => {
|
|
4563
|
+
var _a;
|
|
4564
|
+
if (!highlighted) return;
|
|
4565
|
+
if (highlightedByPointer.current) {
|
|
4566
|
+
highlightedByPointer.current = false;
|
|
4567
|
+
} else {
|
|
4568
|
+
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
4569
|
+
block: "nearest"
|
|
4570
|
+
});
|
|
4571
|
+
}
|
|
4572
|
+
}, [highlighted]);
|
|
4573
|
+
const highlightFromPointer = () => {
|
|
4574
|
+
if (highlighted) return;
|
|
4575
|
+
highlightedByPointer.current = true;
|
|
4576
|
+
onHighlight();
|
|
4577
|
+
};
|
|
4578
|
+
// Focus stays in the search input, which points at the highlighted result
|
|
4579
|
+
// with aria-activedescendant, so results are kept out of the tab order.
|
|
4580
|
+
// Mouse move rather than enter: arrowing scrolls results under a resting
|
|
4581
|
+
// pointer, and that shouldn't steal the highlight.
|
|
4582
|
+
return /*#__PURE__*/React.createElement("a", {
|
|
4583
|
+
"aria-selected": highlighted,
|
|
4584
|
+
className: styles$d.result,
|
|
4585
|
+
href: url,
|
|
4586
|
+
id: id,
|
|
4587
|
+
onMouseMove: highlightFromPointer,
|
|
4588
|
+
ref: ref,
|
|
4589
|
+
role: "option",
|
|
4590
|
+
tabIndex: -1
|
|
4591
|
+
}, /*#__PURE__*/React.createElement(ResultImage, {
|
|
4592
|
+
category: category,
|
|
4593
|
+
imageUrl: imageUrl,
|
|
4594
|
+
product: product
|
|
4595
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
4596
|
+
className: styles$d.title
|
|
4597
|
+
}, title), /*#__PURE__*/React.createElement("span", {
|
|
4598
|
+
className: styles$d.subtitle
|
|
4599
|
+
}, subtitle));
|
|
4600
|
+
}
|
|
4601
|
+
// A person's photo or the result's own image, badged with its product, or
|
|
4602
|
+
// just the product's logomark when there's no image.
|
|
4603
|
+
function ResultImage({
|
|
4604
|
+
category,
|
|
4605
|
+
imageUrl,
|
|
4606
|
+
product
|
|
4607
|
+
}) {
|
|
4608
|
+
const icon = size => isProductIconSlug(product) ? (/*#__PURE__*/React.createElement(ProductIcon, {
|
|
4609
|
+
size: size,
|
|
4610
|
+
slug: product
|
|
4611
|
+
})) : null;
|
|
4612
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
4613
|
+
"aria-hidden": true,
|
|
4614
|
+
className: styles$d.image
|
|
4615
|
+
}, imageUrl ? (/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("img", {
|
|
4616
|
+
alt: "",
|
|
4617
|
+
className: category === "profile" ? styles$d.avatar : styles$d.photo,
|
|
4618
|
+
loading: "lazy",
|
|
4619
|
+
src: imageUrl
|
|
4620
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
4621
|
+
className: styles$d.badge
|
|
4622
|
+
}, icon(16)))) : icon("100%"));
|
|
3759
4623
|
}
|
|
3760
4624
|
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
4625
|
+
var styles$c = {"message":"message-1-PP-","heading":"heading-LioUF"};
|
|
4626
|
+
|
|
4627
|
+
// Sections with a heading render as labelled groups; ones without render
|
|
4628
|
+
// their items straight into the listbox. Indexes run across every section.
|
|
4629
|
+
function ResultListbox({
|
|
4630
|
+
busy,
|
|
4631
|
+
highlightedIndex,
|
|
4632
|
+
id,
|
|
4633
|
+
label,
|
|
4634
|
+
labelledBy,
|
|
4635
|
+
onHighlight,
|
|
4636
|
+
sections
|
|
3768
4637
|
}) {
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
4638
|
+
let index = 0;
|
|
4639
|
+
const renderItem = item => {
|
|
4640
|
+
const itemIndex = index++;
|
|
4641
|
+
return /*#__PURE__*/React.createElement(SearchResult, {
|
|
4642
|
+
category: item.category,
|
|
4643
|
+
highlighted: itemIndex === highlightedIndex,
|
|
4644
|
+
id: resultId(id, itemIndex),
|
|
4645
|
+
imageUrl: item.imageUrl,
|
|
4646
|
+
key: item.key,
|
|
4647
|
+
onHighlight: () => onHighlight(itemIndex),
|
|
4648
|
+
product: item.product,
|
|
4649
|
+
subtitle: item.subtitle,
|
|
4650
|
+
title: item.title,
|
|
4651
|
+
url: item.url
|
|
3773
4652
|
});
|
|
4653
|
+
};
|
|
4654
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
4655
|
+
"aria-busy": busy,
|
|
4656
|
+
"aria-label": label,
|
|
4657
|
+
"aria-labelledby": labelledBy,
|
|
4658
|
+
id: id,
|
|
4659
|
+
role: "listbox"
|
|
4660
|
+
}, sections.map((section, sectionIndex) => {
|
|
4661
|
+
if (section.heading === undefined) {
|
|
4662
|
+
return /*#__PURE__*/React.createElement(React.Fragment, {
|
|
4663
|
+
key: section.key
|
|
4664
|
+
}, section.items.map(renderItem));
|
|
4665
|
+
}
|
|
4666
|
+
const headingId = `${id}-group-${sectionIndex}`;
|
|
4667
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
4668
|
+
"aria-labelledby": headingId,
|
|
4669
|
+
key: section.key,
|
|
4670
|
+
role: "group"
|
|
4671
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
4672
|
+
className: styles$c.heading,
|
|
4673
|
+
id: headingId
|
|
4674
|
+
}, section.heading), section.items.map(renderItem));
|
|
4675
|
+
}));
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
function RecentlyVisited({
|
|
4679
|
+
count,
|
|
4680
|
+
hasMore,
|
|
4681
|
+
items,
|
|
4682
|
+
loading,
|
|
4683
|
+
showMore,
|
|
4684
|
+
...navigation
|
|
4685
|
+
}) {
|
|
4686
|
+
const headingId = `${navigation.id}-heading`;
|
|
4687
|
+
if (items.length === 0 && !hasMore && !loading) {
|
|
4688
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4689
|
+
className: styles$c.message
|
|
4690
|
+
}, "Pages you visit will show up here.");
|
|
3774
4691
|
} else {
|
|
3775
|
-
return null
|
|
4692
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
|
4693
|
+
className: styles$c.heading,
|
|
4694
|
+
id: headingId
|
|
4695
|
+
}, "Recently visited"), /*#__PURE__*/React.createElement(ResultListbox, {
|
|
4696
|
+
labelledBy: headingId,
|
|
4697
|
+
sections: [{
|
|
4698
|
+
items,
|
|
4699
|
+
key: "recents"
|
|
4700
|
+
}],
|
|
4701
|
+
...navigation
|
|
4702
|
+
}), hasMore && /*#__PURE__*/React.createElement(EndOfResultsObserver, {
|
|
4703
|
+
key: count,
|
|
4704
|
+
onVisible: showMore
|
|
4705
|
+
}));
|
|
3776
4706
|
}
|
|
3777
4707
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
4708
|
+
|
|
4709
|
+
var styles$b = {"header":"header-sXEyG","bordered":"bordered-mWkCy","input":"input-YQkTC","clearButton":"clearButton-9Yh9w"};
|
|
4710
|
+
|
|
4711
|
+
// A combobox: focus stays here while aria-activedescendant points screen
|
|
4712
|
+
// readers at the highlighted result in the listbox it controls.
|
|
4713
|
+
function SearchInput({
|
|
4714
|
+
bordered = false,
|
|
4715
|
+
expanded,
|
|
4716
|
+
highlightedIndex,
|
|
4717
|
+
inputRef,
|
|
4718
|
+
listboxId,
|
|
4719
|
+
onChange,
|
|
4720
|
+
onFocus,
|
|
4721
|
+
onKeyDown,
|
|
4722
|
+
value
|
|
3780
4723
|
}) {
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
}
|
|
3795
|
-
};
|
|
3796
|
-
window.addEventListener("keydown", handleKeyDown, {
|
|
3797
|
-
capture: true
|
|
3798
|
-
});
|
|
3799
|
-
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
3800
|
-
capture: true
|
|
3801
|
-
});
|
|
3802
|
-
}, []);
|
|
3803
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
|
3804
|
-
"aria-keyshortcuts": `${isMac() ? "Meta+K" : "Control+K"} /`,
|
|
4724
|
+
return /*#__PURE__*/React.createElement("label", {
|
|
4725
|
+
className: classnames(styles$b.header, {
|
|
4726
|
+
[styles$b.bordered]: bordered
|
|
4727
|
+
})
|
|
4728
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
4729
|
+
name: "general.search",
|
|
4730
|
+
size: 20,
|
|
4731
|
+
theme: "--t-icon-color-dim"
|
|
4732
|
+
}), /*#__PURE__*/React.createElement("input", {
|
|
4733
|
+
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
4734
|
+
"aria-autocomplete": "list",
|
|
4735
|
+
"aria-controls": expanded ? listboxId : undefined,
|
|
4736
|
+
"aria-expanded": expanded,
|
|
3805
4737
|
"aria-label": "Search",
|
|
3806
|
-
|
|
4738
|
+
autoFocus: true,
|
|
4739
|
+
className: styles$b.input,
|
|
4740
|
+
onChange: event => onChange(event.target.value),
|
|
4741
|
+
onFocus: onFocus,
|
|
4742
|
+
onKeyDown: onKeyDown,
|
|
4743
|
+
placeholder: "Search",
|
|
4744
|
+
ref: inputRef,
|
|
4745
|
+
role: "combobox",
|
|
4746
|
+
type: "search",
|
|
4747
|
+
value: value
|
|
4748
|
+
}), value !== "" && (/*#__PURE__*/React.createElement("button", {
|
|
4749
|
+
"aria-label": "Clear search",
|
|
4750
|
+
className: styles$b.clearButton,
|
|
4751
|
+
onClick: () => {
|
|
4752
|
+
var _a;
|
|
4753
|
+
onChange("");
|
|
4754
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
4755
|
+
},
|
|
4756
|
+
tabIndex: -1,
|
|
3807
4757
|
type: "button"
|
|
3808
|
-
}, /*#__PURE__*/React.createElement(
|
|
3809
|
-
|
|
3810
|
-
}))
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
4758
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
4759
|
+
name: "general.smallX"
|
|
4760
|
+
}))));
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
function SearchResults({
|
|
4764
|
+
label,
|
|
4765
|
+
sections,
|
|
4766
|
+
status,
|
|
4767
|
+
term,
|
|
4768
|
+
...navigation
|
|
4769
|
+
}) {
|
|
4770
|
+
switch (status) {
|
|
4771
|
+
case "idle":
|
|
4772
|
+
return null;
|
|
4773
|
+
case "loading":
|
|
4774
|
+
if (sections.length > 0) {
|
|
4775
|
+
return /*#__PURE__*/React.createElement(ResultListbox, {
|
|
4776
|
+
busy: true,
|
|
4777
|
+
label: label,
|
|
4778
|
+
sections: sections,
|
|
4779
|
+
...navigation
|
|
4780
|
+
});
|
|
4781
|
+
}
|
|
4782
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4783
|
+
className: styles$c.message,
|
|
4784
|
+
role: "status"
|
|
4785
|
+
}, "Searching\u2026");
|
|
4786
|
+
case "error":
|
|
4787
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4788
|
+
className: styles$c.message,
|
|
4789
|
+
role: "alert"
|
|
4790
|
+
}, "Search isn\u2019t working right now. Try again.");
|
|
4791
|
+
case "done":
|
|
4792
|
+
if (sections.length === 0) {
|
|
4793
|
+
return /*#__PURE__*/React.createElement("p", {
|
|
4794
|
+
className: styles$c.message,
|
|
4795
|
+
role: "status"
|
|
4796
|
+
}, "No results for \u201C", term.trim(), "\u201D");
|
|
4797
|
+
}
|
|
4798
|
+
return /*#__PURE__*/React.createElement(ResultListbox, {
|
|
4799
|
+
busy: false,
|
|
4800
|
+
label: label,
|
|
4801
|
+
sections: sections,
|
|
4802
|
+
...navigation
|
|
4803
|
+
});
|
|
4804
|
+
}
|
|
3818
4805
|
}
|
|
4806
|
+
|
|
4807
|
+
// Keys the recents list for navigation, so the highlight
|
|
4808
|
+
// stays put as more recents load and as their lookups answer.
|
|
4809
|
+
const RECENTS = "recents";
|
|
3819
4810
|
function SearchPanel({
|
|
4811
|
+
mobile = false,
|
|
4812
|
+
env,
|
|
3820
4813
|
products
|
|
3821
4814
|
}) {
|
|
3822
4815
|
var _a, _b;
|
|
@@ -3829,11 +4822,17 @@ function SearchPanel({
|
|
|
3829
4822
|
const hasTerm = term.trim() !== "";
|
|
3830
4823
|
const search = useProductSearch(product.slug, term);
|
|
3831
4824
|
const groups = "groups" in search ? search.groups : undefined;
|
|
3832
|
-
const
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
4825
|
+
const sections = React.useMemo(() => groupSections(groups), [groups]);
|
|
4826
|
+
const {
|
|
4827
|
+
visits,
|
|
4828
|
+
...recents
|
|
4829
|
+
} = useRecentVisits(env);
|
|
4830
|
+
const recentItems = visits.map(visitItem);
|
|
4831
|
+
const items = hasTerm ? sections.flatMap(section => section.items) : recentItems;
|
|
4832
|
+
// Touch-only devices open results by tapping them, with no highlight to
|
|
4833
|
+
// arrow through. A narrow window with a keyboard and mouse keeps both.
|
|
4834
|
+
const touchOnly = useMediaQuery("(hover: none) and (pointer: coarse)");
|
|
4835
|
+
const navigation = useResultNavigation(items, hasTerm ? sections : RECENTS, !touchOnly);
|
|
3837
4836
|
const {
|
|
3838
4837
|
highlightedIndex
|
|
3839
4838
|
} = navigation;
|
|
@@ -3856,296 +4855,273 @@ function SearchPanel({
|
|
|
3856
4855
|
React.useEffect(() => {
|
|
3857
4856
|
if (hasTerm) saveLastProduct(product.slug);
|
|
3858
4857
|
}, [hasTerm, product.slug]);
|
|
3859
|
-
|
|
3860
|
-
"aria-activedescendant": highlightedIndex === null ? undefined : resultId(listboxId, highlightedIndex),
|
|
3861
|
-
"aria-autocomplete": "list",
|
|
3862
|
-
"aria-controls": results.length > 0 ? listboxId : undefined,
|
|
3863
|
-
"aria-expanded": results.length > 0,
|
|
3864
|
-
"aria-label": "Search",
|
|
3865
|
-
autoFocus: true,
|
|
3866
|
-
onChange: event => setTerm(event.target.value),
|
|
3867
|
-
onFocus: () => setAnnounceHighlight(false),
|
|
3868
|
-
onKeyDown: navigation.handleKeyDown,
|
|
3869
|
-
placeholder: "Search",
|
|
3870
|
-
ref: inputRef,
|
|
3871
|
-
role: "combobox",
|
|
3872
|
-
type: "search",
|
|
3873
|
-
value: term
|
|
3874
|
-
}), /*#__PURE__*/React.createElement(ProductTabs, {
|
|
3875
|
-
onKeyDown: handleTabsKeyDown,
|
|
3876
|
-
onSelect: setSelectedSlug,
|
|
3877
|
-
products: products,
|
|
3878
|
-
selected: product
|
|
3879
|
-
}, /*#__PURE__*/React.createElement(SearchResults, {
|
|
4858
|
+
const results = hasTerm ? (/*#__PURE__*/React.createElement(SearchResults, {
|
|
3880
4859
|
highlightedIndex: highlightedIndex,
|
|
3881
4860
|
id: listboxId,
|
|
3882
4861
|
label: `${product.appName} results`,
|
|
3883
4862
|
onHighlight: navigation.highlightIndex,
|
|
3884
|
-
|
|
4863
|
+
sections: sections,
|
|
4864
|
+
status: search.status,
|
|
3885
4865
|
term: term
|
|
3886
|
-
}))
|
|
4866
|
+
})) : (/*#__PURE__*/React.createElement(RecentlyVisited, {
|
|
4867
|
+
highlightedIndex: highlightedIndex,
|
|
4868
|
+
id: listboxId,
|
|
4869
|
+
items: recentItems,
|
|
4870
|
+
onHighlight: navigation.highlightIndex,
|
|
4871
|
+
...recents
|
|
4872
|
+
}));
|
|
4873
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SearchInput, {
|
|
4874
|
+
bordered: mobile,
|
|
4875
|
+
expanded: items.length > 0,
|
|
4876
|
+
highlightedIndex: highlightedIndex,
|
|
4877
|
+
inputRef: inputRef,
|
|
4878
|
+
listboxId: listboxId,
|
|
4879
|
+
onChange: setTerm,
|
|
4880
|
+
onFocus: () => setAnnounceHighlight(false),
|
|
4881
|
+
onKeyDown: navigation.handleKeyDown,
|
|
4882
|
+
value: term
|
|
4883
|
+
}), mobile ? (/*#__PURE__*/React.createElement(ProductSelect, {
|
|
4884
|
+
onSelect: setSelectedSlug,
|
|
4885
|
+
products: products,
|
|
4886
|
+
selected: product
|
|
4887
|
+
}, results)) : (/*#__PURE__*/React.createElement(ProductTabs, {
|
|
4888
|
+
onKeyDown: handleTabsKeyDown,
|
|
4889
|
+
onSelect: setSelectedSlug,
|
|
4890
|
+
products: products,
|
|
4891
|
+
selected: product
|
|
4892
|
+
}, results)), /*#__PURE__*/React.createElement("div", {
|
|
3887
4893
|
"aria-live": "polite",
|
|
3888
|
-
className: styles$
|
|
3889
|
-
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.
|
|
4894
|
+
className: styles$i.visuallyHidden
|
|
4895
|
+
}, announceHighlight ? (_b = navigation.highlighted) === null || _b === void 0 ? void 0 : _b.title : null), mobile ? null : /*#__PURE__*/React.createElement(KeyboardHints, {
|
|
3890
4896
|
copied: navigation.copied
|
|
3891
4897
|
}));
|
|
3892
4898
|
}
|
|
3893
|
-
function KeyboardHints({
|
|
3894
|
-
copied
|
|
3895
|
-
}) {
|
|
3896
|
-
const command = commandKeyLabel();
|
|
3897
|
-
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", {
|
|
3898
|
-
role: "status"
|
|
3899
|
-
}, copied ? "Copied!" : "Copy link"), " \u00B7 ", command, "K Open search");
|
|
3900
|
-
}
|
|
3901
|
-
function initialProduct(products) {
|
|
3902
|
-
var _a, _b, _c;
|
|
3903
|
-
const lastProduct = readLastProduct();
|
|
3904
|
-
const bySlug = slug => products.find(product => product.slug === slug);
|
|
3905
|
-
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];
|
|
3906
|
-
}
|
|
3907
4899
|
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
function getAllPcoMeta() {
|
|
3918
|
-
const elements = document.head.querySelectorAll(`meta[property^="pco:"]`);
|
|
3919
|
-
const meta = {};
|
|
3920
|
-
elements.forEach(e => {
|
|
3921
|
-
const property = e.getAttribute("property");
|
|
3922
|
-
meta[property.substring("pco:".length)] = parseValue(e);
|
|
3923
|
-
});
|
|
3924
|
-
return meta;
|
|
4900
|
+
// Products open modals from many libraries, not all of which mark themselves
|
|
4901
|
+
// with roles, so a modal is recognized by what it does: cover the page.
|
|
4902
|
+
function isModalOpen() {
|
|
4903
|
+
const {
|
|
4904
|
+
clientHeight,
|
|
4905
|
+
clientWidth
|
|
4906
|
+
} = document.documentElement;
|
|
4907
|
+
const element = document.elementFromPoint(clientWidth / 2, clientHeight / 2);
|
|
4908
|
+
return element !== null && (isInNativeDialog(element) || hasBackdrop(element, clientWidth, clientHeight));
|
|
3925
4909
|
}
|
|
3926
|
-
//
|
|
3927
|
-
//
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
var _a, _b;
|
|
3931
|
-
return (_b = (_a = document.head.querySelector(`meta[property="pco:recently_visitable"]`)) === null || _a === void 0 ? void 0 : _a.getAttribute("metadata")) !== null && _b !== void 0 ? _b : null;
|
|
4910
|
+
// A native modal's backdrop is drawn by the browser rather than being an
|
|
4911
|
+
// element; hit-testing it lands on the dialog itself.
|
|
4912
|
+
function isInNativeDialog(element) {
|
|
4913
|
+
return element.closest("dialog[open]") !== null;
|
|
3932
4914
|
}
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
function sync() {
|
|
3938
|
-
const nextState = getAllPcoMeta();
|
|
3939
|
-
if (!shallowCompare(store.getState(), nextState)) store.setState(nextState);
|
|
3940
|
-
const metadata = getRecentlyVisitableMetadata();
|
|
3941
|
-
if (recentlyVisitableMetadataStore.getState().metadata !== metadata) {
|
|
3942
|
-
recentlyVisitableMetadataStore.setState({
|
|
3943
|
-
metadata
|
|
3944
|
-
});
|
|
4915
|
+
function hasBackdrop(element, width, height) {
|
|
4916
|
+
let current = element;
|
|
4917
|
+
while (current && !isBackdrop(current, width, height)) {
|
|
4918
|
+
current = current.parentElement;
|
|
3945
4919
|
}
|
|
4920
|
+
return current !== null;
|
|
3946
4921
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
// Re-read once now, since observe() is only called once a consumer is
|
|
3957
|
-
// actually mounting, which is always after the document has fully parsed.
|
|
3958
|
-
sync();
|
|
3959
|
-
observer.observe(document.head, {
|
|
3960
|
-
attributes: true,
|
|
3961
|
-
childList: true,
|
|
3962
|
-
subtree: true
|
|
3963
|
-
});
|
|
3964
|
-
}
|
|
3965
|
-
function usePcoMeta() {
|
|
3966
|
-
observe();
|
|
3967
|
-
return shim.useSyncExternalStore(store.subscribe, () => store.getState());
|
|
4922
|
+
// Some backdrops start below the topbar rather than at the top of the screen,
|
|
4923
|
+
// so the layer only has to span the width and reach from the center to the
|
|
4924
|
+
// bottom. Its own box has to reach the center: a bar along the bottom can
|
|
4925
|
+
// hold a panel that pops up over the center, like Services' media player.
|
|
4926
|
+
// react-aria leaves a closed modal's backdrop up while it fades out, search's
|
|
4927
|
+
// own included, so one that's on its way out doesn't count.
|
|
4928
|
+
function isBackdrop(element, width, height) {
|
|
4929
|
+
const rect = element.getBoundingClientRect();
|
|
4930
|
+
return !element.matches("[data-exiting]") && getComputedStyle(element).position === "fixed" && rect.width >= width - SCROLLBAR_SLACK && rect.top <= height / 2 && rect.bottom >= height - ROUNDING_SLACK;
|
|
3968
4931
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
4932
|
+
// react-aria keeps a classic scrollbar's gutter reserved while its modals lock
|
|
4933
|
+
// scrolling, so their backdrops stop short of the right edge by its width.
|
|
4934
|
+
const SCROLLBAR_SLACK = 24;
|
|
4935
|
+
// Rects are fractional at non-100% zoom, but clientWidth and clientHeight are
|
|
4936
|
+
// rounded to whole pixels.
|
|
4937
|
+
const ROUNDING_SLACK = 1;
|
|
4938
|
+
|
|
4939
|
+
function useSearchShortcut(enabled, open, setOpen) {
|
|
4940
|
+
React.useEffect(() => {
|
|
4941
|
+
if (!enabled) return undefined;
|
|
4942
|
+
// Rich text editors like Trix bind ⌘K to "insert link"; capturing at the
|
|
4943
|
+
// window and stopping propagation keeps them from ever seeing it. Under a
|
|
4944
|
+
// product's modal, search stays shut rather than stacking a second modal,
|
|
4945
|
+
// and the keystroke goes on to the product.
|
|
4946
|
+
const handleKeyDown = event => {
|
|
4947
|
+
if (open && isSearchShortcut(event)) {
|
|
4948
|
+
event.preventDefault();
|
|
4949
|
+
event.stopPropagation();
|
|
4950
|
+
setOpen(false);
|
|
4951
|
+
} else if (!open && (isSearchShortcut(event) || isLegacySearchShortcut(event)) && !isModalOpen()) {
|
|
4952
|
+
event.preventDefault();
|
|
4953
|
+
event.stopPropagation();
|
|
4954
|
+
setOpen(true);
|
|
4955
|
+
}
|
|
4956
|
+
};
|
|
4957
|
+
window.addEventListener("keydown", handleKeyDown, {
|
|
4958
|
+
capture: true
|
|
4959
|
+
});
|
|
4960
|
+
return () => window.removeEventListener("keydown", handleKeyDown, {
|
|
4961
|
+
capture: true
|
|
4962
|
+
});
|
|
4963
|
+
}, [enabled, open, setOpen]);
|
|
3972
4964
|
}
|
|
3973
4965
|
|
|
3974
|
-
const
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
}
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
}
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
}
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
} else if (value.type === "page") {
|
|
4005
|
-
return isNonBlankString(value.product) && isNonBlankString(value.title) && isNonBlankString(value.path) && typeof value.details === "string";
|
|
4006
|
-
} else {
|
|
4007
|
-
return false;
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
|
-
// Cuts on code points so a surrogate pair is never split.
|
|
4011
|
-
function clampText(value) {
|
|
4012
|
-
return Array.from(value).slice(0, MAX_TEXT_LENGTH).join("");
|
|
4966
|
+
const PERMISSION_CHECKS = {
|
|
4967
|
+
"check-ins": () => fetchMe("check-ins", "permission").then(({
|
|
4968
|
+
permission
|
|
4969
|
+
}) => permission !== null),
|
|
4970
|
+
giving: personId => {
|
|
4971
|
+
if (personId === undefined) {
|
|
4972
|
+
return Promise.reject(new Error("No person to check Giving access for"));
|
|
4973
|
+
} else {
|
|
4974
|
+
return fetchAttributes(`giving/v2/person_permissions/${personId}`, {
|
|
4975
|
+
"fields[PersonPermissions]": "can_search"
|
|
4976
|
+
}).then(({
|
|
4977
|
+
can_search
|
|
4978
|
+
}) => can_search);
|
|
4979
|
+
}
|
|
4980
|
+
},
|
|
4981
|
+
people: () => fetchMe("people", "people_permissions,site_administrator").then(({
|
|
4982
|
+
people_permissions,
|
|
4983
|
+
site_administrator
|
|
4984
|
+
}) => site_administrator || people_permissions !== null),
|
|
4985
|
+
registrations: () => fetchMe("registrations", "can_search").then(({
|
|
4986
|
+
can_search
|
|
4987
|
+
}) => can_search),
|
|
4988
|
+
services: () => fetchMe("services", "can_search").then(({
|
|
4989
|
+
can_search
|
|
4990
|
+
}) => can_search)
|
|
4991
|
+
};
|
|
4992
|
+
function fetchMe(product, fields) {
|
|
4993
|
+
return fetchAttributes(`${product}/v2/me`, {
|
|
4994
|
+
"fields[Person]": fields
|
|
4995
|
+
});
|
|
4013
4996
|
}
|
|
4014
|
-
function
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4997
|
+
function fetchAttributes(path, params) {
|
|
4998
|
+
const url = `${window.location.origin}/~api/${path}?${new URLSearchParams(params)}`;
|
|
4999
|
+
return apiRequest(defaultFetch, url, {
|
|
5000
|
+
headers: apiVersionHeaders(url)
|
|
5001
|
+
}).then(({
|
|
5002
|
+
json,
|
|
5003
|
+
ok
|
|
5004
|
+
}) => {
|
|
5005
|
+
if (!ok || json === null) throw new Error("Permission check failed");
|
|
5006
|
+
return json.data.attributes;
|
|
5007
|
+
});
|
|
4024
5008
|
}
|
|
4025
|
-
//
|
|
4026
|
-
//
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
}
|
|
4034
|
-
|
|
4035
|
-
return parsed.entries.filter(isEntry);
|
|
5009
|
+
// Permissions rarely change mid-session, so a successful check is kept until
|
|
5010
|
+
// the page reloads. A failed one is dropped so a remount tries again.
|
|
5011
|
+
const permissionCache = new Map();
|
|
5012
|
+
function canSearch(slug, personId) {
|
|
5013
|
+
const check = PERMISSION_CHECKS[slug];
|
|
5014
|
+
const cached = permissionCache.get(slug);
|
|
5015
|
+
if (!check) {
|
|
5016
|
+
return Promise.resolve(true);
|
|
5017
|
+
} else if (cached) {
|
|
5018
|
+
return cached;
|
|
4036
5019
|
} else {
|
|
4037
|
-
|
|
5020
|
+
const promise = check(personId).catch(() => {
|
|
5021
|
+
permissionCache.delete(slug);
|
|
5022
|
+
return false;
|
|
5023
|
+
});
|
|
5024
|
+
permissionCache.set(slug, promise);
|
|
5025
|
+
return promise;
|
|
4038
5026
|
}
|
|
4039
5027
|
}
|
|
4040
|
-
function addVisit(entries, visit) {
|
|
4041
|
-
const key = entryKey(visit);
|
|
4042
|
-
return [clampEntry(visit), ...entries.filter(entry => entryKey(entry) !== key)].slice(0, MAX_ENTRIES);
|
|
4043
|
-
}
|
|
4044
|
-
function serializeRecentlyVisited(entries, personId) {
|
|
4045
|
-
return JSON.stringify({
|
|
4046
|
-
entries,
|
|
4047
|
-
person: personId,
|
|
4048
|
-
v: FORMAT_VERSION
|
|
4049
|
-
});
|
|
4050
|
-
}
|
|
4051
5028
|
|
|
4052
|
-
|
|
4053
|
-
//
|
|
4054
|
-
//
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
const
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
if (resource && isPcrl(resource)) {
|
|
4077
|
-
return {
|
|
4078
|
-
pcrl: resource,
|
|
4079
|
-
type: "resource"
|
|
5029
|
+
function useSearchableProducts(apps) {
|
|
5030
|
+
// Keyed by name because providers hand over a fresh `apps` array on renders
|
|
5031
|
+
// where nothing changed.
|
|
5032
|
+
const accessibleKey = inSwitcherOrder(apps).map(({
|
|
5033
|
+
slug
|
|
5034
|
+
}) => slug).join(",");
|
|
5035
|
+
const personId = useCurrentPersonId();
|
|
5036
|
+
const [settled, setSettled] = React.useState();
|
|
5037
|
+
React.useEffect(() => {
|
|
5038
|
+
let current = true;
|
|
5039
|
+
const accessible = accessibleKey.split(",").flatMap(slug => SEARCHABLE_PRODUCTS.filter(product => product.slug === slug));
|
|
5040
|
+
Promise.all(accessible.map(({
|
|
5041
|
+
slug
|
|
5042
|
+
}) => canSearch(slug, personId))).then(allowed => {
|
|
5043
|
+
if (current) {
|
|
5044
|
+
setSettled({
|
|
5045
|
+
accessibleKey,
|
|
5046
|
+
personId,
|
|
5047
|
+
products: accessible.filter((_, index) => allowed[index])
|
|
5048
|
+
});
|
|
5049
|
+
}
|
|
5050
|
+
});
|
|
5051
|
+
return () => {
|
|
5052
|
+
current = false;
|
|
4080
5053
|
};
|
|
4081
|
-
}
|
|
5054
|
+
}, [accessibleKey, personId]);
|
|
5055
|
+
if ((settled === null || settled === void 0 ? void 0 : settled.accessibleKey) === accessibleKey && settled.personId === personId) {
|
|
4082
5056
|
return {
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
product,
|
|
4086
|
-
title,
|
|
4087
|
-
type: "page"
|
|
5057
|
+
products: settled.products,
|
|
5058
|
+
status: "done"
|
|
4088
5059
|
};
|
|
4089
5060
|
} else {
|
|
4090
|
-
return
|
|
5061
|
+
return {
|
|
5062
|
+
status: "loading"
|
|
5063
|
+
};
|
|
4091
5064
|
}
|
|
4092
5065
|
}
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
// panel always shows what actually gets stored.
|
|
4096
|
-
function useCurrentVisit() {
|
|
4097
|
-
const {
|
|
4098
|
-
recently_visitable: title,
|
|
4099
|
-
resource
|
|
4100
|
-
} = usePcoMeta();
|
|
4101
|
-
const metadata = useRecentlyVisitableMetadata();
|
|
4102
|
-
return React.useMemo(() => visitFromPage({
|
|
4103
|
-
metadata,
|
|
4104
|
-
resource,
|
|
4105
|
-
title
|
|
4106
|
-
}), [metadata, resource, title]);
|
|
5066
|
+
function inSwitcherOrder(apps) {
|
|
5067
|
+
return groupApps(apps).restProducts.flatMap(app => SEARCHABLE_PRODUCTS.filter(product => product.appName === app.name));
|
|
4107
5068
|
}
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
5069
|
+
|
|
5070
|
+
/**
|
|
5071
|
+
* Writes the height of the visible part of the page to the parent of `ref`'s
|
|
5072
|
+
* element as `--visual-viewport-height`. It shrinks when an on-screen keyboard
|
|
5073
|
+
* opens; `100dvh` doesn't, since mobile Safari draws the keyboard over it.
|
|
5074
|
+
*/
|
|
5075
|
+
function useVisualViewportHeight(ref, enabled) {
|
|
5076
|
+
React.useEffect(() => {
|
|
5077
|
+
var _a;
|
|
5078
|
+
const viewport = window.visualViewport;
|
|
5079
|
+
const parent = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
5080
|
+
if (!enabled || !viewport || !parent) return undefined;
|
|
5081
|
+
const update = () => parent.style.setProperty("--visual-viewport-height", `${viewport.height}px`);
|
|
5082
|
+
update();
|
|
5083
|
+
viewport.addEventListener("resize", update);
|
|
5084
|
+
return () => {
|
|
5085
|
+
viewport.removeEventListener("resize", update);
|
|
5086
|
+
parent.style.removeProperty("--visual-viewport-height");
|
|
5087
|
+
};
|
|
5088
|
+
}, [enabled, ref]);
|
|
4115
5089
|
}
|
|
4116
5090
|
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
env,
|
|
4122
|
-
personId
|
|
5091
|
+
function ProductSearch({
|
|
5092
|
+
apps,
|
|
5093
|
+
mobile = false,
|
|
5094
|
+
env
|
|
4123
5095
|
}) {
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
//
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
}
|
|
5096
|
+
const searchable = useSearchableProducts(apps);
|
|
5097
|
+
const [open, setOpen] = React.useState(false);
|
|
5098
|
+
const modalRef = React.useRef(null);
|
|
5099
|
+
// Sizes Tapestry's modal frame, the dialog's parent, to fit above the keyboard.
|
|
5100
|
+
useVisualViewportHeight(modalRef, mobile && open);
|
|
5101
|
+
const available = searchable.status === "done" && searchable.products.length > 0;
|
|
5102
|
+
useSearchShortcut(available, open, setOpen);
|
|
5103
|
+
if (!available) return null;
|
|
5104
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SearchButton, {
|
|
5105
|
+
mobile: mobile,
|
|
5106
|
+
onClick: () => setOpen(true)
|
|
5107
|
+
}), /*#__PURE__*/React.createElement(tapestry.Modal, {
|
|
5108
|
+
"aria-label": "Search",
|
|
5109
|
+
className: classnames(styles$i.modal, {
|
|
5110
|
+
[styles$i.mobile]: mobile
|
|
5111
|
+
}),
|
|
5112
|
+
dismissible: true,
|
|
5113
|
+
onClose: () => setOpen(false),
|
|
5114
|
+
open: open,
|
|
5115
|
+
ref: modalRef,
|
|
5116
|
+
size: "lg"
|
|
5117
|
+
}, mobile && /*#__PURE__*/React.createElement(tapestry.ModalHeader, {
|
|
5118
|
+
className: styles$i.header,
|
|
5119
|
+
title: "Search"
|
|
5120
|
+
}), /*#__PURE__*/React.createElement(SearchPanel, {
|
|
5121
|
+
mobile: mobile,
|
|
5122
|
+
env: env,
|
|
5123
|
+
products: searchable.products
|
|
5124
|
+
})));
|
|
4149
5125
|
}
|
|
4150
5126
|
|
|
4151
5127
|
function useRecordRecentlyVisited({
|
|
@@ -4190,9 +5166,9 @@ function StyledRoot({
|
|
|
4190
5166
|
`), children);
|
|
4191
5167
|
}
|
|
4192
5168
|
|
|
4193
|
-
var styles$
|
|
5169
|
+
var styles$a = {"route":"route--8BWe","is-active":"is-active-FsU9Y","apps-routes-separator":"apps-routes-separator-Nlm-N"};
|
|
4194
5170
|
|
|
4195
|
-
var styles$
|
|
5171
|
+
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"};
|
|
4196
5172
|
|
|
4197
5173
|
const UserMenu = ({
|
|
4198
5174
|
appName,
|
|
@@ -4211,7 +5187,7 @@ const UserMenu = ({
|
|
|
4211
5187
|
component: Outsider,
|
|
4212
5188
|
onOutsideClick: () => setUserMenuVisible(false),
|
|
4213
5189
|
cleanup: !userMenuVisible,
|
|
4214
|
-
className: styles$
|
|
5190
|
+
className: styles$9.popup,
|
|
4215
5191
|
isVisible: userMenuVisible
|
|
4216
5192
|
}, /*#__PURE__*/React.createElement("div", null, hasConnectedPeople ? (/*#__PURE__*/React.createElement("div", {
|
|
4217
5193
|
style: {
|
|
@@ -4355,11 +5331,11 @@ const UserMenuTrigger = ({
|
|
|
4355
5331
|
userMenuVisible
|
|
4356
5332
|
}) => {
|
|
4357
5333
|
return /*#__PURE__*/React.createElement("div", {
|
|
4358
|
-
className: styles$
|
|
5334
|
+
className: styles$9["trigger-wrap"],
|
|
4359
5335
|
"data-pendo": "topbar-org-switcher"
|
|
4360
5336
|
}, /*#__PURE__*/React.createElement(Hoverable, {
|
|
4361
|
-
className: classnames(styles$
|
|
4362
|
-
[styles$
|
|
5337
|
+
className: classnames(styles$9["trigger"], {
|
|
5338
|
+
[styles$9["is-visible"]]: userMenuVisible
|
|
4363
5339
|
}),
|
|
4364
5340
|
component: "button",
|
|
4365
5341
|
type: "button",
|
|
@@ -4371,7 +5347,7 @@ const UserMenuTrigger = ({
|
|
|
4371
5347
|
requestConnectedPeopleFetch();
|
|
4372
5348
|
}
|
|
4373
5349
|
}, showOrgName && (/*#__PURE__*/React.createElement("span", {
|
|
4374
|
-
className: styles$
|
|
5350
|
+
className: styles$9["trigger-orgname"]
|
|
4375
5351
|
}, orgName)), /*#__PURE__*/React.createElement(DisclosureChevronIcon, null)));
|
|
4376
5352
|
};
|
|
4377
5353
|
|
|
@@ -4431,13 +5407,14 @@ const NotSmallTopbar = ({
|
|
|
4431
5407
|
colors: props.colors,
|
|
4432
5408
|
requestAppsFetch: props.requestAppsFetch
|
|
4433
5409
|
})), /*#__PURE__*/React.createElement("div", {
|
|
4434
|
-
className: styles$
|
|
5410
|
+
className: styles$a["apps-routes-separator"]
|
|
4435
5411
|
}), routesVisible && props.routes, /*#__PURE__*/React.createElement("div", {
|
|
4436
5412
|
style: {
|
|
4437
5413
|
margin: "auto"
|
|
4438
5414
|
}
|
|
4439
5415
|
}), !notificationsEnabled && notifications(), productSearchEnabled ? (/*#__PURE__*/React.createElement(ProductSearch, {
|
|
4440
|
-
apps: props.apps
|
|
5416
|
+
apps: props.apps,
|
|
5417
|
+
env: props.env
|
|
4441
5418
|
})) : search({
|
|
4442
5419
|
hideRoutes: () => setRoutesVisible(false),
|
|
4443
5420
|
showRoutes: () => setRoutesVisible(true)
|
|
@@ -4471,12 +5448,12 @@ const NotSmallRoute = ({
|
|
|
4471
5448
|
...nativeProps
|
|
4472
5449
|
}) => {
|
|
4473
5450
|
return /*#__PURE__*/React.createElement("a", {
|
|
4474
|
-
className: classnames(styles$
|
|
5451
|
+
className: classnames(styles$a["route"], className, active && styles$a["is-active"]),
|
|
4475
5452
|
...nativeProps
|
|
4476
5453
|
});
|
|
4477
5454
|
};
|
|
4478
5455
|
|
|
4479
|
-
var styles$
|
|
5456
|
+
var styles$8 = {"avatar":"avatar-tW-wn"};
|
|
4480
5457
|
|
|
4481
5458
|
function Avatar({
|
|
4482
5459
|
env,
|
|
@@ -4486,7 +5463,7 @@ function Avatar({
|
|
|
4486
5463
|
}) {
|
|
4487
5464
|
const url = incomingUrl || `${pcoUrl$1(env)("people")}/static/no_photo_thumbnail_gray.svg`;
|
|
4488
5465
|
return /*#__PURE__*/React.createElement("div", {
|
|
4489
|
-
className: styles$
|
|
5466
|
+
className: styles$8.avatar,
|
|
4490
5467
|
style: {
|
|
4491
5468
|
backgroundImage: `url(${url})`,
|
|
4492
5469
|
...style
|
|
@@ -32245,7 +33222,7 @@ function requireReactDom () {
|
|
|
32245
33222
|
|
|
32246
33223
|
var reactDomExports = requireReactDom();
|
|
32247
33224
|
|
|
32248
|
-
var styles$
|
|
33225
|
+
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"};
|
|
32249
33226
|
|
|
32250
33227
|
const AppsSwitcher = ({
|
|
32251
33228
|
appName,
|
|
@@ -32273,8 +33250,8 @@ const AppsSwitcher = ({
|
|
|
32273
33250
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32274
33251
|
"aria-current": name === appName ? "page" : undefined,
|
|
32275
33252
|
"aria-label": name,
|
|
32276
|
-
className: classnames(styles$
|
|
32277
|
-
[styles$
|
|
33253
|
+
className: classnames(styles$7.link, {
|
|
33254
|
+
[styles$7.current]: name === appName
|
|
32278
33255
|
}),
|
|
32279
33256
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32280
33257
|
"data-turbolinks": false,
|
|
@@ -32286,48 +33263,48 @@ const AppsSwitcher = ({
|
|
|
32286
33263
|
"aria-expanded": visible,
|
|
32287
33264
|
"aria-haspopup": "true",
|
|
32288
33265
|
"aria-label": `App switcher: ${appName}`,
|
|
32289
|
-
className: styles$
|
|
33266
|
+
className: styles$7.trigger,
|
|
32290
33267
|
onClick: onTriggerClick,
|
|
32291
33268
|
type: "button"
|
|
32292
33269
|
}, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
32293
33270
|
appName: appName.replace(/[\s-]/, "")
|
|
32294
33271
|
})), /*#__PURE__*/reactDomExports.createPortal(/*#__PURE__*/React.createElement("div", {
|
|
32295
33272
|
ref: containerRef,
|
|
32296
|
-
className: classnames(styles$
|
|
32297
|
-
[styles$
|
|
33273
|
+
className: classnames(styles$7.layer, {
|
|
33274
|
+
[styles$7["layer--visible"]]: visible
|
|
32298
33275
|
}),
|
|
32299
33276
|
onClick: toggle,
|
|
32300
33277
|
onKeyDown: handleKeyDown
|
|
32301
33278
|
}, /*#__PURE__*/React.createElement("nav", {
|
|
32302
33279
|
"aria-label": "Switch app",
|
|
32303
33280
|
"aria-modal": visible,
|
|
32304
|
-
className: styles$
|
|
33281
|
+
className: styles$7.panel,
|
|
32305
33282
|
"data-pendo": "app-switcher",
|
|
32306
33283
|
onClick: event => event.stopPropagation(),
|
|
32307
33284
|
role: "dialog"
|
|
32308
33285
|
}, /*#__PURE__*/React.createElement("div", {
|
|
32309
|
-
className: styles$
|
|
33286
|
+
className: styles$7.header
|
|
32310
33287
|
}, /*#__PURE__*/React.createElement("span", {
|
|
32311
|
-
className: styles$
|
|
33288
|
+
className: styles$7.heading
|
|
32312
33289
|
}, "Switch to"), /*#__PURE__*/React.createElement("button", {
|
|
32313
33290
|
"aria-label": "Close",
|
|
32314
|
-
className: styles$
|
|
33291
|
+
className: styles$7.close,
|
|
32315
33292
|
onClick: toggle,
|
|
32316
33293
|
type: "button"
|
|
32317
33294
|
}, /*#__PURE__*/React.createElement(X, {
|
|
32318
33295
|
"aria-hidden": "true",
|
|
32319
33296
|
fill: "var(--t-icon-color-dim)"
|
|
32320
33297
|
}))), /*#__PURE__*/React.createElement("ul", {
|
|
32321
|
-
className: styles$
|
|
33298
|
+
className: styles$7.list,
|
|
32322
33299
|
role: "list"
|
|
32323
33300
|
}, homeProduct && productLink(homeProduct.name), restProducts.map(({
|
|
32324
33301
|
name
|
|
32325
33302
|
}) => productLink(name)), accountsProduct && (/*#__PURE__*/React.createElement("li", {
|
|
32326
|
-
className: styles$
|
|
33303
|
+
className: styles$7["accounts-item"]
|
|
32327
33304
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32328
33305
|
"aria-current": appName === "Accounts" ? "page" : undefined,
|
|
32329
33306
|
"aria-label": "Account settings",
|
|
32330
|
-
className: classnames(styles$
|
|
33307
|
+
className: classnames(styles$7.link, styles$7["accounts-link"]),
|
|
32331
33308
|
"data-pendo": "app-switcher--change-app--Accounts",
|
|
32332
33309
|
"data-turbolinks": false,
|
|
32333
33310
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(accountsProduct.name)
|
|
@@ -32350,14 +33327,14 @@ ClientStorage.defaultProps = {
|
|
|
32350
33327
|
method: "localStorage"
|
|
32351
33328
|
};
|
|
32352
33329
|
|
|
32353
|
-
var styles$
|
|
33330
|
+
var styles$6 = {"appmenu-button":"appmenu-button-b0UvT","appmenu-layer":"appmenu-layer-0IQpN","appmenu-container":"appmenu-container-gackd","appmenu-scroll":"appmenu-scroll-mPSx0","appmenu-list":"appmenu-list-h7clX","appmenu-list-a":"appmenu-list-a-rGIQN","appmenu-list-a--top":"appmenu-list-a--top-RT-2F","appmenu-list-a--bottom":"appmenu-list-a--bottom-YDeBC","appmenu-close-button":"appmenu-close-button-vv9bV","current-route":"current-route-wpJgz","is-active":"is-active-wy8Bg","routes":"routes-Nmt1S","route":"route-kkknQ","actions":"actions-INQ7-","profile-button":"profile-button-a2WO2"};
|
|
32354
33331
|
|
|
32355
33332
|
function AccountSettingsLink({
|
|
32356
33333
|
env
|
|
32357
33334
|
}) {
|
|
32358
33335
|
const accountsName = "accounts";
|
|
32359
33336
|
return /*#__PURE__*/React.createElement("a", {
|
|
32360
|
-
className: classnames(styles$
|
|
33337
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--bottom"]),
|
|
32361
33338
|
"data-pendo": `app-switcher--change-app--${accountsName}`,
|
|
32362
33339
|
"data-turbolinks": false,
|
|
32363
33340
|
href: pcoUrl$1(env)(accountsName) + getAppsSwitchProductPath(accountsName)
|
|
@@ -32380,7 +33357,7 @@ const CloseMenuButton = ({
|
|
|
32380
33357
|
}) => (/*#__PURE__*/React.createElement("button", {
|
|
32381
33358
|
"aria-label": "Close menu",
|
|
32382
33359
|
type: "button",
|
|
32383
|
-
className: styles$
|
|
33360
|
+
className: styles$6["appmenu-close-button"],
|
|
32384
33361
|
style: style
|
|
32385
33362
|
}, /*#__PURE__*/React.createElement(X, {
|
|
32386
33363
|
fill: color,
|
|
@@ -32441,7 +33418,7 @@ const LegacyAppsSwitcher = ({
|
|
|
32441
33418
|
"aria-haspopup": "true",
|
|
32442
33419
|
"aria-label": `App switcher: ${appName}`,
|
|
32443
33420
|
type: "button",
|
|
32444
|
-
className: styles$
|
|
33421
|
+
className: styles$6["appmenu-button"],
|
|
32445
33422
|
onClick: onTriggerClick
|
|
32446
33423
|
}, /*#__PURE__*/React.createElement(ColorAppIcon, {
|
|
32447
33424
|
appName: appName.replace(/[\s-]/, "")
|
|
@@ -32449,7 +33426,7 @@ const LegacyAppsSwitcher = ({
|
|
|
32449
33426
|
item: "Topbar:Small:Menus:Products:visibility",
|
|
32450
33427
|
render: () => (/*#__PURE__*/React.createElement("div", {
|
|
32451
33428
|
ref: appMenuRef,
|
|
32452
|
-
className: styles$
|
|
33429
|
+
className: styles$6["appmenu-layer"],
|
|
32453
33430
|
style: {
|
|
32454
33431
|
...(visible && {
|
|
32455
33432
|
display: "block"
|
|
@@ -32466,37 +33443,37 @@ const LegacyAppsSwitcher = ({
|
|
|
32466
33443
|
e.stopPropagation();
|
|
32467
33444
|
}
|
|
32468
33445
|
},
|
|
32469
|
-
className: styles$
|
|
33446
|
+
className: styles$6["appmenu-container"]
|
|
32470
33447
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32471
33448
|
"aria-current": appName === (homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) ? "page" : undefined,
|
|
32472
33449
|
"aria-label": homeProduct ? homeProduct.name : appName,
|
|
32473
33450
|
"data-pendo": `app-switcher--change-app--${homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name}`,
|
|
32474
33451
|
"data-turbolinks": false,
|
|
32475
|
-
className: classnames(styles$
|
|
33452
|
+
className: classnames(styles$6["appmenu-list-a"], styles$6["appmenu-list-a--top"]),
|
|
32476
33453
|
href: homeProduct ? pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(homeProduct === null || homeProduct === void 0 ? void 0 : homeProduct.name) : undefined
|
|
32477
33454
|
}, /*#__PURE__*/React.createElement(UserMenuAppLockup, {
|
|
32478
33455
|
appName: homeProduct ? homeProduct.name : appName
|
|
32479
33456
|
})), /*#__PURE__*/React.createElement("div", {
|
|
32480
|
-
className: styles$
|
|
33457
|
+
className: styles$6["appmenu-scroll"]
|
|
32481
33458
|
}, /*#__PURE__*/React.createElement("ul", {
|
|
32482
33459
|
"data-pendo": "app-switcher",
|
|
32483
|
-
className: styles$
|
|
33460
|
+
className: styles$6["appmenu-list"]
|
|
32484
33461
|
}, appsMenuFormatter(restProducts).map(({
|
|
32485
33462
|
name
|
|
32486
33463
|
}) => (/*#__PURE__*/React.createElement("li", {
|
|
32487
33464
|
key: name,
|
|
32488
|
-
className: styles$
|
|
33465
|
+
className: styles$6["appmenu-list-li"]
|
|
32489
33466
|
}, /*#__PURE__*/React.createElement("a", {
|
|
32490
33467
|
"aria-current": name === appName ? "page" : undefined,
|
|
32491
33468
|
"aria-label": name,
|
|
32492
33469
|
"data-pendo": `app-switcher--change-app--${name}`,
|
|
32493
33470
|
"data-turbolinks": false,
|
|
32494
|
-
className: styles$
|
|
33471
|
+
className: styles$6["appmenu-list-a"],
|
|
32495
33472
|
href: pcoUrl$1(env)("accounts") + getAppsSwitchProductPath(name)
|
|
32496
33473
|
}, /*#__PURE__*/React.createElement(UserMenuAppLockup, {
|
|
32497
33474
|
appName: name
|
|
32498
33475
|
}))))), accountsProduct && (/*#__PURE__*/React.createElement("li", {
|
|
32499
|
-
className: styles$
|
|
33476
|
+
className: styles$6["appmenu-list-li"]
|
|
32500
33477
|
}, /*#__PURE__*/React.createElement(AccountSettingsLink, {
|
|
32501
33478
|
env: env
|
|
32502
33479
|
}))))))))
|
|
@@ -32560,19 +33537,25 @@ const SmallTopbar = ({
|
|
|
32560
33537
|
toggle: toggleProductSwitcher,
|
|
32561
33538
|
visible: productSwitcherVisible
|
|
32562
33539
|
})), routesMenuVisible && (/*#__PURE__*/React.createElement("div", {
|
|
32563
|
-
className: styles$
|
|
33540
|
+
className: styles$6["routes"]
|
|
32564
33541
|
}, props.routes)), /*#__PURE__*/React.createElement("button", {
|
|
32565
33542
|
type: "button",
|
|
32566
|
-
className: classnames(styles$
|
|
32567
|
-
[styles$
|
|
33543
|
+
className: classnames(styles$6["current-route"], {
|
|
33544
|
+
[styles$6["is-active"]]: routesMenuVisible
|
|
32568
33545
|
}),
|
|
32569
33546
|
onClick: props.routes.length > 0 ? () => {
|
|
32570
33547
|
setProductSwitcherVisible(false);
|
|
32571
33548
|
setRoutesMenuVisible(visible => !visible);
|
|
32572
33549
|
} : null
|
|
32573
|
-
}, /*#__PURE__*/React.createElement(CurrentRouteComponent, null, capitalize(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React.createElement("
|
|
33550
|
+
}, /*#__PURE__*/React.createElement(CurrentRouteComponent, null, capitalize$1(currentRouteText)), props.routes.length > 0 && /*#__PURE__*/React.createElement(DisclosureChevronIcon, null)), /*#__PURE__*/React.createElement("div", {
|
|
33551
|
+
className: styles$6["actions"]
|
|
33552
|
+
}, productSearchEnabled && (/*#__PURE__*/React.createElement(ProductSearch, {
|
|
33553
|
+
apps: props.apps,
|
|
33554
|
+
mobile: true,
|
|
33555
|
+
env: props.env
|
|
33556
|
+
})), /*#__PURE__*/React.createElement("button", {
|
|
32574
33557
|
type: "button",
|
|
32575
|
-
className: styles$
|
|
33558
|
+
className: styles$6["profile-button"],
|
|
32576
33559
|
onClick: () => {
|
|
32577
33560
|
var _a, _b;
|
|
32578
33561
|
props.requestConnectedPeopleFetch();
|
|
@@ -32581,14 +33564,14 @@ const SmallTopbar = ({
|
|
|
32581
33564
|
}, /*#__PURE__*/React.createElement(Avatar, {
|
|
32582
33565
|
env: props.env,
|
|
32583
33566
|
url: props.userAvatarPath
|
|
32584
|
-
})));
|
|
33567
|
+
}))));
|
|
32585
33568
|
};
|
|
32586
33569
|
const SmallRoute = ({
|
|
32587
33570
|
active,
|
|
32588
33571
|
className,
|
|
32589
33572
|
...props
|
|
32590
33573
|
}) => (/*#__PURE__*/React.createElement("a", {
|
|
32591
|
-
className: classnames(styles$
|
|
33574
|
+
className: classnames(styles$6["route"], className, active && styles$6["is-active"]),
|
|
32592
33575
|
...props
|
|
32593
33576
|
}));
|
|
32594
33577
|
|
|
@@ -32727,7 +33710,7 @@ const topbarTheme = {
|
|
|
32727
33710
|
id: "topbar"
|
|
32728
33711
|
};
|
|
32729
33712
|
|
|
32730
|
-
var styles$
|
|
33713
|
+
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"};
|
|
32731
33714
|
|
|
32732
33715
|
function isPresent(value) {
|
|
32733
33716
|
return value !== undefined && value.trim() !== "";
|
|
@@ -32756,15 +33739,31 @@ function Field({
|
|
|
32756
33739
|
value
|
|
32757
33740
|
}) {
|
|
32758
33741
|
return /*#__PURE__*/React.createElement("div", {
|
|
32759
|
-
className: styles$
|
|
33742
|
+
className: styles$5["debug-pco-resource-field"]
|
|
32760
33743
|
}, /*#__PURE__*/React.createElement("span", {
|
|
32761
|
-
className: styles$
|
|
33744
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
32762
33745
|
}, label), /*#__PURE__*/React.createElement("span", {
|
|
32763
|
-
className: styles$
|
|
33746
|
+
className: styles$5["debug-pco-resource-field-value"]
|
|
32764
33747
|
}, value));
|
|
32765
33748
|
}
|
|
33749
|
+
// Only staff ghosting or impersonating a person get pco:staff-access-level,
|
|
33750
|
+
// which keeps the panel away from regular users on staging.
|
|
32766
33751
|
function DebugPcoResource({
|
|
32767
33752
|
env
|
|
33753
|
+
}) {
|
|
33754
|
+
const {
|
|
33755
|
+
"staff-access-level": staffAccessLevel
|
|
33756
|
+
} = usePcoMeta();
|
|
33757
|
+
if (isPresent(staffAccessLevel)) {
|
|
33758
|
+
return /*#__PURE__*/React.createElement(DebugPanel, {
|
|
33759
|
+
env: env
|
|
33760
|
+
});
|
|
33761
|
+
} else {
|
|
33762
|
+
return null;
|
|
33763
|
+
}
|
|
33764
|
+
}
|
|
33765
|
+
function DebugPanel({
|
|
33766
|
+
env
|
|
32768
33767
|
}) {
|
|
32769
33768
|
const {
|
|
32770
33769
|
recently_visitable: recentlyVisitable,
|
|
@@ -32773,12 +33772,17 @@ function DebugPcoResource({
|
|
|
32773
33772
|
const [lookup, setLookup] = React.useState({
|
|
32774
33773
|
status: "idle"
|
|
32775
33774
|
});
|
|
32776
|
-
const
|
|
33775
|
+
const visit = useCurrentVisit();
|
|
33776
|
+
const personId = useCurrentPersonId();
|
|
33777
|
+
const recentlyVisited = useRecentlyVisited({
|
|
33778
|
+
env,
|
|
33779
|
+
personId
|
|
33780
|
+
});
|
|
33781
|
+
const pcrl = (visit === null || visit === void 0 ? void 0 : visit.type) === "resource" ? visit.pcrl : null;
|
|
32777
33782
|
const hasRecentlyVisitable = isPresent(recentlyVisitable);
|
|
33783
|
+
const hasInvalidResource = isPresent(resource) && !isPcrl(resource);
|
|
32778
33784
|
React.useEffect(() => {
|
|
32779
|
-
|
|
32780
|
-
// tags are present.
|
|
32781
|
-
const url = hasResource ? pcrlLookupUrl(resource, env) : null;
|
|
33785
|
+
const url = pcrl ? pcrlLookupUrl(pcrl, env) : null;
|
|
32782
33786
|
if (!url) {
|
|
32783
33787
|
setLookup({
|
|
32784
33788
|
status: "idle"
|
|
@@ -32810,37 +33814,45 @@ function DebugPcoResource({
|
|
|
32810
33814
|
return () => {
|
|
32811
33815
|
cancelled = true;
|
|
32812
33816
|
};
|
|
32813
|
-
}, [
|
|
33817
|
+
}, [pcrl, env]);
|
|
32814
33818
|
return /*#__PURE__*/React.createElement("div", {
|
|
32815
|
-
className: classnames("staff-only", styles$
|
|
33819
|
+
className: classnames("staff-only", styles$5["debug-pco-resource"])
|
|
32816
33820
|
}, /*#__PURE__*/React.createElement("div", {
|
|
32817
|
-
className: styles$
|
|
33821
|
+
className: styles$5["debug-pco-resource-header"]
|
|
32818
33822
|
}, /*#__PURE__*/React.createElement("span", {
|
|
32819
|
-
className: styles$
|
|
33823
|
+
className: styles$5["debug-pco-resource-badge"]
|
|
32820
33824
|
}, "Staff"), /*#__PURE__*/React.createElement("span", {
|
|
32821
|
-
className: styles$
|
|
33825
|
+
className: styles$5["debug-pco-resource-title"]
|
|
32822
33826
|
}, "Recently Visited debug")), /*#__PURE__*/React.createElement(Field, {
|
|
32823
33827
|
label: "pco:resource",
|
|
32824
33828
|
value: describeTag("resource", resource)
|
|
32825
33829
|
}), /*#__PURE__*/React.createElement(Field, {
|
|
32826
33830
|
label: "pco:recently_visitable",
|
|
32827
33831
|
value: describeTag("recently_visitable", recentlyVisitable)
|
|
32828
|
-
}),
|
|
32829
|
-
className: styles$
|
|
32830
|
-
}, "
|
|
32831
|
-
className: styles$
|
|
32832
|
-
}, "
|
|
32833
|
-
className: styles$
|
|
32834
|
-
}, "
|
|
32835
|
-
className: styles$
|
|
32836
|
-
},
|
|
32837
|
-
className: styles$
|
|
32838
|
-
},
|
|
32839
|
-
className: styles$
|
|
32840
|
-
},
|
|
32841
|
-
|
|
32842
|
-
|
|
32843
|
-
|
|
33832
|
+
}), hasInvalidResource && (/*#__PURE__*/React.createElement("p", {
|
|
33833
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33834
|
+
}, "pco:resource isn't a PCRL (product/v2/type/id), so Recently Visited ignores it.")), visit === null && (/*#__PURE__*/React.createElement("p", {
|
|
33835
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33836
|
+
}, "This page is not eligible for Recently Visited")), pcrl && hasRecentlyVisitable && (/*#__PURE__*/React.createElement("p", {
|
|
33837
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33838
|
+
}, "Both tags are present \u2014 pco:resource takes precedence for the Recently Visited entry.")), pcrl && lookup.status === "loading" && (/*#__PURE__*/React.createElement("p", {
|
|
33839
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33840
|
+
}, "Looking up resource\u2026")), pcrl && lookup.status === "error" && (/*#__PURE__*/React.createElement("p", {
|
|
33841
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33842
|
+
}, lookup.message)), pcrl && lookup.status === "loaded" && (/*#__PURE__*/React.createElement("pre", {
|
|
33843
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33844
|
+
}, JSON.stringify(lookup.json, null, 2))), (visit === null || visit === void 0 ? void 0 : visit.type) === "page" && (/*#__PURE__*/React.createElement("p", {
|
|
33845
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33846
|
+
}, "Recently Visited title: ", visit.title)), /*#__PURE__*/React.createElement("span", {
|
|
33847
|
+
className: styles$5["debug-pco-resource-field-label"]
|
|
33848
|
+
}, "Recently Visited"), personId ? (/*#__PURE__*/React.createElement("pre", {
|
|
33849
|
+
className: styles$5["debug-pco-resource-json"]
|
|
33850
|
+
}, JSON.stringify(recentlyVisited, null, 2))) : (/*#__PURE__*/React.createElement("p", {
|
|
33851
|
+
className: styles$5["debug-pco-resource-note"]
|
|
33852
|
+
}, "No current person, so nothing is recorded.")));
|
|
33853
|
+
}
|
|
33854
|
+
|
|
33855
|
+
var styles$4 = {"drawer-wrap":"drawer-wrap-J0rSL","is-visible":"is-visible-KTME8","drawer":"drawer-j7f4l"};
|
|
32844
33856
|
|
|
32845
33857
|
function Drawer({
|
|
32846
33858
|
children,
|
|
@@ -32848,7 +33860,7 @@ function Drawer({
|
|
|
32848
33860
|
}) {
|
|
32849
33861
|
return /*#__PURE__*/React.createElement("div", {
|
|
32850
33862
|
id: id,
|
|
32851
|
-
className: styles$
|
|
33863
|
+
className: styles$4["drawer"]
|
|
32852
33864
|
}, children);
|
|
32853
33865
|
}
|
|
32854
33866
|
function DrawerWrap({
|
|
@@ -32858,7 +33870,7 @@ function DrawerWrap({
|
|
|
32858
33870
|
...props
|
|
32859
33871
|
}) {
|
|
32860
33872
|
return /*#__PURE__*/React.createElement("div", {
|
|
32861
|
-
className: classnames(styles$
|
|
33873
|
+
className: classnames(styles$4["drawer-wrap"], className, isVisible && styles$4["is-visible"]),
|
|
32862
33874
|
...props
|
|
32863
33875
|
}, children);
|
|
32864
33876
|
}
|
|
@@ -33042,15 +34054,18 @@ const SwitchView = ({
|
|
|
33042
34054
|
}));
|
|
33043
34055
|
};
|
|
33044
34056
|
|
|
33045
|
-
var styles$
|
|
34057
|
+
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"};
|
|
33046
34058
|
|
|
33047
34059
|
const COLOR_MODES = [{
|
|
34060
|
+
icon: "toolbar.sun",
|
|
33048
34061
|
label: "Light",
|
|
33049
34062
|
value: "light"
|
|
33050
34063
|
}, {
|
|
34064
|
+
icon: "toolbar.moon",
|
|
33051
34065
|
label: "Dark",
|
|
33052
34066
|
value: "dark"
|
|
33053
34067
|
}, {
|
|
34068
|
+
icon: "toolbar.display",
|
|
33054
34069
|
label: "System",
|
|
33055
34070
|
value: "system"
|
|
33056
34071
|
}];
|
|
@@ -33062,14 +34077,15 @@ function ColorModeSwitcher() {
|
|
|
33062
34077
|
const updateStore = useUpdateStore();
|
|
33063
34078
|
const [counter] = React.useState(() => COLOR_MODE_INSTANCE_COUNTER++);
|
|
33064
34079
|
return /*#__PURE__*/React.createElement("fieldset", {
|
|
33065
|
-
className: styles$
|
|
34080
|
+
className: styles$3["color-mode-switcher"]
|
|
33066
34081
|
}, /*#__PURE__*/React.createElement("legend", {
|
|
33067
|
-
className: styles$
|
|
34082
|
+
className: styles$3["color-mode-switcher-label"]
|
|
33068
34083
|
}, "Appearance", " "), /*#__PURE__*/React.createElement("div", {
|
|
33069
|
-
className: styles$
|
|
34084
|
+
className: styles$3["color-mode-switcher-modes"]
|
|
33070
34085
|
}, COLOR_MODES.map(({
|
|
33071
|
-
|
|
33072
|
-
label
|
|
34086
|
+
icon,
|
|
34087
|
+
label,
|
|
34088
|
+
value
|
|
33073
34089
|
}) => (/*#__PURE__*/React.createElement("label", {
|
|
33074
34090
|
key: value
|
|
33075
34091
|
}, /*#__PURE__*/React.createElement("input", {
|
|
@@ -33086,8 +34102,9 @@ function ColorModeSwitcher() {
|
|
|
33086
34102
|
document.documentElement.setAttribute(RESOLVED_COLOR_MODE_ATTRIBUTE, resolveColorMode(colorMode));
|
|
33087
34103
|
updateColorMode(colorMode);
|
|
33088
34104
|
}
|
|
33089
|
-
}), /*#__PURE__*/React.createElement(
|
|
33090
|
-
|
|
34105
|
+
}), /*#__PURE__*/React.createElement(Icon, {
|
|
34106
|
+
name: icon,
|
|
34107
|
+
size: 15
|
|
33091
34108
|
}), label)))));
|
|
33092
34109
|
}
|
|
33093
34110
|
function updateColorMode(mode) {
|
|
@@ -33102,28 +34119,6 @@ function updateColorMode(mode) {
|
|
|
33102
34119
|
method: "POST"
|
|
33103
34120
|
});
|
|
33104
34121
|
}
|
|
33105
|
-
const COLOR_MODE_ICON_PATHS = {
|
|
33106
|
-
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",
|
|
33107
|
-
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",
|
|
33108
|
-
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"
|
|
33109
|
-
};
|
|
33110
|
-
function ColorModeIcon({
|
|
33111
|
-
mode
|
|
33112
|
-
}) {
|
|
33113
|
-
return /*#__PURE__*/React.createElement("svg", {
|
|
33114
|
-
width: "16",
|
|
33115
|
-
height: "16",
|
|
33116
|
-
viewBox: "0 0 14 14",
|
|
33117
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
33118
|
-
"aria-hidden": "true"
|
|
33119
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
33120
|
-
d: COLOR_MODE_ICON_PATHS[mode],
|
|
33121
|
-
strokeWidth: "1.5",
|
|
33122
|
-
fill: "none",
|
|
33123
|
-
strokeLinecap: "round",
|
|
33124
|
-
strokeLinejoin: "round"
|
|
33125
|
-
}));
|
|
33126
|
-
}
|
|
33127
34122
|
|
|
33128
34123
|
const requestClearConnectedPeopleCache = () => remove();
|
|
33129
34124
|
function UserProfile({
|
|
@@ -33186,10 +34181,10 @@ function UserProfile({
|
|
|
33186
34181
|
alignment: "center",
|
|
33187
34182
|
spacing: 0.5,
|
|
33188
34183
|
width: "100%"
|
|
33189
|
-
}, /*#__PURE__*/React.createElement(
|
|
34184
|
+
}, /*#__PURE__*/React.createElement(Icon, {
|
|
33190
34185
|
name: mfaConfigured ? "general.lock" : "general.unlock",
|
|
33191
|
-
size:
|
|
33192
|
-
|
|
34186
|
+
size: 12,
|
|
34187
|
+
theme: mfaConfigured ? "--t-icon-color-status-success" : "--t-icon-color-status-error"
|
|
33193
34188
|
}), /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
33194
34189
|
size: 4,
|
|
33195
34190
|
weight: 400,
|
|
@@ -33237,9 +34232,10 @@ function UserProfile({
|
|
|
33237
34232
|
}, /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
33238
34233
|
color: "var(--text-color-default-secondary)",
|
|
33239
34234
|
size: 3
|
|
33240
|
-
}, "Log out"), /*#__PURE__*/React.createElement(
|
|
34235
|
+
}, "Log out"), /*#__PURE__*/React.createElement(Icon, {
|
|
33241
34236
|
name: "general.intoBox",
|
|
33242
|
-
|
|
34237
|
+
size: 14,
|
|
34238
|
+
theme: "--t-icon-color-secondary"
|
|
33243
34239
|
})))), env !== "production" && /*#__PURE__*/React.createElement(DebugPcoResource, {
|
|
33244
34240
|
env: env
|
|
33245
34241
|
}), hasConnectedPeople && (/*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
@@ -33301,26 +34297,42 @@ function UserProfile({
|
|
|
33301
34297
|
|
|
33302
34298
|
var ActionType;
|
|
33303
34299
|
(function (ActionType) {
|
|
34300
|
+
ActionType["clearPending"] = "clearPending";
|
|
33304
34301
|
ActionType["close"] = "close";
|
|
33305
34302
|
ActionType["mount"] = "mount";
|
|
33306
34303
|
ActionType["open"] = "open";
|
|
34304
|
+
ActionType["setPending"] = "setPending";
|
|
33307
34305
|
ActionType["unmount"] = "unmount";
|
|
33308
34306
|
})(ActionType || (ActionType = {}));
|
|
33309
34307
|
const initialState = {
|
|
33310
34308
|
currentContent: null,
|
|
33311
|
-
open: false
|
|
34309
|
+
open: false,
|
|
34310
|
+
pending: null
|
|
33312
34311
|
};
|
|
33313
34312
|
const reducer = (state, action) => {
|
|
34313
|
+
var _a, _b;
|
|
33314
34314
|
switch (action.type) {
|
|
33315
34315
|
case ActionType.mount:
|
|
33316
34316
|
return {
|
|
33317
34317
|
...state,
|
|
33318
|
-
currentContent: action.payload.content
|
|
34318
|
+
currentContent: action.payload.content,
|
|
34319
|
+
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
|
|
33319
34320
|
};
|
|
33320
34321
|
case ActionType.unmount:
|
|
33321
34322
|
return {
|
|
33322
34323
|
...state,
|
|
33323
|
-
currentContent: null
|
|
34324
|
+
currentContent: null,
|
|
34325
|
+
pending: null
|
|
34326
|
+
};
|
|
34327
|
+
case ActionType.setPending:
|
|
34328
|
+
return {
|
|
34329
|
+
...state,
|
|
34330
|
+
pending: action.payload
|
|
34331
|
+
};
|
|
34332
|
+
case ActionType.clearPending:
|
|
34333
|
+
return {
|
|
34334
|
+
...state,
|
|
34335
|
+
pending: null
|
|
33324
34336
|
};
|
|
33325
34337
|
case ActionType.open:
|
|
33326
34338
|
return {
|
|
@@ -33339,13 +34351,181 @@ const useToolbarReducer = overrides => React__namespace.useReducer(reducer, {
|
|
|
33339
34351
|
...overrides
|
|
33340
34352
|
});
|
|
33341
34353
|
|
|
33342
|
-
var styles$
|
|
34354
|
+
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"};
|
|
33343
34355
|
|
|
33344
|
-
|
|
34356
|
+
// PCO Box serves TLS by default, and a bare http:// frame inside an https
|
|
34357
|
+
// product page is blocked as mixed content, so follow the page's scheme.
|
|
34358
|
+
const iframeSrc = () => `${window.location.protocol}//pco.test/iframe`;
|
|
34359
|
+
const STAFF_PINK = "var(--t-fill-color-product-staff)";
|
|
34360
|
+
// The pco.test domain is only ever served by a local PCO Box, so the tool
|
|
34361
|
+
// stays hidden in staging and production — and on Topbar's own dev server,
|
|
34362
|
+
// which runs on localhost and has no pco.test to frame.
|
|
34363
|
+
function isPcoTestHost() {
|
|
34364
|
+
const {
|
|
34365
|
+
hostname
|
|
34366
|
+
} = window.location;
|
|
34367
|
+
return hostname === "pco.test" || hostname.endsWith(".pco.test");
|
|
34368
|
+
}
|
|
34369
|
+
// Flattened from the source artwork's two outline paths — its gradients,
|
|
34370
|
+
// filters and clip paths carry nothing at icon size. Those paths are authored
|
|
34371
|
+
// on a 128px canvas, hence the transform: it fits the pair into the same ~24px
|
|
34372
|
+
// optical box the sibling toolbar icons occupy. Filled explicitly rather than
|
|
34373
|
+
// with `currentColor` so the mark stays staff pink instead of inheriting the
|
|
34374
|
+
// toolbar's text color and its hover/active states.
|
|
34375
|
+
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";
|
|
34376
|
+
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";
|
|
34377
|
+
function ToolsIcon() {
|
|
34378
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
34379
|
+
"aria-hidden": "true",
|
|
34380
|
+
width: "32",
|
|
34381
|
+
height: "32",
|
|
34382
|
+
viewBox: "0 0 32 32",
|
|
34383
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
34384
|
+
}, /*#__PURE__*/React.createElement("g", {
|
|
34385
|
+
fill: STAFF_PINK,
|
|
34386
|
+
transform: "translate(2.92 2.66) scale(.204)"
|
|
34387
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
34388
|
+
d: WRENCH
|
|
34389
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
34390
|
+
d: SCREWDRIVER
|
|
34391
|
+
})));
|
|
34392
|
+
}
|
|
34393
|
+
// A cross-origin frame's own history object is unreachable (touching it throws
|
|
34394
|
+
// a SecurityError), but the frame's navigations do land in the top-level joint
|
|
34395
|
+
// session history, so the *parent's* back/forward traverse them while leaving
|
|
34396
|
+
// the host page in place. Counting frame loads tracks how deep we are, which is
|
|
34397
|
+
// what keeps a click on Back from stepping past the frame's first page and
|
|
34398
|
+
// navigating the host app instead. Loads we caused are attributed to the
|
|
34399
|
+
// traversal that requested them rather than counted as new navigations.
|
|
34400
|
+
function useFrameHistory() {
|
|
34401
|
+
const [steps, setSteps] = React.useState({
|
|
34402
|
+
back: 0,
|
|
34403
|
+
forward: 0
|
|
34404
|
+
});
|
|
34405
|
+
// Traversal has to read the current counts outside of render, so the ref is
|
|
34406
|
+
// the source of truth and state exists to paint the buttons.
|
|
34407
|
+
const stepsRef = React.useRef(steps);
|
|
34408
|
+
const pendingTraversal = React.useRef(null);
|
|
34409
|
+
const frameIsLoaded = React.useRef(false);
|
|
34410
|
+
const moveTo = React.useCallback(next => {
|
|
34411
|
+
stepsRef.current = next;
|
|
34412
|
+
setSteps(next);
|
|
34413
|
+
}, []);
|
|
34414
|
+
const onFrameLoad = React.useCallback(() => {
|
|
34415
|
+
if (!frameIsLoaded.current) {
|
|
34416
|
+
// The frame's first page is the floor, not a step.
|
|
34417
|
+
frameIsLoaded.current = true;
|
|
34418
|
+
} else {
|
|
34419
|
+
const traversal = pendingTraversal.current;
|
|
34420
|
+
pendingTraversal.current = null;
|
|
34421
|
+
const {
|
|
34422
|
+
back,
|
|
34423
|
+
forward
|
|
34424
|
+
} = stepsRef.current;
|
|
34425
|
+
if (traversal === "back") {
|
|
34426
|
+
moveTo({
|
|
34427
|
+
back: back - 1,
|
|
34428
|
+
forward: forward + 1
|
|
34429
|
+
});
|
|
34430
|
+
} else if (traversal === "forward") {
|
|
34431
|
+
moveTo({
|
|
34432
|
+
back: back + 1,
|
|
34433
|
+
forward: forward - 1
|
|
34434
|
+
});
|
|
34435
|
+
} else {
|
|
34436
|
+
moveTo({
|
|
34437
|
+
back: back + 1,
|
|
34438
|
+
forward: 0
|
|
34439
|
+
});
|
|
34440
|
+
}
|
|
34441
|
+
}
|
|
34442
|
+
}, [moveTo]);
|
|
34443
|
+
// Guarded here rather than trusting the button's disabled state: traversing
|
|
34444
|
+
// with no frame step left would navigate the host app out from under us.
|
|
34445
|
+
const traverse = React.useCallback(direction => {
|
|
34446
|
+
if (stepsRef.current[direction] > 0) {
|
|
34447
|
+
pendingTraversal.current = direction;
|
|
34448
|
+
window.history[direction]();
|
|
34449
|
+
}
|
|
34450
|
+
}, []);
|
|
34451
|
+
return {
|
|
34452
|
+
onFrameLoad,
|
|
34453
|
+
steps,
|
|
34454
|
+
traverse
|
|
34455
|
+
};
|
|
34456
|
+
}
|
|
34457
|
+
function NavButton({
|
|
34458
|
+
direction,
|
|
34459
|
+
disabled,
|
|
34460
|
+
onClick
|
|
34461
|
+
}) {
|
|
34462
|
+
return /*#__PURE__*/React.createElement(tapestryReact.Button
|
|
34463
|
+
// Tapestry's `disabled` suppresses the click but emits no ARIA state.
|
|
34464
|
+
, {
|
|
34465
|
+
"aria-disabled": disabled,
|
|
34466
|
+
disabled: disabled,
|
|
34467
|
+
hover: {
|
|
34468
|
+
backgroundColor: "var(--t-fill-color-neutral-070)"
|
|
34469
|
+
},
|
|
34470
|
+
icon: {
|
|
34471
|
+
fill: "var(--t-icon-color-dim)",
|
|
34472
|
+
name: direction === "back" ? "general.leftChevron" : "general.rightChevron",
|
|
34473
|
+
size: "md"
|
|
34474
|
+
},
|
|
34475
|
+
onClick: onClick,
|
|
34476
|
+
opacity: disabled ? 0.35 : 1,
|
|
34477
|
+
theme: false,
|
|
34478
|
+
title: direction === "back" ? "Back" : "Forward"
|
|
34479
|
+
});
|
|
34480
|
+
}
|
|
34481
|
+
function DeveloperToolsComponent({
|
|
34482
|
+
closeDrawer
|
|
34483
|
+
}) {
|
|
34484
|
+
const {
|
|
34485
|
+
onFrameLoad,
|
|
34486
|
+
steps,
|
|
34487
|
+
traverse
|
|
34488
|
+
} = useFrameHistory();
|
|
34489
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
34490
|
+
closeDrawer: closeDrawer
|
|
34491
|
+
}, /*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
34492
|
+
alignment: "center",
|
|
34493
|
+
axis: "horizontal",
|
|
34494
|
+
spacing: 0.25
|
|
34495
|
+
}, /*#__PURE__*/React.createElement(NavButton, {
|
|
34496
|
+
direction: "back",
|
|
34497
|
+
disabled: steps.back === 0,
|
|
34498
|
+
onClick: () => traverse("back")
|
|
34499
|
+
}), /*#__PURE__*/React.createElement(NavButton, {
|
|
34500
|
+
direction: "forward",
|
|
34501
|
+
disabled: steps.forward === 0,
|
|
34502
|
+
onClick: () => traverse("forward")
|
|
34503
|
+
}), /*#__PURE__*/React.createElement(DrawerHeading, {
|
|
34504
|
+
paddingLeft: 0.5
|
|
34505
|
+
}, "Developer Tools"))), /*#__PURE__*/React.createElement(tapestryReact.Box, {
|
|
34506
|
+
as: "iframe",
|
|
34507
|
+
border: "none",
|
|
34508
|
+
flex: "1",
|
|
34509
|
+
minHeight: 0,
|
|
34510
|
+
onLoad: onFrameLoad,
|
|
34511
|
+
src: iframeSrc(),
|
|
34512
|
+
title: "Developer Tools",
|
|
34513
|
+
width: "100%"
|
|
34514
|
+
}));
|
|
34515
|
+
}
|
|
34516
|
+
const DeveloperTools = {
|
|
34517
|
+
component: DeveloperToolsComponent,
|
|
34518
|
+
icons: {
|
|
34519
|
+
component: ToolsIcon
|
|
34520
|
+
},
|
|
34521
|
+
label: "Developer Tools",
|
|
34522
|
+
// No space: `name` becomes an element id and a pendo attribute.
|
|
34523
|
+
name: "DeveloperTools"
|
|
34524
|
+
};
|
|
33345
34525
|
|
|
33346
|
-
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"};
|
|
34526
|
+
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"};
|
|
33347
34527
|
|
|
33348
|
-
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";
|
|
34528
|
+
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";
|
|
33349
34529
|
// Same stops as the AskAIBanner border gradient, so the two stay in sync.
|
|
33350
34530
|
const GRADIENT_STOPS = [{
|
|
33351
34531
|
color: "var(--t-fill-color-tag-teal-040)",
|
|
@@ -33394,7 +34574,7 @@ function SparkleIcon({
|
|
|
33394
34574
|
stopColor: color
|
|
33395
34575
|
}
|
|
33396
34576
|
})))))), /*#__PURE__*/React.createElement("path", {
|
|
33397
|
-
d: SPARKLE_PATH,
|
|
34577
|
+
d: SPARKLE_PATH$1,
|
|
33398
34578
|
fill: isGradient ? `url(#${gradientId})` : "currentColor"
|
|
33399
34579
|
}));
|
|
33400
34580
|
}
|
|
@@ -33403,8 +34583,8 @@ function AskAIBanner(props) {
|
|
|
33403
34583
|
var _a, _b;
|
|
33404
34584
|
const isStacked = props.variant !== "inline";
|
|
33405
34585
|
return /*#__PURE__*/React.createElement("button", {
|
|
33406
|
-
className: classnames(styles.banner, {
|
|
33407
|
-
[styles.stacked]: isStacked
|
|
34586
|
+
className: classnames(styles$1.banner, {
|
|
34587
|
+
[styles$1.stacked]: isStacked
|
|
33408
34588
|
}),
|
|
33409
34589
|
onClick: () => props.onClick(),
|
|
33410
34590
|
type: "button"
|
|
@@ -33419,15 +34599,15 @@ function StackedContent({
|
|
|
33419
34599
|
title
|
|
33420
34600
|
}) {
|
|
33421
34601
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
33422
|
-
className: styles.content
|
|
34602
|
+
className: styles$1.content
|
|
33423
34603
|
}, /*#__PURE__*/React.createElement(AskAIIcon, null), /*#__PURE__*/React.createElement("span", {
|
|
33424
|
-
className: styles.text
|
|
34604
|
+
className: styles$1.text
|
|
33425
34605
|
}, /*#__PURE__*/React.createElement("span", {
|
|
33426
|
-
className: styles.title
|
|
34606
|
+
className: styles$1.title
|
|
33427
34607
|
}, title), /*#__PURE__*/React.createElement("span", {
|
|
33428
|
-
className: styles.subtitle
|
|
34608
|
+
className: styles$1.subtitle
|
|
33429
34609
|
}, "Support answers tailored to you"))), /*#__PURE__*/React.createElement(tapestryReact.Icon, {
|
|
33430
|
-
className: styles.chevron,
|
|
34610
|
+
className: styles$1.chevron,
|
|
33431
34611
|
name: "general.rightChevron",
|
|
33432
34612
|
size: "16px"
|
|
33433
34613
|
}));
|
|
@@ -33437,18 +34617,18 @@ function InlineContent({
|
|
|
33437
34617
|
title
|
|
33438
34618
|
}) {
|
|
33439
34619
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
33440
|
-
className: styles.inlineContent
|
|
34620
|
+
className: styles$1.inlineContent
|
|
33441
34621
|
}, /*#__PURE__*/React.createElement(SparkleIcon, {
|
|
33442
|
-
className: styles.sparkle,
|
|
34622
|
+
className: styles$1.sparkle,
|
|
33443
34623
|
fill: "currentColor"
|
|
33444
34624
|
}), /*#__PURE__*/React.createElement("span", {
|
|
33445
|
-
className: styles.inlineTitle
|
|
34625
|
+
className: styles$1.inlineTitle
|
|
33446
34626
|
}, title)), /*#__PURE__*/React.createElement("span", {
|
|
33447
|
-
className: styles.action
|
|
34627
|
+
className: styles$1.action
|
|
33448
34628
|
}, /*#__PURE__*/React.createElement("span", {
|
|
33449
|
-
className: styles.actionLabel
|
|
34629
|
+
className: styles$1.actionLabel
|
|
33450
34630
|
}, action), /*#__PURE__*/React.createElement(tapestryReact.Icon, {
|
|
33451
|
-
className: styles.actionChevron,
|
|
34631
|
+
className: styles$1.actionChevron,
|
|
33452
34632
|
name: "general.rightChevron",
|
|
33453
34633
|
size: "14px"
|
|
33454
34634
|
})));
|
|
@@ -33465,9 +34645,9 @@ function AskAIIcon() {
|
|
|
33465
34645
|
const borderA = `${uid}-border-a`;
|
|
33466
34646
|
const borderB = `${uid}-border-b`;
|
|
33467
34647
|
return /*#__PURE__*/React.createElement("span", {
|
|
33468
|
-
className: styles.icon
|
|
34648
|
+
className: styles$1.icon
|
|
33469
34649
|
}, /*#__PURE__*/React.createElement("svg", {
|
|
33470
|
-
className: styles.iconShape,
|
|
34650
|
+
className: styles$1.iconShape,
|
|
33471
34651
|
viewBox: "0 0 32 32",
|
|
33472
34652
|
fill: "none",
|
|
33473
34653
|
"aria-hidden": "true"
|
|
@@ -33638,7 +34818,7 @@ function AskAIIcon() {
|
|
|
33638
34818
|
stopColor: "#D48B91",
|
|
33639
34819
|
stopOpacity: "0"
|
|
33640
34820
|
})))), /*#__PURE__*/React.createElement("svg", {
|
|
33641
|
-
className: styles.iconSparkle,
|
|
34821
|
+
className: styles$1.iconSparkle,
|
|
33642
34822
|
viewBox: "0 0 19.4995 17.9999",
|
|
33643
34823
|
"aria-hidden": "true"
|
|
33644
34824
|
}, /*#__PURE__*/React.createElement("path", {
|
|
@@ -33647,6 +34827,23 @@ function AskAIIcon() {
|
|
|
33647
34827
|
})));
|
|
33648
34828
|
}
|
|
33649
34829
|
|
|
34830
|
+
var styles = {"button":"button-2NVI1","sparkle":"sparkle-ruJGP","label":"label-4NUGB"};
|
|
34831
|
+
|
|
34832
|
+
function AskAIButton({
|
|
34833
|
+
onClick,
|
|
34834
|
+
text = "Ask AI"
|
|
34835
|
+
}) {
|
|
34836
|
+
return /*#__PURE__*/React.createElement("button", {
|
|
34837
|
+
className: styles.button,
|
|
34838
|
+
onClick: () => onClick(),
|
|
34839
|
+
type: "button"
|
|
34840
|
+
}, /*#__PURE__*/React.createElement(SparkleIcon, {
|
|
34841
|
+
className: styles.sparkle
|
|
34842
|
+
}), /*#__PURE__*/React.createElement("span", {
|
|
34843
|
+
className: styles.label
|
|
34844
|
+
}, text));
|
|
34845
|
+
}
|
|
34846
|
+
|
|
33650
34847
|
// The article heading lives in the deepest populated hierarchy level (lvl0 is
|
|
33651
34848
|
// always "Planning Center").
|
|
33652
34849
|
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) || "";
|
|
@@ -33802,16 +34999,24 @@ const DocumentationSearch = ({
|
|
|
33802
34999
|
// not the path, so updating it is a same-document navigation that doesn't
|
|
33803
35000
|
// reload the iframe, but also lets the route survive a forced reload (e.g.
|
|
33804
35001
|
// Turbo re-parenting the topbar).
|
|
33805
|
-
|
|
35002
|
+
//
|
|
35003
|
+
// `panel` loads picobot as its own toolbar panel rather than inside Helpdesk.
|
|
35004
|
+
function buildPicobotSrc(origin, path, {
|
|
35005
|
+
panel = false
|
|
35006
|
+
} = {}) {
|
|
33806
35007
|
const [rawPath, queryPart] = path.replace(/^\/+/, "").split("?", 2);
|
|
33807
35008
|
const safePath = rawPath.split("/").filter(segment => segment && segment !== "." && segment !== "..").join("/");
|
|
33808
35009
|
const fragment = queryPart !== undefined ? `${safePath}?${queryPart}` : safePath;
|
|
33809
|
-
const
|
|
35010
|
+
const url = new URL("topbar", origin);
|
|
35011
|
+
if (panel) url.searchParams.set("panel", "1");
|
|
35012
|
+
const base = url.toString();
|
|
33810
35013
|
return fragment ? `${base}#${fragment}` : base;
|
|
33811
35014
|
}
|
|
33812
35015
|
|
|
33813
35016
|
const LOAD_TIMEOUT_MS = 3000;
|
|
33814
|
-
function createPicobotIframe(picobotOrigin
|
|
35017
|
+
function createPicobotIframe(picobotOrigin, {
|
|
35018
|
+
panel = false
|
|
35019
|
+
} = {}) {
|
|
33815
35020
|
return /*#__PURE__*/React.forwardRef(function PicobotIframe({
|
|
33816
35021
|
currentPath: iframePath,
|
|
33817
35022
|
onChangePath: onIframeChangePath,
|
|
@@ -33867,7 +35072,9 @@ function createPicobotIframe(picobotOrigin) {
|
|
|
33867
35072
|
(_b = (_a = iframeRef.current) === null || _a === void 0 ? void 0 : _a.contentWindow) === null || _b === void 0 ? void 0 : _b.postMessage(message, picobotOrigin);
|
|
33868
35073
|
}
|
|
33869
35074
|
}), []);
|
|
33870
|
-
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath
|
|
35075
|
+
const iframeSrc = buildPicobotSrc(picobotOrigin, loadedPath, {
|
|
35076
|
+
panel
|
|
35077
|
+
});
|
|
33871
35078
|
return /*#__PURE__*/React.createElement("iframe", {
|
|
33872
35079
|
ref: iframeRef,
|
|
33873
35080
|
src: iframeSrc,
|
|
@@ -33906,22 +35113,28 @@ function HelpdeskComponent({
|
|
|
33906
35113
|
currentPath,
|
|
33907
35114
|
env,
|
|
33908
35115
|
helpdeskConfig,
|
|
33909
|
-
|
|
35116
|
+
isPicobotLoading,
|
|
35117
|
+
onChangePath,
|
|
35118
|
+
openPicobot
|
|
33910
35119
|
}) {
|
|
33911
35120
|
const PicobotIframe = React.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot")), [env]);
|
|
35121
|
+
const loadingFallback = /*#__PURE__*/React.createElement(StackView$1, {
|
|
35122
|
+
flex: 1,
|
|
35123
|
+
axis: "vertical"
|
|
35124
|
+
}, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
35125
|
+
closeDrawer: closeDrawer
|
|
35126
|
+
}, /*#__PURE__*/React.createElement(StackView$1, {
|
|
35127
|
+
axis: "horizontal",
|
|
35128
|
+
spacing: 1
|
|
35129
|
+
}, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null))));
|
|
35130
|
+
// The add-on reads its args once, so mounting before the gate answers
|
|
35131
|
+
// would leave out openPicobot for good.
|
|
35132
|
+
if (isPicobotLoading) return loadingFallback;
|
|
33912
35133
|
return /*#__PURE__*/React.createElement(addOns.UiExtensionFirstPartyComponent, {
|
|
33913
35134
|
grow: 1,
|
|
33914
35135
|
display: "flex",
|
|
33915
35136
|
extensionId: env === "staging" ? "42" : "11",
|
|
33916
|
-
loadingFallback:
|
|
33917
|
-
flex: 1,
|
|
33918
|
-
axis: "vertical"
|
|
33919
|
-
}, /*#__PURE__*/React.createElement(DrawerHeader, {
|
|
33920
|
-
closeDrawer: closeDrawer
|
|
33921
|
-
}, /*#__PURE__*/React.createElement(StackView$1, {
|
|
33922
|
-
axis: "horizontal",
|
|
33923
|
-
spacing: 1
|
|
33924
|
-
}, /*#__PURE__*/React.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React.createElement(LoadingIndicator$1, null)))),
|
|
35137
|
+
loadingFallback: loadingFallback,
|
|
33925
35138
|
additionalComponents: {
|
|
33926
35139
|
AskAIBanner: {
|
|
33927
35140
|
component: AskAIBanner,
|
|
@@ -33941,6 +35154,18 @@ function HelpdeskComponent({
|
|
|
33941
35154
|
}
|
|
33942
35155
|
}
|
|
33943
35156
|
},
|
|
35157
|
+
AskAIButton: {
|
|
35158
|
+
component: AskAIButton,
|
|
35159
|
+
properties: {
|
|
35160
|
+
onClick: {
|
|
35161
|
+
event: false,
|
|
35162
|
+
type: Function
|
|
35163
|
+
},
|
|
35164
|
+
text: {
|
|
35165
|
+
type: String
|
|
35166
|
+
}
|
|
35167
|
+
}
|
|
35168
|
+
},
|
|
33944
35169
|
CalendlyWidget: {
|
|
33945
35170
|
component: CalendlyWidget,
|
|
33946
35171
|
properties: {
|
|
@@ -34019,7 +35244,11 @@ function HelpdeskComponent({
|
|
|
34019
35244
|
currentPageParams: location.search,
|
|
34020
35245
|
currentPath,
|
|
34021
35246
|
helpdeskConfig,
|
|
34022
|
-
onChangePath
|
|
35247
|
+
onChangePath,
|
|
35248
|
+
// Its presence tells helpdesk that Picobot has its own panel.
|
|
35249
|
+
...(openPicobot && {
|
|
35250
|
+
openPicobot
|
|
35251
|
+
})
|
|
34023
35252
|
}
|
|
34024
35253
|
});
|
|
34025
35254
|
}
|
|
@@ -34566,6 +35795,142 @@ const Notifications = {
|
|
|
34566
35795
|
name: "Notifications"
|
|
34567
35796
|
};
|
|
34568
35797
|
|
|
35798
|
+
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";
|
|
35799
|
+
// Helpdesk reads this context off the email path to open its email form the
|
|
35800
|
+
// same way a handoff inside its own drawer does.
|
|
35801
|
+
function emailPathWithContext(context = {}) {
|
|
35802
|
+
const params = new URLSearchParams();
|
|
35803
|
+
for (const key of ["conversationId", "source", "turnIndex"]) {
|
|
35804
|
+
const value = context[key];
|
|
35805
|
+
if (value !== null && value !== undefined) params.set(key, String(value));
|
|
35806
|
+
}
|
|
35807
|
+
return `email?${params}`;
|
|
35808
|
+
}
|
|
35809
|
+
function PicobotComponent({
|
|
35810
|
+
clearPending,
|
|
35811
|
+
closeDrawer,
|
|
35812
|
+
currentPath,
|
|
35813
|
+
env,
|
|
35814
|
+
onChangePath,
|
|
35815
|
+
openTool,
|
|
35816
|
+
pending
|
|
35817
|
+
}) {
|
|
35818
|
+
const PicobotIframe = React.useMemo(() => createPicobotIframe(pcoUrl(env)("picobot"), {
|
|
35819
|
+
panel: true
|
|
35820
|
+
}), [env]);
|
|
35821
|
+
const picobotRef = React.useRef(null);
|
|
35822
|
+
const [isPicobotReady, setIsPicobotReady] = React.useState(false);
|
|
35823
|
+
// Picobot has no listener until it sends READY, so openPicobot's draft or
|
|
35824
|
+
// conversation waits for it.
|
|
35825
|
+
React.useEffect(function handOverPendingOncePicobotIsReady() {
|
|
35826
|
+
var _a, _b;
|
|
35827
|
+
if (!pending || !isPicobotReady) return;
|
|
35828
|
+
const {
|
|
35829
|
+
conversationId,
|
|
35830
|
+
draft,
|
|
35831
|
+
ticketExternalId
|
|
35832
|
+
} = pending;
|
|
35833
|
+
if (conversationId) {
|
|
35834
|
+
(_a = picobotRef.current) === null || _a === void 0 ? void 0 : _a.postMessage({
|
|
35835
|
+
payload: {
|
|
35836
|
+
conversation_id: conversationId,
|
|
35837
|
+
// Picobot asks whether this resolved the ticket, and closes it.
|
|
35838
|
+
...(ticketExternalId && {
|
|
35839
|
+
external_id: ticketExternalId,
|
|
35840
|
+
source: "ticket_submitted"
|
|
35841
|
+
})
|
|
35842
|
+
},
|
|
35843
|
+
type: "LOAD_CONVERSATION"
|
|
35844
|
+
});
|
|
35845
|
+
} else if (draft) {
|
|
35846
|
+
// Not both: Picobot cancels a conversation load when a prefill
|
|
35847
|
+
// starts a new conversation.
|
|
35848
|
+
(_b = picobotRef.current) === null || _b === void 0 ? void 0 : _b.postMessage({
|
|
35849
|
+
payload: {
|
|
35850
|
+
source: "email_form",
|
|
35851
|
+
text: draft
|
|
35852
|
+
},
|
|
35853
|
+
type: "PREFILL_COMPOSER"
|
|
35854
|
+
});
|
|
35855
|
+
}
|
|
35856
|
+
clearPending();
|
|
35857
|
+
}, [pending, isPicobotReady, clearPending]);
|
|
35858
|
+
const [iframeErrored, setIframeErrored] = React.useState(false);
|
|
35859
|
+
const [mountKey, setMountKey] = React.useState(0);
|
|
35860
|
+
const handleRetry = React.useCallback(() => {
|
|
35861
|
+
setIframeErrored(false);
|
|
35862
|
+
setIsPicobotReady(false);
|
|
35863
|
+
setMountKey(key => key + 1);
|
|
35864
|
+
}, []);
|
|
35865
|
+
const handleMessage = React.useCallback(data => {
|
|
35866
|
+
var _a;
|
|
35867
|
+
const message = data;
|
|
35868
|
+
switch (message === null || message === void 0 ? void 0 : message.type) {
|
|
35869
|
+
case "CLOSE_DRAWER":
|
|
35870
|
+
return closeDrawer();
|
|
35871
|
+
case "READY":
|
|
35872
|
+
return setIsPicobotReady(true);
|
|
35873
|
+
case "PICOBOT_SUPPORT_HANDOFF":
|
|
35874
|
+
return openTool("helpdesk", ((_a = message.payload) === null || _a === void 0 ? void 0 : _a.method) === "zoom" ? "phone" : emailPathWithContext(message.payload));
|
|
35875
|
+
case "IFRAME_LOAD_ERROR":
|
|
35876
|
+
console.warn("topbar: Picobot iframe failed to load");
|
|
35877
|
+
return setIframeErrored(true);
|
|
35878
|
+
}
|
|
35879
|
+
}, [closeDrawer, openTool]);
|
|
35880
|
+
if (iframeErrored) {
|
|
35881
|
+
return /*#__PURE__*/React.createElement(LoadError, {
|
|
35882
|
+
onContactSupport: () => openTool("helpdesk", "email"),
|
|
35883
|
+
onRetry: handleRetry
|
|
35884
|
+
});
|
|
35885
|
+
}
|
|
35886
|
+
return /*#__PURE__*/React.createElement(PicobotIframe, {
|
|
35887
|
+
key: mountKey,
|
|
35888
|
+
ref: picobotRef,
|
|
35889
|
+
currentPath: currentPath,
|
|
35890
|
+
onChangePath: onChangePath,
|
|
35891
|
+
onMessage: handleMessage
|
|
35892
|
+
});
|
|
35893
|
+
}
|
|
35894
|
+
function LoadError({
|
|
35895
|
+
onContactSupport,
|
|
35896
|
+
onRetry
|
|
35897
|
+
}) {
|
|
35898
|
+
return /*#__PURE__*/React.createElement(tapestryReact.StackView, {
|
|
35899
|
+
axis: "vertical",
|
|
35900
|
+
flex: 1,
|
|
35901
|
+
alignment: "center",
|
|
35902
|
+
distribution: "center",
|
|
35903
|
+
padding: 3,
|
|
35904
|
+
spacing: 1
|
|
35905
|
+
}, /*#__PURE__*/React.createElement(tapestryReact.Heading, {
|
|
35906
|
+
level: 2
|
|
35907
|
+
}, "Picobot is temporarily unavailable"), /*#__PURE__*/React.createElement(tapestryReact.Text, {
|
|
35908
|
+
size: 5,
|
|
35909
|
+
color: "var(--t-text-color-secondary)"
|
|
35910
|
+
}, "Try again or contact support."), /*#__PURE__*/React.createElement(tapestryReact.Box, {
|
|
35911
|
+
paddingTop: 2
|
|
35912
|
+
}, /*#__PURE__*/React.createElement(tapestryReact.Button, {
|
|
35913
|
+
onClick: onRetry,
|
|
35914
|
+
iconLeft: "general.refresh",
|
|
35915
|
+
theme: "primary",
|
|
35916
|
+
variant: "fill",
|
|
35917
|
+
title: "Retry"
|
|
35918
|
+
})), /*#__PURE__*/React.createElement(tapestryReact.Button, {
|
|
35919
|
+
onClick: onContactSupport,
|
|
35920
|
+
variant: "naked",
|
|
35921
|
+
theme: "primary",
|
|
35922
|
+
title: "Contact support"
|
|
35923
|
+
}));
|
|
35924
|
+
}
|
|
35925
|
+
const Picobot = {
|
|
35926
|
+
component: PicobotComponent,
|
|
35927
|
+
icons: {
|
|
35928
|
+
staticDotPath: SPARKLE_PATH,
|
|
35929
|
+
staticPath: SPARKLE_PATH
|
|
35930
|
+
},
|
|
35931
|
+
name: "Picobot"
|
|
35932
|
+
};
|
|
35933
|
+
|
|
34569
35934
|
const {
|
|
34570
35935
|
LoadingIndicator,
|
|
34571
35936
|
StackView
|
|
@@ -34961,6 +36326,41 @@ const useChatConfig = ({
|
|
|
34961
36326
|
};
|
|
34962
36327
|
};
|
|
34963
36328
|
|
|
36329
|
+
// Picobot's API rejects requests without a version header, and apiRequest
|
|
36330
|
+
// sets its own headers, so the version rides in on the fetch.
|
|
36331
|
+
const picobotFetch = (url, opts) => defaultFetch(url, {
|
|
36332
|
+
...opts,
|
|
36333
|
+
headers: {
|
|
36334
|
+
...(opts === null || opts === void 0 ? void 0 : opts.headers),
|
|
36335
|
+
"X-PCO-API-Version": "2026-08-11"
|
|
36336
|
+
}
|
|
36337
|
+
});
|
|
36338
|
+
// Routes wait on the gate, so a slow Picobot can't hold them up for long.
|
|
36339
|
+
const GATE_TIMEOUT_MS = 3000;
|
|
36340
|
+
const usePicobotConfig = ({
|
|
36341
|
+
isEnabled
|
|
36342
|
+
}) => {
|
|
36343
|
+
// Undefined until Picobot answers.
|
|
36344
|
+
const [panelEnabled, setPanelEnabled] = React.useState();
|
|
36345
|
+
React.useEffect(() => {
|
|
36346
|
+
if (!isEnabled) return undefined;
|
|
36347
|
+
// Whatever settles first wins, so a late answer can't reopen the gate.
|
|
36348
|
+
const settle = enabled => setPanelEnabled(current => current !== null && current !== void 0 ? current : enabled);
|
|
36349
|
+
const timeout = setTimeout(() => settle(false), GATE_TIMEOUT_MS);
|
|
36350
|
+
apiRequest(picobotFetch, `${window.location.origin}/~api/picobot/v2/me`).then(({
|
|
36351
|
+
json
|
|
36352
|
+
}) => {
|
|
36353
|
+
var _a, _b;
|
|
36354
|
+
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);
|
|
36355
|
+
}).catch(() => settle(false)).finally(() => clearTimeout(timeout));
|
|
36356
|
+
return () => clearTimeout(timeout);
|
|
36357
|
+
}, [isEnabled]);
|
|
36358
|
+
return {
|
|
36359
|
+
displayPicobotIcon: panelEnabled === true,
|
|
36360
|
+
isLoading: isEnabled && panelEnabled === undefined
|
|
36361
|
+
};
|
|
36362
|
+
};
|
|
36363
|
+
|
|
34964
36364
|
const Profile = {
|
|
34965
36365
|
component: UserProfile,
|
|
34966
36366
|
icons: {
|
|
@@ -34971,10 +36371,16 @@ const Profile = {
|
|
|
34971
36371
|
};
|
|
34972
36372
|
|
|
34973
36373
|
const DURATION = 100;
|
|
36374
|
+
// Exempt paths match regardless of query, so `email?conversationId=…` is
|
|
36375
|
+
// exempt along with `email`.
|
|
34974
36376
|
function isToolPathExemptFromStorage(toolName, path) {
|
|
34975
36377
|
var _a, _b;
|
|
34976
36378
|
const tool = toolsRouteMap[toolName];
|
|
34977
|
-
|
|
36379
|
+
const [pathWithoutQuery] = path.split("?");
|
|
36380
|
+
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;
|
|
36381
|
+
}
|
|
36382
|
+
function isConversationPath(path) {
|
|
36383
|
+
return Boolean(path === null || path === void 0 ? void 0 : path.startsWith("conversations/"));
|
|
34978
36384
|
}
|
|
34979
36385
|
function scrollToolDrawerToTop(toolName) {
|
|
34980
36386
|
var _a;
|
|
@@ -34987,6 +36393,7 @@ const TOOLS_MAP = {
|
|
|
34987
36393
|
Chat,
|
|
34988
36394
|
Helpdesk,
|
|
34989
36395
|
Notifications,
|
|
36396
|
+
Picobot,
|
|
34990
36397
|
Profile,
|
|
34991
36398
|
Tasks
|
|
34992
36399
|
};
|
|
@@ -35014,7 +36421,8 @@ function Toolbar({
|
|
|
35014
36421
|
});
|
|
35015
36422
|
const {
|
|
35016
36423
|
currentContent,
|
|
35017
|
-
open
|
|
36424
|
+
open,
|
|
36425
|
+
pending
|
|
35018
36426
|
} = state;
|
|
35019
36427
|
const mounted = !!currentContent;
|
|
35020
36428
|
const storeData = useStore();
|
|
@@ -35027,6 +36435,15 @@ function Toolbar({
|
|
|
35027
36435
|
...(env === "development" ? _storeDataOverride !== null && _storeDataOverride !== void 0 ? _storeDataOverride : {} : {})
|
|
35028
36436
|
};
|
|
35029
36437
|
const updateStore = useUpdateStore();
|
|
36438
|
+
const {
|
|
36439
|
+
displayPicobotIcon,
|
|
36440
|
+
isLoading: isPicobotGateLoading
|
|
36441
|
+
} = usePicobotConfig({
|
|
36442
|
+
isEnabled: Boolean(userId)
|
|
36443
|
+
});
|
|
36444
|
+
// Picobot is a kill-switched tool: until its gate says the panel is
|
|
36445
|
+
// enabled, its routes are held, and if it never does, they're ignored.
|
|
36446
|
+
const routableTool = React.useCallback(name => name === Picobot.name.toLowerCase() && !displayPicobotIcon ? undefined : toolsRouteMap[name], [displayPicobotIcon]);
|
|
35030
36447
|
React.useEffect(() => {
|
|
35031
36448
|
if (open) {
|
|
35032
36449
|
document.body.style.overflow = "hidden";
|
|
@@ -35114,7 +36531,9 @@ function Toolbar({
|
|
|
35114
36531
|
});
|
|
35115
36532
|
}, [clearRoute]);
|
|
35116
36533
|
React.useEffect(() => {
|
|
35117
|
-
if (!toolbarFragmentRoute)
|
|
36534
|
+
if (!toolbarFragmentRoute || !routableTool(toolbarFragmentRoute.tool)) {
|
|
36535
|
+
return;
|
|
36536
|
+
}
|
|
35118
36537
|
const isExempt = isToolPathExemptFromStorage(toolbarFragmentRoute.tool, toolbarFragmentRoute.path);
|
|
35119
36538
|
if (isExempt) return;
|
|
35120
36539
|
const nextState = {
|
|
@@ -35124,12 +36543,32 @@ function Toolbar({
|
|
|
35124
36543
|
if (!shallowCompare(nextState, toolStateCookie)) {
|
|
35125
36544
|
setToolStateCookie(nextState);
|
|
35126
36545
|
}
|
|
35127
|
-
}, [toolbarFragmentRoute, toolStateCookie]);
|
|
36546
|
+
}, [toolbarFragmentRoute, toolStateCookie, routableTool]);
|
|
35128
36547
|
React.useEffect(() => {
|
|
35129
|
-
|
|
35130
|
-
|
|
36548
|
+
// Helpdesk conversations may belong to Picobot, so they wait for its gate
|
|
36549
|
+
// rather than flashing open in Helpdesk first.
|
|
36550
|
+
const mayRedirectToPicobot = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path) && (isPicobotGateLoading || displayPicobotIcon);
|
|
36551
|
+
const tool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && !mayRedirectToPicobot && routableTool(toolbarFragmentRoute.tool);
|
|
36552
|
+
if (tool && (currentContent !== tool || !open)) {
|
|
36553
|
+
setToolAndOpenDrawer(tool);
|
|
35131
36554
|
}
|
|
35132
|
-
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, currentContent, open]);
|
|
36555
|
+
}, [setToolAndOpenDrawer, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path, currentContent, open, routableTool, isPicobotGateLoading, displayPicobotIcon]);
|
|
36556
|
+
const openTool = React.useCallback((name, path, pendingValue) => {
|
|
36557
|
+
const tool = routableTool(name.toLowerCase());
|
|
36558
|
+
if (!tool) return;
|
|
36559
|
+
dispatch({
|
|
36560
|
+
payload: pendingValue === undefined ? null : {
|
|
36561
|
+
tool: tool.name,
|
|
36562
|
+
value: pendingValue
|
|
36563
|
+
},
|
|
36564
|
+
type: ActionType.setPending
|
|
36565
|
+
});
|
|
36566
|
+
onChangeToolbarFragmentRoute(tool.name.toLowerCase(), path);
|
|
36567
|
+
scrollToolDrawerToTop(tool.name);
|
|
36568
|
+
}, [onChangeToolbarFragmentRoute, routableTool]);
|
|
36569
|
+
const clearPending = React.useCallback(() => dispatch({
|
|
36570
|
+
type: ActionType.clearPending
|
|
36571
|
+
}), []);
|
|
35133
36572
|
const toggleDrawer = content => {
|
|
35134
36573
|
if (!open) {
|
|
35135
36574
|
return openDrawer(content);
|
|
@@ -35146,12 +36585,52 @@ function Toolbar({
|
|
|
35146
36585
|
joltPromise: storeData === null || storeData === void 0 ? void 0 : storeData.joltPromise,
|
|
35147
36586
|
userId
|
|
35148
36587
|
});
|
|
35149
|
-
|
|
36588
|
+
// Picobot conversations moved from Helpdesk to the Picobot panel, so
|
|
36589
|
+
// send old links and saved paths there once the panel is enabled.
|
|
36590
|
+
React.useEffect(() => {
|
|
36591
|
+
if (!displayPicobotIcon) return;
|
|
36592
|
+
if ((toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) === Helpdesk.name.toLowerCase() && isConversationPath(toolbarFragmentRoute.path)) {
|
|
36593
|
+
onChangeToolbarFragmentRoute(Picobot.name.toLowerCase(), toolbarFragmentRoute.path);
|
|
36594
|
+
}
|
|
36595
|
+
}, [displayPicobotIcon, toolbarFragmentRoute, onChangeToolbarFragmentRoute]);
|
|
36596
|
+
React.useEffect(() => {
|
|
36597
|
+
if (!displayPicobotIcon) return;
|
|
36598
|
+
const {
|
|
36599
|
+
helpdesk,
|
|
36600
|
+
picobot
|
|
36601
|
+
} = toolStateCookie;
|
|
36602
|
+
if (!isConversationPath(helpdesk)) return;
|
|
36603
|
+
setToolStateCookie({
|
|
36604
|
+
...toolStateCookie,
|
|
36605
|
+
helpdesk: "",
|
|
36606
|
+
picobot: picobot || helpdesk
|
|
36607
|
+
});
|
|
36608
|
+
}, [displayPicobotIcon, toolStateCookie]);
|
|
36609
|
+
const openPicobot = React.useCallback(options => {
|
|
36610
|
+
var _a;
|
|
36611
|
+
// Moving an open Picobot off its path reloads or navigates the iframe,
|
|
36612
|
+
// which would race the options it's being handed.
|
|
36613
|
+
const path = currentContent === Picobot ? (_a = toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path) !== null && _a !== void 0 ? _a : "" : "";
|
|
36614
|
+
openTool(Picobot.name, path, options);
|
|
36615
|
+
}, [openTool, currentContent, toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path]);
|
|
36616
|
+
React.useEffect(() => {
|
|
36617
|
+
if (!displayPicobotIcon) return undefined;
|
|
36618
|
+
window.topbar = window.topbar || {};
|
|
36619
|
+
window.topbar.openPicobot = openPicobot;
|
|
36620
|
+
return () => {
|
|
36621
|
+
delete window.topbar.openPicobot;
|
|
36622
|
+
};
|
|
36623
|
+
}, [displayPicobotIcon, openPicobot]);
|
|
36624
|
+
const TOOLS = [Notifications, Tasks, displayChatIcon && Chat, displayPicobotIcon && Picobot, isPcoTestHost() && DeveloperTools].filter(Boolean);
|
|
35150
36625
|
const handleToolOnClick = tool => {
|
|
35151
36626
|
toggleDrawer(tool);
|
|
35152
36627
|
scrollToolDrawerToTop(tool.name);
|
|
35153
36628
|
};
|
|
35154
36629
|
const toolSpecificProps = {
|
|
36630
|
+
Helpdesk: {
|
|
36631
|
+
isPicobotLoading: isPicobotGateLoading,
|
|
36632
|
+
openPicobot: displayPicobotIcon ? openPicobot : undefined
|
|
36633
|
+
},
|
|
35155
36634
|
Profile: {
|
|
35156
36635
|
appName,
|
|
35157
36636
|
closeDrawer,
|
|
@@ -35174,6 +36653,7 @@ function Toolbar({
|
|
|
35174
36653
|
const routeIsForCurrentTool = (toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.tool) && toolsRouteMap[toolbarFragmentRoute.tool] === currentContent;
|
|
35175
36654
|
const currentToolProps = {
|
|
35176
36655
|
appName,
|
|
36656
|
+
clearPending,
|
|
35177
36657
|
closeDrawer,
|
|
35178
36658
|
currentPath: routeIsForCurrentTool ? toolbarFragmentRoute === null || toolbarFragmentRoute === void 0 ? void 0 : toolbarFragmentRoute.path : null,
|
|
35179
36659
|
env,
|
|
@@ -35182,6 +36662,8 @@ function Toolbar({
|
|
|
35182
36662
|
joltPromise,
|
|
35183
36663
|
onChangePath,
|
|
35184
36664
|
openDrawer: () => currentContent && openDrawer(currentContent),
|
|
36665
|
+
openTool,
|
|
36666
|
+
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,
|
|
35185
36667
|
userId,
|
|
35186
36668
|
...(currentContent ? toolSpecificProps[currentContent.name] : {})
|
|
35187
36669
|
};
|
|
@@ -35192,38 +36674,37 @@ function Toolbar({
|
|
|
35192
36674
|
return /*#__PURE__*/React.createElement(tapestryReact.ThemeProvider, {
|
|
35193
36675
|
theme: topbarTheme
|
|
35194
36676
|
}, /*#__PURE__*/React.createElement(addOns.UiExtensionDataContext, null, /*#__PURE__*/React.createElement("div", {
|
|
35195
|
-
className: styles$
|
|
36677
|
+
className: styles$2["toolbar"],
|
|
35196
36678
|
"data-pendo": "toolbar-profile-support-wrapper"
|
|
35197
36679
|
}, /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
35198
36680
|
"aria-label": `Open ${userName}’s profile drawer`,
|
|
35199
36681
|
onClick: () => toggleDrawer(Profile),
|
|
35200
36682
|
"data-pendo": "toolbar-profile",
|
|
35201
|
-
className: styles$
|
|
36683
|
+
className: styles$2["toolbar-button--avatar"]
|
|
35202
36684
|
}, /*#__PURE__*/React.createElement(Avatar, {
|
|
35203
36685
|
env: env,
|
|
35204
36686
|
url: userAvatarPath
|
|
35205
36687
|
})), /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
35206
|
-
className: styles$
|
|
36688
|
+
className: styles$2["toolbar-button--home"],
|
|
35207
36689
|
onClick: () => closeDrawer()
|
|
35208
36690
|
}, /*#__PURE__*/React.createElement(tapestryReact.Icon, {
|
|
35209
36691
|
name: "toolbar.planningCenter",
|
|
35210
36692
|
size: "24px"
|
|
35211
36693
|
})), [HelpDeskTool, ...TOOLS].map(tool => {
|
|
36694
|
+
var _a;
|
|
35212
36695
|
const current = open && currentContent === tool;
|
|
35213
36696
|
const indicator = indicatorState[tool.name];
|
|
35214
36697
|
return /*#__PURE__*/React.createElement(ToolbarButton, {
|
|
35215
36698
|
current: current,
|
|
35216
36699
|
onClick: () => handleToolOnClick(tool),
|
|
35217
36700
|
id: getDataPendoAttribute(tool.name),
|
|
35218
|
-
"aria-label": `Open ${tool.name} drawer`,
|
|
36701
|
+
"aria-label": `Open ${(_a = tool.label) !== null && _a !== void 0 ? _a : tool.name} drawer`,
|
|
35219
36702
|
"data-pendo": getDataPendoAttribute(tool.name),
|
|
35220
36703
|
key: tool.name
|
|
35221
|
-
},
|
|
35222
|
-
|
|
35223
|
-
|
|
35224
|
-
|
|
35225
|
-
dot: true
|
|
35226
|
-
})));
|
|
36704
|
+
}, /*#__PURE__*/React.createElement(ToolbarToolIcon, {
|
|
36705
|
+
icons: tool.icons,
|
|
36706
|
+
dot: !!indicator
|
|
36707
|
+
}));
|
|
35227
36708
|
})), /*#__PURE__*/React.createElement(DrawerWrap, {
|
|
35228
36709
|
isVisible: open
|
|
35229
36710
|
}, mounted && (/*#__PURE__*/React.createElement(Drawer, {
|
|
@@ -35241,12 +36722,25 @@ const ToolbarButton = ({
|
|
|
35241
36722
|
}) => {
|
|
35242
36723
|
return /*#__PURE__*/React.createElement("button", {
|
|
35243
36724
|
type: "button",
|
|
35244
|
-
className: classnames(styles$
|
|
36725
|
+
className: classnames(styles$2["toolbar-button"], className, current && styles$2["is-current"]),
|
|
35245
36726
|
onClick: onClick,
|
|
35246
36727
|
"data-toolbar-link": "true",
|
|
35247
36728
|
...restProps
|
|
35248
36729
|
}, children);
|
|
35249
36730
|
};
|
|
36731
|
+
function ToolbarToolIcon({
|
|
36732
|
+
dot,
|
|
36733
|
+
icons
|
|
36734
|
+
}) {
|
|
36735
|
+
if ("component" in icons) {
|
|
36736
|
+
return /*#__PURE__*/React.createElement(icons.component, null);
|
|
36737
|
+
} else {
|
|
36738
|
+
return /*#__PURE__*/React.createElement(ToolbarButtonIcon, {
|
|
36739
|
+
path: dot ? icons.staticDotPath : icons.staticPath,
|
|
36740
|
+
dot: dot
|
|
36741
|
+
});
|
|
36742
|
+
}
|
|
36743
|
+
}
|
|
35250
36744
|
function ToolbarButtonIcon({
|
|
35251
36745
|
dot,
|
|
35252
36746
|
path
|