@planningcenter/topbar 11.2.5 → 12.0.0-qa-832.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/topbar.js +935 -50
- package/dist/topbar.js.map +1 -1
- package/dist/topbar.modern.js +936 -51
- package/dist/topbar.modern.js.map +1 -1
- package/dist/topbar.module.js +936 -51
- package/dist/topbar.module.js.map +1 -1
- package/dist/topbar.umd.js +938 -54
- package/dist/topbar.umd.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/package.json +6 -4
package/dist/topbar.modern.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Link } from '@planningcenter/tapestry';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { useState, useEffect, useMemo, useLayoutEffect, useCallback, useRef } from 'react';
|
|
3
|
+
import React__default, { useState, useEffect, useMemo, useLayoutEffect, useContext, createContext, useCallback, useRef } from 'react';
|
|
4
4
|
import pcoUrl$1 from '@planningcenter/url';
|
|
5
5
|
import * as general from '@planningcenter/icons/paths/general';
|
|
6
6
|
import Jolt from '@planningcenter/jolt-client';
|
|
7
7
|
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
8
|
+
import 'react/jsx-runtime';
|
|
8
9
|
import { ThemeProvider, Divider, Button, StackView as StackView$2, Text, Icon, designSystem, Box, Heading, GridView, Avatar as Avatar$1, Link as Link$1, HeadingUppercase, List, Combobox, Logo, Dropdown, Spinner, Badge } from '@planningcenter/tapestry-react';
|
|
9
10
|
import { components, UiExtensionFirstPartyComponent, UiExtensionDataContext } from '@planningcenter/add-ons';
|
|
10
11
|
import * as churchCenter from '@planningcenter/icons/paths/church-center';
|
|
@@ -940,8 +941,8 @@ const AppsProvider = ({
|
|
|
940
941
|
const notificationsEnabled = !window.notificationsDisabled;
|
|
941
942
|
const organizationTimeZone = ((_k = (_j = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _j === void 0 ? void 0 : _j.attributes) === null || _k === void 0 ? void 0 : _k.org_time_zone) || "America/Los_Angeles";
|
|
942
943
|
const algoliaSearchAuth = {
|
|
943
|
-
algoliaApplicationId: (_m = (_l = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _l === void 0 ? void 0 : _l.attributes) === null || _m === void 0 ? void 0 : _m.
|
|
944
|
-
algoliaSearchOnlyApiKey: (_p = (_o = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _o === void 0 ? void 0 : _o.attributes) === null || _p === void 0 ? void 0 : _p.
|
|
944
|
+
algoliaApplicationId: (_m = (_l = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _l === void 0 ? void 0 : _l.attributes) === null || _m === void 0 ? void 0 : _m.algolia_help_center_application_identifier,
|
|
945
|
+
algoliaSearchOnlyApiKey: (_p = (_o = topbarResponse === null || topbarResponse === void 0 ? void 0 : topbarResponse.data) === null || _o === void 0 ? void 0 : _o.attributes) === null || _p === void 0 ? void 0 : _p.algolia_help_center_search_key
|
|
945
946
|
};
|
|
946
947
|
const joltPromise = useMemo(() => {
|
|
947
948
|
return resolveJoltPromise({
|
|
@@ -2224,10 +2225,750 @@ const sortByAttribute = attr => arr => arr.slice().sort((a, b) => a[attr].locale
|
|
|
2224
2225
|
const appsMenuFormatter = apps => sortByAttribute("name")(apps).reduce((acc, item) => (item.name || "") === "Church Center" || (item.name || "") === "API" ? acc : item.name === "Home" ? [item, ...acc] : [...acc, item], []);
|
|
2225
2226
|
const connectedPeopleMenuFormatter = connectedPeople => connectedPeople.slice().sort((a, b) => a.attributes["organization_name"].localeCompare(b.attributes["organization_name"]));
|
|
2226
2227
|
|
|
2228
|
+
// Generated by scripts/write-version.cjs from package.json. Do not edit.
|
|
2229
|
+
const BRIDGE_VERSION = "0.0.7-qa-41.0";
|
|
2230
|
+
|
|
2231
|
+
/**
|
|
2232
|
+
* PCA_BRIDGE_BUS_INIT — the script injected via
|
|
2233
|
+
* `injectedJavaScriptBeforeContentLoaded` before any web content loads.
|
|
2234
|
+
*
|
|
2235
|
+
* Installs `window.__pcaBridge` in the WebView's JS context. The bus:
|
|
2236
|
+
* - is per-WebView-context-by-construction (each WebView has its own JS
|
|
2237
|
+
* context; the global lives only in that context)
|
|
2238
|
+
* - accepts multiple registered handlers per reserved message type so
|
|
2239
|
+
* multiple Bridge instances in the same WebView can coexist
|
|
2240
|
+
* - buffers messages of a type with no registered handlers for up to 5s
|
|
2241
|
+
* - pings native with `__bridge/busReady` after install so the native
|
|
2242
|
+
* transport can flush its outgoing queue and trigger `reannounceLocal()`
|
|
2243
|
+
*
|
|
2244
|
+
* The bus runs as an IIFE so consumers can dispatch and register against it
|
|
2245
|
+
* immediately. Internal state (buffers, handlers) is closure-private.
|
|
2246
|
+
*/
|
|
2247
|
+
|
|
2248
|
+
const PCA_BRIDGE_BUS_INIT = `
|
|
2249
|
+
(function () {
|
|
2250
|
+
if (typeof window === 'undefined') return
|
|
2251
|
+
if (window.__pcaBridge) return // idempotent in case it's injected twice
|
|
2252
|
+
|
|
2253
|
+
var BRIDGE_VERSION = ${JSON.stringify(BRIDGE_VERSION)}
|
|
2254
|
+
var BUFFER_TTL_MS = 5000
|
|
2255
|
+
var handlers = Object.create(null) // type -> Array<fn>
|
|
2256
|
+
var buffer = Object.create(null) // type -> Array<{msg, receivedAt}>
|
|
2257
|
+
var logger = {
|
|
2258
|
+
debug: function () {},
|
|
2259
|
+
warn: function (msg, ctx) {
|
|
2260
|
+
try { console.warn('[pca-bridge]', msg, ctx) } catch (e) {}
|
|
2261
|
+
},
|
|
2262
|
+
error: function (msg, ctx) {
|
|
2263
|
+
try { console.error('[pca-bridge]', msg, ctx) } catch (e) {}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function now() { return Date.now() }
|
|
2268
|
+
|
|
2269
|
+
function sweepBuffer() {
|
|
2270
|
+
var cutoff = now() - BUFFER_TTL_MS
|
|
2271
|
+
var dropped = []
|
|
2272
|
+
var hasAny = false
|
|
2273
|
+
for (var type in buffer) {
|
|
2274
|
+
var entries = buffer[type]
|
|
2275
|
+
var kept = []
|
|
2276
|
+
for (var i = 0; i < entries.length; i++) {
|
|
2277
|
+
if (entries[i].receivedAt >= cutoff) {
|
|
2278
|
+
kept.push(entries[i])
|
|
2279
|
+
} else {
|
|
2280
|
+
dropped.push({ type: type, ageMs: now() - entries[i].receivedAt })
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (kept.length === 0) delete buffer[type]
|
|
2284
|
+
else {
|
|
2285
|
+
buffer[type] = kept
|
|
2286
|
+
hasAny = true
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
if (dropped.length > 0 && window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === 'function') {
|
|
2290
|
+
try {
|
|
2291
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
2292
|
+
type: '__bridge/drop',
|
|
2293
|
+
bridgeVersion: BRIDGE_VERSION,
|
|
2294
|
+
messages: dropped
|
|
2295
|
+
}))
|
|
2296
|
+
} catch (e) {}
|
|
2297
|
+
}
|
|
2298
|
+
// Stop ticking when the buffer drains; ensureSweep() restarts on demand.
|
|
2299
|
+
if (!hasAny && sweepInterval !== null) {
|
|
2300
|
+
clearInterval(sweepInterval)
|
|
2301
|
+
sweepInterval = null
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
var sweepInterval = null
|
|
2306
|
+
function ensureSweep() {
|
|
2307
|
+
if (sweepInterval !== null) return
|
|
2308
|
+
sweepInterval = setInterval(sweepBuffer, 1000)
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
function register(type, fn) {
|
|
2312
|
+
if (!handlers[type]) handlers[type] = []
|
|
2313
|
+
handlers[type].push(fn)
|
|
2314
|
+
// Drain any buffered messages for this type, applying the same TTL the
|
|
2315
|
+
// background sweep uses so entries that already expired don't get
|
|
2316
|
+
// delivered just because sweep hasn't run yet.
|
|
2317
|
+
var pending = buffer[type]
|
|
2318
|
+
if (pending && pending.length > 0) {
|
|
2319
|
+
delete buffer[type]
|
|
2320
|
+
var cutoff = now() - BUFFER_TTL_MS
|
|
2321
|
+
for (var i = 0; i < pending.length; i++) {
|
|
2322
|
+
if (pending[i].receivedAt < cutoff) continue
|
|
2323
|
+
try { fn(pending[i].msg) } catch (e) {
|
|
2324
|
+
logger.error('Handler threw while draining buffer', { type: type, error: e })
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
function unregister(type, fn) {
|
|
2331
|
+
var arr = handlers[type]
|
|
2332
|
+
if (!arr) return
|
|
2333
|
+
var idx = arr.indexOf(fn)
|
|
2334
|
+
if (idx >= 0) arr.splice(idx, 1)
|
|
2335
|
+
if (arr.length === 0) delete handlers[type]
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
function dispatch(msg) {
|
|
2339
|
+
if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') return
|
|
2340
|
+
var type = msg.type
|
|
2341
|
+
var arr = handlers[type]
|
|
2342
|
+
if (arr && arr.length > 0) {
|
|
2343
|
+
// Snapshot the array to avoid issues if a handler registers/unregisters during dispatch
|
|
2344
|
+
var snapshot = arr.slice()
|
|
2345
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
2346
|
+
try { snapshot[i](msg) } catch (e) {
|
|
2347
|
+
logger.error('Handler threw during dispatch', { type: type, error: e })
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
} else {
|
|
2351
|
+
if (!buffer[type]) buffer[type] = []
|
|
2352
|
+
buffer[type].push({ msg: msg, receivedAt: now() })
|
|
2353
|
+
ensureSweep()
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
function setLogger(next) {
|
|
2358
|
+
// Validate both methods the bus actually calls; a partial logger would
|
|
2359
|
+
// throw TypeError when dispatch/register tries to invoke a missing one.
|
|
2360
|
+
if (next && typeof next.warn === 'function' && typeof next.error === 'function') {
|
|
2361
|
+
logger = next
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
window.__pcaBridge = {
|
|
2366
|
+
register: register,
|
|
2367
|
+
unregister: unregister,
|
|
2368
|
+
dispatch: dispatch,
|
|
2369
|
+
setLogger: setLogger
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// Ping native: the bus is ready in this JS context. Native transport
|
|
2373
|
+
// listens for this to (a) flush its outgoing queue and (b) call
|
|
2374
|
+
// bridge.reannounceLocal() so the freshly-loaded web context learns about
|
|
2375
|
+
// already-exposed local actions/values.
|
|
2376
|
+
try {
|
|
2377
|
+
if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === 'function') {
|
|
2378
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({ type: '__bridge/busReady', bridgeVersion: BRIDGE_VERSION }))
|
|
2379
|
+
}
|
|
2380
|
+
} catch (e) {}
|
|
2381
|
+
})();
|
|
2382
|
+
true;
|
|
2383
|
+
`;
|
|
2384
|
+
|
|
2385
|
+
class TimeoutError extends Error {
|
|
2386
|
+
constructor(name, timeoutMs) {
|
|
2387
|
+
super(`Call to "${name}" timed out after ${timeoutMs}ms`);
|
|
2388
|
+
this.name = 'TimeoutError';
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
class HandlerError extends Error {
|
|
2392
|
+
constructor(name, code, originalMessage) {
|
|
2393
|
+
super(`Handler for "${name}" threw [${code}]: ${originalMessage}`);
|
|
2394
|
+
this.name = 'HandlerError';
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
class DisposedError extends Error {
|
|
2398
|
+
constructor(name) {
|
|
2399
|
+
super(`Bridge disposed while call to "${name}" was in flight`);
|
|
2400
|
+
this.name = 'DisposedError';
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
let idCounter = 0;
|
|
2405
|
+
function generateId() {
|
|
2406
|
+
idCounter += 1;
|
|
2407
|
+
return `pca-bridge-${idCounter}`;
|
|
2408
|
+
}
|
|
2409
|
+
const noopLogger = {
|
|
2410
|
+
debug: () => {},
|
|
2411
|
+
warn: () => {},
|
|
2412
|
+
error: () => {}
|
|
2413
|
+
};
|
|
2414
|
+
function isIndexable(x) {
|
|
2415
|
+
return typeof x === 'object' && x !== null;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
/**
|
|
2419
|
+
* Shallow equality, one level deep. Used by `BridgeCore.publish` to dedup
|
|
2420
|
+
* re-emissions of new-but-content-equal values (e.g., a freshly-built
|
|
2421
|
+
* `modalButtons` array whose contents didn't change between renders).
|
|
2422
|
+
*/
|
|
2423
|
+
function shallowEqual(a, b) {
|
|
2424
|
+
const sameRef = Object.is(a, b);
|
|
2425
|
+
const bothComparableObjects = isIndexable(a) && isIndexable(b) && Array.isArray(a) === Array.isArray(b);
|
|
2426
|
+
let result;
|
|
2427
|
+
if (sameRef) {
|
|
2428
|
+
result = true;
|
|
2429
|
+
} else if (!bothComparableObjects) {
|
|
2430
|
+
result = false;
|
|
2431
|
+
} else {
|
|
2432
|
+
const keys = Object.keys(a);
|
|
2433
|
+
result = keys.length === Object.keys(b).length && keys.every(k => Object.prototype.hasOwnProperty.call(b, k) && Object.is(a[k], b[k]));
|
|
2434
|
+
}
|
|
2435
|
+
return result;
|
|
2436
|
+
}
|
|
2437
|
+
function isWireEnvelope(parsed) {
|
|
2438
|
+
return typeof parsed === 'object' && parsed !== null && 'type' in parsed && typeof parsed.type === 'string' && parsed.type.startsWith('__bridge/') && 'bridgeVersion' in parsed && typeof parsed.bridgeVersion === 'string';
|
|
2439
|
+
}
|
|
2440
|
+
function parseWireEnvelope(rawData) {
|
|
2441
|
+
let parsed;
|
|
2442
|
+
try {
|
|
2443
|
+
parsed = JSON.parse(rawData);
|
|
2444
|
+
} catch {
|
|
2445
|
+
parsed = null;
|
|
2446
|
+
}
|
|
2447
|
+
return isWireEnvelope(parsed) ? parsed : null;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
2451
|
+
const INCOMING_CALL_QUEUE_TTL_MS = 5000;
|
|
2452
|
+
const INCOMING_CALL_QUEUE_MAX_PER_ACTION = 32;
|
|
2453
|
+
class BridgeCore {
|
|
2454
|
+
injectedJavaScriptBeforeContentLoaded = PCA_BRIDGE_BUS_INIT;
|
|
2455
|
+
localVersion = BRIDGE_VERSION;
|
|
2456
|
+
// `never` arg type lets us widen any user-defined `LocalActions[K]` callable
|
|
2457
|
+
// with a single `as` cast inside `expose()`.
|
|
2458
|
+
localActions = new Map();
|
|
2459
|
+
localValues = new Map();
|
|
2460
|
+
remoteActions = new Set();
|
|
2461
|
+
remoteValues = new Map();
|
|
2462
|
+
actionListeners = new Map();
|
|
2463
|
+
valueListeners = new Map();
|
|
2464
|
+
pendingCalls = new Map();
|
|
2465
|
+
callQueue = new Map();
|
|
2466
|
+
|
|
2467
|
+
// Private backing field + public getter so the `readonly` declared on the
|
|
2468
|
+
// Bridge interface is actually enforced — not just at the interface level
|
|
2469
|
+
// but for any code holding a concrete BridgeCore reference.
|
|
2470
|
+
_remoteVersion = undefined;
|
|
2471
|
+
get remoteVersion() {
|
|
2472
|
+
return this._remoteVersion;
|
|
2473
|
+
}
|
|
2474
|
+
disposed = false;
|
|
2475
|
+
constructor(options) {
|
|
2476
|
+
const sendRaw = options.send;
|
|
2477
|
+
this.send = msg => {
|
|
2478
|
+
sendRaw({
|
|
2479
|
+
...msg,
|
|
2480
|
+
bridgeVersion: BRIDGE_VERSION
|
|
2481
|
+
});
|
|
2482
|
+
};
|
|
2483
|
+
this.logger = options.logger ?? noopLogger;
|
|
2484
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2485
|
+
this.now = options.now ?? Date.now;
|
|
2486
|
+
}
|
|
2487
|
+
call(name, args, options) {
|
|
2488
|
+
const requestId = generateId();
|
|
2489
|
+
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
|
2490
|
+
return new Promise((resolve, reject) => {
|
|
2491
|
+
const timer = setTimeout(() => {
|
|
2492
|
+
if (this.pendingCalls.delete(requestId)) {
|
|
2493
|
+
reject(new TimeoutError(name, timeoutMs));
|
|
2494
|
+
}
|
|
2495
|
+
}, timeoutMs);
|
|
2496
|
+
// Heterogeneous map: cast the typed resolve to the loose storage slot.
|
|
2497
|
+
this.pendingCalls.set(requestId, {
|
|
2498
|
+
name,
|
|
2499
|
+
resolve: resolve,
|
|
2500
|
+
reject,
|
|
2501
|
+
timer
|
|
2502
|
+
});
|
|
2503
|
+
this.send({
|
|
2504
|
+
type: '__bridge/call',
|
|
2505
|
+
requestId,
|
|
2506
|
+
name,
|
|
2507
|
+
args
|
|
2508
|
+
});
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
expose(name, handler) {
|
|
2512
|
+
if (this.localActions.has(name)) {
|
|
2513
|
+
this.logger.warn('Action already exposed; last-write-wins (the previous handler is gone)', {
|
|
2514
|
+
name
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
this.localActions.set(name, handler);
|
|
2518
|
+
this.send({
|
|
2519
|
+
type: '__bridge/exposed',
|
|
2520
|
+
name,
|
|
2521
|
+
kind: 'action'
|
|
2522
|
+
});
|
|
2523
|
+
const queued = this.callQueue.get(name);
|
|
2524
|
+
if (queued) {
|
|
2525
|
+
this.callQueue.delete(name);
|
|
2526
|
+
const cutoff = this.now() - INCOMING_CALL_QUEUE_TTL_MS;
|
|
2527
|
+
for (const entry of queued) {
|
|
2528
|
+
if (entry.receivedAt >= cutoff) {
|
|
2529
|
+
void this.invokeLocalHandler(entry.msg);
|
|
2530
|
+
} else {
|
|
2531
|
+
this.logger.warn('Queued incoming call expired before handler registered', {
|
|
2532
|
+
name,
|
|
2533
|
+
ageMs: this.now() - entry.receivedAt
|
|
2534
|
+
});
|
|
2535
|
+
this.rejectQueued(entry, 'expired');
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
unexpose(name) {
|
|
2541
|
+
if (this.localActions.delete(name)) {
|
|
2542
|
+
this.send({
|
|
2543
|
+
type: '__bridge/unexposed',
|
|
2544
|
+
name,
|
|
2545
|
+
kind: 'action'
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
hasRemoteAction(name) {
|
|
2550
|
+
return this.remoteActions.has(name);
|
|
2551
|
+
}
|
|
2552
|
+
onRemoteAction(name, callback) {
|
|
2553
|
+
let set = this.actionListeners.get(name);
|
|
2554
|
+
if (!set) {
|
|
2555
|
+
set = new Set();
|
|
2556
|
+
this.actionListeners.set(name, set);
|
|
2557
|
+
}
|
|
2558
|
+
set.add(callback);
|
|
2559
|
+
return () => {
|
|
2560
|
+
const current = this.actionListeners.get(name);
|
|
2561
|
+
if (current) {
|
|
2562
|
+
current.delete(callback);
|
|
2563
|
+
if (current.size === 0) {
|
|
2564
|
+
this.actionListeners.delete(name);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
publish(name, value) {
|
|
2570
|
+
// Skip wire send if content unchanged — callers may publish on every
|
|
2571
|
+
// render with a fresh-but-content-equal value.
|
|
2572
|
+
if (!shallowEqual(this.localValues.get(name), value)) {
|
|
2573
|
+
this.localValues.set(name, value);
|
|
2574
|
+
this.send({
|
|
2575
|
+
type: '__bridge/publish',
|
|
2576
|
+
name,
|
|
2577
|
+
value
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
unpublish(name) {
|
|
2582
|
+
if (this.localValues.delete(name)) {
|
|
2583
|
+
this.send({
|
|
2584
|
+
type: '__bridge/unexposed',
|
|
2585
|
+
name,
|
|
2586
|
+
kind: 'value'
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
seedLocalValue(name, value) {
|
|
2591
|
+
this.localValues.set(name, value);
|
|
2592
|
+
}
|
|
2593
|
+
getRemoteValue(name) {
|
|
2594
|
+
return this.remoteValues.get(name);
|
|
2595
|
+
}
|
|
2596
|
+
onRemoteValue(name, callback) {
|
|
2597
|
+
let set = this.valueListeners.get(name);
|
|
2598
|
+
if (!set) {
|
|
2599
|
+
set = new Set();
|
|
2600
|
+
this.valueListeners.set(name, set);
|
|
2601
|
+
}
|
|
2602
|
+
const wrapped = callback;
|
|
2603
|
+
set.add(wrapped);
|
|
2604
|
+
return () => {
|
|
2605
|
+
const current = this.valueListeners.get(name);
|
|
2606
|
+
if (current) {
|
|
2607
|
+
current.delete(wrapped);
|
|
2608
|
+
if (current.size === 0) {
|
|
2609
|
+
this.valueListeners.delete(name);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
reannounceLocal() {
|
|
2615
|
+
for (const name of this.localActions.keys()) {
|
|
2616
|
+
this.send({
|
|
2617
|
+
type: '__bridge/exposed',
|
|
2618
|
+
name,
|
|
2619
|
+
kind: 'action'
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
for (const [name, value] of this.localValues) {
|
|
2623
|
+
this.send({
|
|
2624
|
+
type: '__bridge/exposed',
|
|
2625
|
+
name,
|
|
2626
|
+
kind: 'value',
|
|
2627
|
+
currentValue: value
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
/**
|
|
2633
|
+
* Drop remote-side cached state. Called by transports when the remote JS
|
|
2634
|
+
* context is about to be torn down (e.g., WebView reload): the new context
|
|
2635
|
+
* will re-announce what it actually exposes, and stale entries would
|
|
2636
|
+
* otherwise silently mask removed actions/values.
|
|
2637
|
+
*/
|
|
2638
|
+
clearRemoteState() {
|
|
2639
|
+
for (const name of this.remoteActions) {
|
|
2640
|
+
this.notifySet(this.actionListeners, name, false, 'action availability');
|
|
2641
|
+
}
|
|
2642
|
+
this.remoteActions.clear();
|
|
2643
|
+
for (const name of this.remoteValues.keys()) {
|
|
2644
|
+
this.notifySet(this.valueListeners, name, undefined, 'remote value change');
|
|
2645
|
+
}
|
|
2646
|
+
this.remoteValues.clear();
|
|
2647
|
+
this._remoteVersion = undefined;
|
|
2648
|
+
}
|
|
2649
|
+
handleMessage(rawData) {
|
|
2650
|
+
const msg = parseWireEnvelope(rawData);
|
|
2651
|
+
let consumed = false;
|
|
2652
|
+
if (msg) {
|
|
2653
|
+
this.handleIncomingMessage(msg);
|
|
2654
|
+
consumed = true;
|
|
2655
|
+
}
|
|
2656
|
+
return consumed;
|
|
2657
|
+
}
|
|
2658
|
+
applyLogger(logger) {
|
|
2659
|
+
if (this.logger === noopLogger) {
|
|
2660
|
+
this.logger = logger;
|
|
2661
|
+
return true;
|
|
2662
|
+
}
|
|
2663
|
+
return false;
|
|
2664
|
+
}
|
|
2665
|
+
handleLoadStart() {}
|
|
2666
|
+
handleLoadEnd() {}
|
|
2667
|
+
handleNavigationStateChange(navState) {
|
|
2668
|
+
if (!navState.loading) {
|
|
2669
|
+
this.handleLoadEnd();
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
handleIncomingMessage(msg) {
|
|
2673
|
+
if (!this.disposed) {
|
|
2674
|
+
this._remoteVersion = msg.bridgeVersion;
|
|
2675
|
+
switch (msg.type) {
|
|
2676
|
+
case '__bridge/call':
|
|
2677
|
+
void this.routeIncomingCall(msg);
|
|
2678
|
+
break;
|
|
2679
|
+
case '__bridge/response':
|
|
2680
|
+
this.handleIncomingResponse(msg);
|
|
2681
|
+
break;
|
|
2682
|
+
case '__bridge/exposed':
|
|
2683
|
+
this.handleIncomingExposed(msg);
|
|
2684
|
+
break;
|
|
2685
|
+
case '__bridge/unexposed':
|
|
2686
|
+
this.handleIncomingUnexposed(msg);
|
|
2687
|
+
break;
|
|
2688
|
+
case '__bridge/publish':
|
|
2689
|
+
this.handleIncomingPublish(msg);
|
|
2690
|
+
break;
|
|
2691
|
+
case '__bridge/busReady':
|
|
2692
|
+
case '__bridge/drop':
|
|
2693
|
+
break;
|
|
2694
|
+
default:
|
|
2695
|
+
{
|
|
2696
|
+
// parseWireEnvelope admits any `__bridge/*` type; this catches
|
|
2697
|
+
// future message types a remote on a newer bridge sends to us.
|
|
2698
|
+
const unknown = msg;
|
|
2699
|
+
this.logger.warn('Received unknown bridge message type; ignoring', {
|
|
2700
|
+
type: unknown.type,
|
|
2701
|
+
remoteVersion: this.remoteVersion
|
|
2702
|
+
});
|
|
2703
|
+
break;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
get debug() {
|
|
2709
|
+
return {
|
|
2710
|
+
listLocalActions: () => Array.from(this.localActions.keys()),
|
|
2711
|
+
listRemoteActions: () => Array.from(this.remoteActions),
|
|
2712
|
+
listLocalValues: () => Array.from(this.localValues.keys()),
|
|
2713
|
+
listRemoteValues: () => Array.from(this.remoteValues.keys())
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
dispose() {
|
|
2717
|
+
if (!this.disposed) {
|
|
2718
|
+
this.disposed = true;
|
|
2719
|
+
for (const pending of this.pendingCalls.values()) {
|
|
2720
|
+
clearTimeout(pending.timer);
|
|
2721
|
+
pending.reject(new DisposedError(pending.name));
|
|
2722
|
+
}
|
|
2723
|
+
this.pendingCalls.clear();
|
|
2724
|
+
this.callQueue.clear();
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
async routeIncomingCall(msg) {
|
|
2728
|
+
if (this.localActions.has(msg.name)) {
|
|
2729
|
+
await this.invokeLocalHandler(msg);
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
let queued = this.callQueue.get(msg.name);
|
|
2733
|
+
if (!queued) {
|
|
2734
|
+
queued = [];
|
|
2735
|
+
this.callQueue.set(msg.name, queued);
|
|
2736
|
+
}
|
|
2737
|
+
// Evict TTL-expired and over-capacity entries so a remote that spams an
|
|
2738
|
+
// unknown action can't grow the queue without bound. Each evicted call
|
|
2739
|
+
// gets an error response so the caller fails fast.
|
|
2740
|
+
const cutoff = this.now() - INCOMING_CALL_QUEUE_TTL_MS;
|
|
2741
|
+
while (queued.length > 0) {
|
|
2742
|
+
const head = queued[0];
|
|
2743
|
+
if (!head || head.receivedAt >= cutoff) break;
|
|
2744
|
+
queued.shift();
|
|
2745
|
+
this.rejectQueued(head, 'expired');
|
|
2746
|
+
}
|
|
2747
|
+
while (queued.length >= INCOMING_CALL_QUEUE_MAX_PER_ACTION) {
|
|
2748
|
+
const evicted = queued.shift();
|
|
2749
|
+
if (!evicted) break;
|
|
2750
|
+
this.rejectQueued(evicted, 'queue_full');
|
|
2751
|
+
}
|
|
2752
|
+
queued.push({
|
|
2753
|
+
msg,
|
|
2754
|
+
receivedAt: this.now()
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
rejectQueued(entry, code) {
|
|
2758
|
+
this.send({
|
|
2759
|
+
type: '__bridge/response',
|
|
2760
|
+
requestId: entry.msg.requestId,
|
|
2761
|
+
ok: false,
|
|
2762
|
+
code,
|
|
2763
|
+
message: `Queued call to "${entry.msg.name}" dropped (${code})`
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
async invokeLocalHandler(msg) {
|
|
2767
|
+
// Handler may have been unregistered between queue push and drain. Reply
|
|
2768
|
+
// with an explicit error so the caller fails fast instead of timing out.
|
|
2769
|
+
const handler = this.localActions.get(msg.name);
|
|
2770
|
+
if (!handler) {
|
|
2771
|
+
this.send({
|
|
2772
|
+
type: '__bridge/response',
|
|
2773
|
+
requestId: msg.requestId,
|
|
2774
|
+
ok: false,
|
|
2775
|
+
code: 'unexposed',
|
|
2776
|
+
message: `No handler exposed for "${msg.name}"`
|
|
2777
|
+
});
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
try {
|
|
2781
|
+
const result = await handler(msg.args);
|
|
2782
|
+
this.send({
|
|
2783
|
+
type: '__bridge/response',
|
|
2784
|
+
requestId: msg.requestId,
|
|
2785
|
+
ok: true,
|
|
2786
|
+
result
|
|
2787
|
+
});
|
|
2788
|
+
} catch (error) {
|
|
2789
|
+
this.send({
|
|
2790
|
+
type: '__bridge/response',
|
|
2791
|
+
requestId: msg.requestId,
|
|
2792
|
+
ok: false,
|
|
2793
|
+
code: 'thrown',
|
|
2794
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
handleIncomingResponse(msg) {
|
|
2799
|
+
const pending = this.pendingCalls.get(msg.requestId);
|
|
2800
|
+
if (pending) {
|
|
2801
|
+
this.pendingCalls.delete(msg.requestId);
|
|
2802
|
+
clearTimeout(pending.timer);
|
|
2803
|
+
if (msg.ok) {
|
|
2804
|
+
pending.resolve(msg.result);
|
|
2805
|
+
} else {
|
|
2806
|
+
pending.reject(new HandlerError(pending.name, msg.code, msg.message));
|
|
2807
|
+
}
|
|
2808
|
+
} else {
|
|
2809
|
+
this.logger.warn('Response for unknown or already-resolved requestId', {
|
|
2810
|
+
requestId: msg.requestId
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
handleIncomingExposed(msg) {
|
|
2815
|
+
if (msg.kind === 'action') {
|
|
2816
|
+
if (!this.remoteActions.has(msg.name)) {
|
|
2817
|
+
this.remoteActions.add(msg.name);
|
|
2818
|
+
this.notifySet(this.actionListeners, msg.name, true, 'action availability');
|
|
2819
|
+
}
|
|
2820
|
+
} else {
|
|
2821
|
+
this.remoteValues.set(msg.name, msg.currentValue);
|
|
2822
|
+
this.notifySet(this.valueListeners, msg.name, msg.currentValue, 'remote value change');
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
handleIncomingUnexposed(msg) {
|
|
2826
|
+
if (msg.kind === 'action') {
|
|
2827
|
+
if (this.remoteActions.delete(msg.name)) {
|
|
2828
|
+
this.notifySet(this.actionListeners, msg.name, false, 'action availability');
|
|
2829
|
+
}
|
|
2830
|
+
} else if (this.remoteValues.delete(msg.name)) {
|
|
2831
|
+
this.notifySet(this.valueListeners, msg.name, undefined, 'remote value change');
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
handleIncomingPublish(msg) {
|
|
2835
|
+
this.remoteValues.set(msg.name, msg.value);
|
|
2836
|
+
this.notifySet(this.valueListeners, msg.name, msg.value, 'remote value change');
|
|
2837
|
+
}
|
|
2838
|
+
notifySet(subs, name, value, label) {
|
|
2839
|
+
const set = subs.get(name);
|
|
2840
|
+
if (set) {
|
|
2841
|
+
for (const callback of set) {
|
|
2842
|
+
try {
|
|
2843
|
+
callback(value);
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
this.logger.error(`Subscriber threw during ${label} notification`, {
|
|
2846
|
+
name,
|
|
2847
|
+
error
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// Subset of window.__pcaBridge that bridge_web.ts needs.
|
|
2856
|
+
|
|
2857
|
+
const INBOUND_TYPES = ['__bridge/call', '__bridge/response', '__bridge/exposed', '__bridge/unexposed', '__bridge/publish'];
|
|
2858
|
+
class WebBridge extends BridgeCore {
|
|
2859
|
+
constructor(options = {}) {
|
|
2860
|
+
const bus = window.__pcaBridge;
|
|
2861
|
+
super({
|
|
2862
|
+
send: msg => {
|
|
2863
|
+
if (window.ReactNativeWebView) {
|
|
2864
|
+
try {
|
|
2865
|
+
window.ReactNativeWebView.postMessage(JSON.stringify(msg));
|
|
2866
|
+
} catch (error) {
|
|
2867
|
+
options.logger?.error('WebBridge postMessage failed', {
|
|
2868
|
+
error
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
},
|
|
2873
|
+
logger: options.logger,
|
|
2874
|
+
defaultTimeoutMs: options.defaultTimeoutMs
|
|
2875
|
+
});
|
|
2876
|
+
this.bus = bus;
|
|
2877
|
+
this.handlerFn = msg => {
|
|
2878
|
+
this.handleIncomingMessage(msg);
|
|
2879
|
+
};
|
|
2880
|
+
if (bus) {
|
|
2881
|
+
if (options.logger) {
|
|
2882
|
+
bus.setLogger(options.logger);
|
|
2883
|
+
}
|
|
2884
|
+
for (const type of INBOUND_TYPES) {
|
|
2885
|
+
bus.register(type, this.handlerFn);
|
|
2886
|
+
}
|
|
2887
|
+
if (window.ReactNativeWebView) {
|
|
2888
|
+
try {
|
|
2889
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
2890
|
+
type: '__bridge/busReady',
|
|
2891
|
+
bridgeVersion: BRIDGE_VERSION
|
|
2892
|
+
}));
|
|
2893
|
+
} catch (error) {
|
|
2894
|
+
options.logger?.error('WebBridge busReady post failed', {
|
|
2895
|
+
error
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
} else {
|
|
2900
|
+
options.logger?.warn('WebBridge constructed without window.__pcaBridge — bus init script may not have run');
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
applyLogger(logger) {
|
|
2904
|
+
const applied = super.applyLogger(logger);
|
|
2905
|
+
if (applied && this.bus) {
|
|
2906
|
+
this.bus.setLogger(logger);
|
|
2907
|
+
}
|
|
2908
|
+
return applied;
|
|
2909
|
+
}
|
|
2910
|
+
dispose() {
|
|
2911
|
+
if (this.bus) {
|
|
2912
|
+
for (const type of INBOUND_TYPES) {
|
|
2913
|
+
this.bus.unregister(type, this.handlerFn);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
super.dispose();
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
function getOrCreateWebBridge(options = {}) {
|
|
2920
|
+
if (window.__pcaWebBridge && options.logger) {
|
|
2921
|
+
window.__pcaWebBridge.applyLogger(options.logger);
|
|
2922
|
+
} else if (!window.__pcaWebBridge) {
|
|
2923
|
+
window.__pcaWebBridge = new WebBridge(options);
|
|
2924
|
+
}
|
|
2925
|
+
return window.__pcaWebBridge;
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
const WebBridgeContext = /*#__PURE__*/createContext(null);
|
|
2929
|
+
function useWebBridge() {
|
|
2930
|
+
const ctxBridge = useContext(WebBridgeContext);
|
|
2931
|
+
if (ctxBridge) {
|
|
2932
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
2933
|
+
return ctxBridge;
|
|
2934
|
+
} else {
|
|
2935
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
2936
|
+
return getOrCreateWebBridge();
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function useBridgePublish(bridge, name, value) {
|
|
2940
|
+
// publish() dedups by shallow equality, so this effect is safe to re-run
|
|
2941
|
+
// on every value change without spamming wire messages.
|
|
2942
|
+
useEffect(() => {
|
|
2943
|
+
bridge.publish(name, value);
|
|
2944
|
+
}, [bridge, name, value]);
|
|
2945
|
+
useEffect(() => {
|
|
2946
|
+
return () => {
|
|
2947
|
+
bridge.unpublish(name);
|
|
2948
|
+
};
|
|
2949
|
+
}, [bridge, name]);
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2227
2952
|
const useSetCurrentRouteInfo = ({
|
|
2228
2953
|
routes,
|
|
2229
2954
|
app
|
|
2230
2955
|
}) => {
|
|
2956
|
+
const bridge = useWebBridge();
|
|
2957
|
+
useBridgePublish(bridge, "routeInfo", {
|
|
2958
|
+
currentApp: app,
|
|
2959
|
+
currentRoutes: routes.map(({
|
|
2960
|
+
key,
|
|
2961
|
+
props: {
|
|
2962
|
+
to,
|
|
2963
|
+
href,
|
|
2964
|
+
active
|
|
2965
|
+
}
|
|
2966
|
+
}) => ({
|
|
2967
|
+
active,
|
|
2968
|
+
name: key,
|
|
2969
|
+
url: to !== null && to !== void 0 ? to : href
|
|
2970
|
+
}))
|
|
2971
|
+
});
|
|
2231
2972
|
useEffect(() => {
|
|
2232
2973
|
if (window.topbar) {
|
|
2233
2974
|
window.topbar.routeInfo = {
|
|
@@ -2247,6 +2988,24 @@ const useSetCurrentRouteInfo = ({
|
|
|
2247
2988
|
};
|
|
2248
2989
|
}
|
|
2249
2990
|
}, [routes, app]);
|
|
2991
|
+
// Publish info for when app goes back
|
|
2992
|
+
useEffect(() => window.addEventListener("pageshow", () => {
|
|
2993
|
+
bridge.publish("routeInfo", {
|
|
2994
|
+
currentApp: app,
|
|
2995
|
+
currentRoutes: routes.map(({
|
|
2996
|
+
key,
|
|
2997
|
+
props: {
|
|
2998
|
+
to,
|
|
2999
|
+
href,
|
|
3000
|
+
active
|
|
3001
|
+
}
|
|
3002
|
+
}) => ({
|
|
3003
|
+
active,
|
|
3004
|
+
name: key,
|
|
3005
|
+
url: to !== null && to !== void 0 ? to : href
|
|
3006
|
+
}))
|
|
3007
|
+
});
|
|
3008
|
+
}), []);
|
|
2250
3009
|
};
|
|
2251
3010
|
|
|
2252
3011
|
const useUpdateStore = () => useCallback(updates => {
|
|
@@ -3454,40 +4213,44 @@ function buildPicobotSrc(origin, path) {
|
|
|
3454
4213
|
return fragment ? `${base}#${fragment}` : base;
|
|
3455
4214
|
}
|
|
3456
4215
|
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
4216
|
+
// The article heading lives in the deepest populated hierarchy level (lvl0 is
|
|
4217
|
+
// always "Planning Center").
|
|
4218
|
+
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) || "";
|
|
4219
|
+
const handleSearchSelect = hit => {
|
|
4220
|
+
// `url` is always absolute (it is also the basis of objectID), so open it
|
|
4221
|
+
// directly. Bail out defensively if a record somehow arrives without one.
|
|
4222
|
+
if (!(hit === null || hit === void 0 ? void 0 : hit.url)) return;
|
|
4223
|
+
const newTab = window.open(hit.url, "_blank");
|
|
3461
4224
|
if (newTab) {
|
|
3462
4225
|
newTab.focus();
|
|
3463
4226
|
}
|
|
3464
4227
|
};
|
|
4228
|
+
// Mirrors @planningcenter/tapestry-react's Logo registry.
|
|
4229
|
+
const VALID_LOGO_NAMES = new Set(["api", "calendar", "check-ins", "church-center", "giving", "groups", "headcounts", "home", "login", "music-stand", "people", "planning-center", "publishing", "registrations", "services"]);
|
|
3465
4230
|
const HeadingIcon = ({
|
|
3466
|
-
|
|
3467
|
-
|
|
4231
|
+
currentAppName,
|
|
4232
|
+
product
|
|
3468
4233
|
}) => {
|
|
3469
|
-
const
|
|
3470
|
-
if (
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
});
|
|
3475
|
-
} else if (currentAppName === "Accounts") {
|
|
4234
|
+
const appName = product || currentAppName;
|
|
4235
|
+
if (!appName) return null;
|
|
4236
|
+
const logoName = appName.toLowerCase().replace(/\s+/g, "-");
|
|
4237
|
+
// Accounts has no tapestry Logo, so use the cog icon
|
|
4238
|
+
if (logoName === "accounts") {
|
|
3476
4239
|
return /*#__PURE__*/React__default.createElement(Icon, {
|
|
3477
4240
|
name: "general.cog",
|
|
3478
4241
|
color: "foregroundSecondary"
|
|
3479
4242
|
});
|
|
3480
|
-
} else {
|
|
3481
|
-
return /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
3482
|
-
width: 2,
|
|
3483
|
-
height: 2
|
|
3484
|
-
}, /*#__PURE__*/React__default.createElement(Logo, {
|
|
3485
|
-
name: currentAppName.toLowerCase().replace(/[\s]/, "-"),
|
|
3486
|
-
size: "sm",
|
|
3487
|
-
theme: "color",
|
|
3488
|
-
markOnly: true
|
|
3489
|
-
}));
|
|
3490
4243
|
}
|
|
4244
|
+
if (!VALID_LOGO_NAMES.has(logoName)) return null;
|
|
4245
|
+
return /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
4246
|
+
width: 2,
|
|
4247
|
+
height: 2
|
|
4248
|
+
}, /*#__PURE__*/React__default.createElement(Logo, {
|
|
4249
|
+
name: logoName,
|
|
4250
|
+
size: "sm",
|
|
4251
|
+
theme: "color",
|
|
4252
|
+
markOnly: true
|
|
4253
|
+
}));
|
|
3491
4254
|
};
|
|
3492
4255
|
const HitResult = ({
|
|
3493
4256
|
hit,
|
|
@@ -3502,11 +4265,10 @@ const HitResult = ({
|
|
|
3502
4265
|
color: "neutralDarker",
|
|
3503
4266
|
size: 4,
|
|
3504
4267
|
whiteSpace: "normal"
|
|
3505
|
-
}, hit.
|
|
4268
|
+
}, hitTitle(hit.hierarchy)));
|
|
3506
4269
|
};
|
|
3507
4270
|
const CustomSearchBox = ({
|
|
3508
|
-
currentAppName
|
|
3509
|
-
indexName
|
|
4271
|
+
currentAppName
|
|
3510
4272
|
}) => {
|
|
3511
4273
|
const {
|
|
3512
4274
|
query,
|
|
@@ -3517,7 +4279,10 @@ const CustomSearchBox = ({
|
|
|
3517
4279
|
results
|
|
3518
4280
|
} = useHits();
|
|
3519
4281
|
const groups = results === null || results === void 0 ? void 0 : results.hits.reduce((acc, hit) => {
|
|
3520
|
-
|
|
4282
|
+
// A result with no url can't be opened, so don't render it at all.
|
|
4283
|
+
if (!hit.url) return acc;
|
|
4284
|
+
// Fall back so a record without a product never yields an "undefined" heading.
|
|
4285
|
+
const key = hit.product || currentAppName || "Other";
|
|
3521
4286
|
if (!acc[key]) {
|
|
3522
4287
|
acc[key] = [];
|
|
3523
4288
|
}
|
|
@@ -3539,7 +4304,7 @@ const CustomSearchBox = ({
|
|
|
3539
4304
|
name: "general.search"
|
|
3540
4305
|
}),
|
|
3541
4306
|
onChange: e => setQuery(e.target.value),
|
|
3542
|
-
onSelect: selectedValue => handleSearchSelect(selectedValue
|
|
4307
|
+
onSelect: selectedValue => handleSearchSelect(selectedValue),
|
|
3543
4308
|
placement: "bottom-start"
|
|
3544
4309
|
}, !hits.length && (/*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
3545
4310
|
axis: "horizontal",
|
|
@@ -3551,7 +4316,7 @@ const CustomSearchBox = ({
|
|
|
3551
4316
|
size: 4,
|
|
3552
4317
|
whiteSpace: "normal"
|
|
3553
4318
|
}, "No results match your search")))), groups && Object.keys(groups).map(key => {
|
|
3554
|
-
const
|
|
4319
|
+
const groupHits = groups[key];
|
|
3555
4320
|
return /*#__PURE__*/React__default.createElement(React__default.Fragment, {
|
|
3556
4321
|
key: key
|
|
3557
4322
|
}, resultIndex > 0 && /*#__PURE__*/React__default.createElement(Divider, {
|
|
@@ -3564,12 +4329,12 @@ const CustomSearchBox = ({
|
|
|
3564
4329
|
paddingVertical: 1
|
|
3565
4330
|
}, /*#__PURE__*/React__default.createElement(HeadingIcon, {
|
|
3566
4331
|
currentAppName: currentAppName,
|
|
3567
|
-
|
|
4332
|
+
product: key
|
|
3568
4333
|
}), /*#__PURE__*/React__default.createElement(Text, {
|
|
3569
4334
|
size: 5,
|
|
3570
4335
|
color: "foregroundSecondary",
|
|
3571
4336
|
fontWeight: 500
|
|
3572
|
-
}, key)),
|
|
4337
|
+
}, key)), groupHits.map(hit => (/*#__PURE__*/React__default.createElement(HitResult, {
|
|
3573
4338
|
key: hit.objectID,
|
|
3574
4339
|
hit: hit,
|
|
3575
4340
|
index: resultIndex++
|
|
@@ -3577,8 +4342,8 @@ const CustomSearchBox = ({
|
|
|
3577
4342
|
})));
|
|
3578
4343
|
};
|
|
3579
4344
|
|
|
4345
|
+
const HELP_CENTER_INDEX = "help_center_english";
|
|
3580
4346
|
const DocumentationSearch = ({
|
|
3581
|
-
indexName,
|
|
3582
4347
|
currentAppName
|
|
3583
4348
|
}) => {
|
|
3584
4349
|
const {
|
|
@@ -3589,13 +4354,13 @@ const DocumentationSearch = ({
|
|
|
3589
4354
|
} = useStore();
|
|
3590
4355
|
const searchClient = algoliasearch(algoliaApplicationId, algoliaSearchOnlyApiKey);
|
|
3591
4356
|
return /*#__PURE__*/React__default.createElement(InstantSearch, {
|
|
3592
|
-
indexName:
|
|
4357
|
+
indexName: HELP_CENTER_INDEX,
|
|
3593
4358
|
searchClient: searchClient
|
|
3594
4359
|
}, /*#__PURE__*/React__default.createElement(Configure, {
|
|
3595
|
-
hitsPerPage: 5
|
|
4360
|
+
hitsPerPage: 5,
|
|
4361
|
+
optionalFilters: currentAppName ? [`product:${currentAppName}`] : undefined
|
|
3596
4362
|
}), /*#__PURE__*/React__default.createElement(CustomSearchBox, {
|
|
3597
|
-
currentAppName: currentAppName
|
|
3598
|
-
indexName: indexName
|
|
4363
|
+
currentAppName: currentAppName
|
|
3599
4364
|
}));
|
|
3600
4365
|
};
|
|
3601
4366
|
|
|
@@ -3603,6 +4368,19 @@ const {
|
|
|
3603
4368
|
LoadingIndicator: LoadingIndicator$1,
|
|
3604
4369
|
StackView: StackView$1
|
|
3605
4370
|
} = components;
|
|
4371
|
+
function CalendlyWidget({
|
|
4372
|
+
url,
|
|
4373
|
+
pageSettings,
|
|
4374
|
+
prefill,
|
|
4375
|
+
styles
|
|
4376
|
+
}) {
|
|
4377
|
+
return url ? (/*#__PURE__*/React__default.createElement(InlineWidget, {
|
|
4378
|
+
url: url,
|
|
4379
|
+
pageSettings: pageSettings,
|
|
4380
|
+
prefill: prefill,
|
|
4381
|
+
styles: styles
|
|
4382
|
+
})) : null;
|
|
4383
|
+
}
|
|
3606
4384
|
function HelpdeskComponent({
|
|
3607
4385
|
appName,
|
|
3608
4386
|
closeDrawer,
|
|
@@ -3671,14 +4449,65 @@ function HelpdeskComponent({
|
|
|
3671
4449
|
spacing: 1
|
|
3672
4450
|
}, /*#__PURE__*/React__default.createElement(DrawerHeading, null, "Help and support"), /*#__PURE__*/React__default.createElement(LoadingIndicator$1, null)))),
|
|
3673
4451
|
additionalComponents: {
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
4452
|
+
CalendlyWidget: {
|
|
4453
|
+
component: CalendlyWidget,
|
|
4454
|
+
properties: {
|
|
4455
|
+
pageSettings: {
|
|
4456
|
+
attribute: false
|
|
4457
|
+
},
|
|
4458
|
+
prefill: {
|
|
4459
|
+
attribute: false
|
|
4460
|
+
},
|
|
4461
|
+
styles: {
|
|
4462
|
+
attribute: false
|
|
4463
|
+
},
|
|
4464
|
+
url: {
|
|
4465
|
+
type: String
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
},
|
|
4469
|
+
DocumentationSearch: {
|
|
4470
|
+
component: DocumentationSearch,
|
|
4471
|
+
properties: {
|
|
4472
|
+
currentAppName: {
|
|
4473
|
+
type: String
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
},
|
|
4477
|
+
DrawerHeader: {
|
|
4478
|
+
component: DrawerHeader,
|
|
4479
|
+
properties: {
|
|
4480
|
+
closeDrawer: {
|
|
4481
|
+
event: false
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
},
|
|
3678
4485
|
DrawerHeading,
|
|
3679
|
-
PicobotIframe
|
|
4486
|
+
PicobotIframe: {
|
|
4487
|
+
component: PicobotIframe,
|
|
4488
|
+
properties: {
|
|
4489
|
+
currentPath: {
|
|
4490
|
+
attribute: false
|
|
4491
|
+
},
|
|
4492
|
+
onChangePath: {
|
|
4493
|
+
event: false,
|
|
4494
|
+
type: Function
|
|
4495
|
+
},
|
|
4496
|
+
onMessage: {
|
|
4497
|
+
event: false,
|
|
4498
|
+
type: Function
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
},
|
|
3680
4502
|
ScrollContainer,
|
|
3681
|
-
SwitchView
|
|
4503
|
+
SwitchView: {
|
|
4504
|
+
component: SwitchView,
|
|
4505
|
+
properties: {
|
|
4506
|
+
activeIndex: {
|
|
4507
|
+
type: Number
|
|
4508
|
+
}
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
3682
4511
|
},
|
|
3683
4512
|
args: {
|
|
3684
4513
|
appName,
|
|
@@ -4282,8 +5111,13 @@ const {
|
|
|
4282
5111
|
function NavLinkWithBadge({
|
|
4283
5112
|
children,
|
|
4284
5113
|
badge,
|
|
4285
|
-
|
|
4286
|
-
|
|
5114
|
+
borderBottomColor,
|
|
5115
|
+
borderBottomStyle,
|
|
5116
|
+
borderBottomWidth,
|
|
5117
|
+
color,
|
|
5118
|
+
fontSize,
|
|
5119
|
+
fontWeight,
|
|
5120
|
+
onClick
|
|
4287
5121
|
}) {
|
|
4288
5122
|
return /*#__PURE__*/React__default.createElement(StackView$2, {
|
|
4289
5123
|
as: "button",
|
|
@@ -4293,7 +5127,14 @@ function NavLinkWithBadge({
|
|
|
4293
5127
|
paddingBottom: 0.75,
|
|
4294
5128
|
onClick: () => onClick(),
|
|
4295
5129
|
spacing: 1,
|
|
4296
|
-
...
|
|
5130
|
+
...{
|
|
5131
|
+
borderBottomColor,
|
|
5132
|
+
borderBottomStyle,
|
|
5133
|
+
borderBottomWidth,
|
|
5134
|
+
color,
|
|
5135
|
+
fontSize,
|
|
5136
|
+
fontWeight
|
|
5137
|
+
}
|
|
4297
5138
|
}, children, badge && /*#__PURE__*/React__default.createElement(Badge, {
|
|
4298
5139
|
size: "sm",
|
|
4299
5140
|
fontWeight: "600",
|
|
@@ -4345,11 +5186,55 @@ function TasksComponent({
|
|
|
4345
5186
|
spacing: 1
|
|
4346
5187
|
}, /*#__PURE__*/React__default.createElement(DrawerHeading, null, "To do"), /*#__PURE__*/React__default.createElement(LoadingIndicator, null)))),
|
|
4347
5188
|
additionalComponents: {
|
|
4348
|
-
DrawerHeader
|
|
5189
|
+
DrawerHeader: {
|
|
5190
|
+
component: DrawerHeader,
|
|
5191
|
+
properties: {
|
|
5192
|
+
closeDrawer: {
|
|
5193
|
+
event: false,
|
|
5194
|
+
type: Function
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
},
|
|
4349
5198
|
DrawerHeading,
|
|
4350
|
-
NavLinkWithBadge
|
|
5199
|
+
NavLinkWithBadge: {
|
|
5200
|
+
component: NavLinkWithBadge,
|
|
5201
|
+
properties: {
|
|
5202
|
+
badge: {
|
|
5203
|
+
attribute: false
|
|
5204
|
+
},
|
|
5205
|
+
borderBottomColor: {
|
|
5206
|
+
type: String
|
|
5207
|
+
},
|
|
5208
|
+
borderBottomStyle: {
|
|
5209
|
+
type: String
|
|
5210
|
+
},
|
|
5211
|
+
borderBottomWidth: {
|
|
5212
|
+
type: String
|
|
5213
|
+
},
|
|
5214
|
+
color: {
|
|
5215
|
+
type: String
|
|
5216
|
+
},
|
|
5217
|
+
fontSize: {
|
|
5218
|
+
attribute: false
|
|
5219
|
+
},
|
|
5220
|
+
fontWeight: {
|
|
5221
|
+
attribute: false
|
|
5222
|
+
},
|
|
5223
|
+
onClick: {
|
|
5224
|
+
event: false,
|
|
5225
|
+
type: Function
|
|
5226
|
+
}
|
|
5227
|
+
}
|
|
5228
|
+
},
|
|
4351
5229
|
ScrollContainer,
|
|
4352
|
-
SwitchView
|
|
5230
|
+
SwitchView: {
|
|
5231
|
+
component: SwitchView,
|
|
5232
|
+
properties: {
|
|
5233
|
+
activeIndex: {
|
|
5234
|
+
type: Number
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
4353
5238
|
},
|
|
4354
5239
|
args: {
|
|
4355
5240
|
closeDrawer,
|