@reykjavik/webtools 0.1.22 → 0.1.23-canary.2
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/CHANGELOG.md +9 -4
- package/README.md +230 -136
- package/{esm/next/SiteImprove.privates.d.ts → SiteImprove.d.ts} +16 -15
- package/SiteImprove.js +176 -0
- package/async.js +14 -8
- package/{next/SiteImprove.privates.d.ts → esm/SiteImprove.d.ts} +16 -15
- package/{next/SiteImprove.privates.js → esm/SiteImprove.js} +71 -15
- package/esm/async.js +14 -8
- package/esm/index.d.ts +1 -0
- package/esm/next/SiteImprove.d.ts +10 -11
- package/esm/next/SiteImprove.js +7 -75
- package/esm/next/http.d.ts +1 -1
- package/esm/remix/Wait.js +1 -1
- package/index.d.ts +1 -0
- package/next/SiteImprove.d.ts +10 -11
- package/next/SiteImprove.js +8 -105
- package/next/http.d.ts +1 -1
- package/package.json +5 -1
- package/remix/Wait.js +1 -1
- package/esm/next/SiteImprove.privates.js +0 -83
package/CHANGELOG.md
CHANGED
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
- ... <!-- Add new lines here. -->
|
|
6
6
|
|
|
7
|
-
## 0.1.
|
|
7
|
+
## 0.1.23-canary.2
|
|
8
8
|
|
|
9
|
-
_2024-03-
|
|
9
|
+
_2024-03-21_
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- feat: Add framework agnostic `@reykjavik/webtools/SiteImprove` module —
|
|
12
|
+
deprecate `@reykjavik/webtools/next/SiteImprove` instead
|
|
13
|
+
- `@reykjavik/webtools/remix/Wait`:
|
|
14
|
+
- fix: Properly reject `data.$error`s as to not trigger error boundaries
|
|
15
|
+
- `@reykjavik/webtools/async`:
|
|
16
|
+
- fix: `maxWait` should gracefully ignore rejecting promises
|
|
12
17
|
|
|
13
|
-
## 0.1.21
|
|
18
|
+
## 0.1.21 – 0.1.22
|
|
14
19
|
|
|
15
20
|
_2024-03-14_
|
|
16
21
|
|
package/README.md
CHANGED
|
@@ -22,23 +22,27 @@ bun add @reykjavik/webtools
|
|
|
22
22
|
- [`cacheControl` helper](#cachecontrol-helper)
|
|
23
23
|
- [Type `TTLConfig`](#type-ttlconfig)
|
|
24
24
|
- [`toSec` TTL helper](#tosec-ttl-helper)
|
|
25
|
-
- [`@reykjavik/webtools/CookieHubConsent`](#reykjavikwebtoolscookiehubconsent)
|
|
26
|
-
- [`CookieHubProvider` component](#cookiehubprovider-component)
|
|
27
|
-
- [`useCookieHubConsent`](#usecookiehubconsent)
|
|
28
25
|
- [`@reykjavik/webtools/async`](#reykjavikwebtoolsasync)
|
|
29
26
|
- [`promiseAllObject`](#promiseallobject)
|
|
30
27
|
- [`maxWait`](#maxwait)
|
|
28
|
+
- [`@reykjavik/webtools/fixIcelandicLocale`](#reykjavikwebtoolsfixicelandiclocale)
|
|
29
|
+
- [Limitations](#limitations)
|
|
30
|
+
- [`@reykjavik/webtools/SiteImprove`](#reykjavikwebtoolssiteimprove)
|
|
31
|
+
- [`SiteImprove` component](#siteimprove-component)
|
|
32
|
+
- [`pingSiteImprove` helper](#pingsiteimprove-helper)
|
|
33
|
+
- [`pingSiteImproveOutbound` helper](#pingsiteimproveoutbound-helper)
|
|
34
|
+
- [`@reykjavik/webtools/CookieHubConsent`](#reykjavikwebtoolscookiehubconsent)
|
|
35
|
+
- [`CookieHubProvider` component](#cookiehubprovider-component)
|
|
36
|
+
- [`useCookieHubConsent`](#usecookiehubconsent)
|
|
31
37
|
- [`@reykjavik/webtools/vanillaExtract`](#reykjavikwebtoolsvanillaextract)
|
|
32
38
|
- [`vanillaGlobal`](#vanillaglobal)
|
|
33
39
|
- [`vanillaProps`](#vanillaprops)
|
|
34
40
|
- [`vanillaClass`](#vanillaclass)
|
|
35
41
|
- [`vanillaClassNested`](#vanillaclassnested)
|
|
36
42
|
- [`vanillaNest`](#vanillanest)
|
|
37
|
-
- [`@reykjavik/webtools/fixIcelandicLocale`](#reykjavikwebtoolsfixicelandiclocale)
|
|
38
|
-
- [Limitations](#limitations)
|
|
39
43
|
- [Framework Specific Tools](#framework-specific-tools)
|
|
40
|
-
- [Next.js Tools](#nextjs-tools)
|
|
41
44
|
- [Remix.run Tools](#remixrun-tools)
|
|
45
|
+
- [Next.js Tools](#nextjs-tools)
|
|
42
46
|
- [Contributing](#contributing)
|
|
43
47
|
- [Changelog](#changelog)
|
|
44
48
|
|
|
@@ -184,6 +188,219 @@ const ttlSec = toSec(ttl);
|
|
|
184
188
|
|
|
185
189
|
---
|
|
186
190
|
|
|
191
|
+
## `@reykjavik/webtools/async`
|
|
192
|
+
|
|
193
|
+
Contains a few small helpers for working with async functions and promises.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### `promiseAllObject`
|
|
198
|
+
|
|
199
|
+
**Syntax:**
|
|
200
|
+
`promiseAllObject<T extends PlainObj>(promisesMap: T>): Promise<{ [K in keyof T]: Awaited<T[K]>; }>`
|
|
201
|
+
|
|
202
|
+
A variation of `Promise.all()` that accepts an object with named promises and
|
|
203
|
+
returns a same-shaped object with the resolved values.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { promiseAllObject } from '@reykjavik/webtools/async';
|
|
207
|
+
|
|
208
|
+
const { user, posts } = await promiseAllObject({
|
|
209
|
+
user: fetchUser(),
|
|
210
|
+
posts: fetchPosts(),
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### `maxWait`
|
|
217
|
+
|
|
218
|
+
**Syntax:** `maxWait(timeout: number, promises: Array<any>): Promise<void>`
|
|
219
|
+
**Syntax:**
|
|
220
|
+
`maxWait<T extends PlainObj>(timeout: number, promises: T): Promise<{ [K in keyof T]: { value: Awaited<T[K]> } | undefined }>`
|
|
221
|
+
|
|
222
|
+
This somewhat esoteric helper resolves soon as all of the passed `promises`
|
|
223
|
+
have resolved, or after `timeout` milliseconds — whichever comes first.
|
|
224
|
+
|
|
225
|
+
If an object is passed, the resolved value will be an object with the same
|
|
226
|
+
keys, with undefined values for any promises that didn't resolve in time, and
|
|
227
|
+
the resolved values in a `value` container object.
|
|
228
|
+
|
|
229
|
+
If any of the promises reject, their values become undefined in the returned
|
|
230
|
+
object.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { maxWait } from '@reykjavik/webtools/async';
|
|
234
|
+
|
|
235
|
+
const user = fetchUser();
|
|
236
|
+
const posts = fetchPosts();
|
|
237
|
+
|
|
238
|
+
// Array of promises resolves to void
|
|
239
|
+
await maxWait(500, [user, posts]);
|
|
240
|
+
|
|
241
|
+
// Object of promises resolves to an object with any resolved values at that time
|
|
242
|
+
const { user, posts } = await maxWait(500, { user, posts });
|
|
243
|
+
|
|
244
|
+
console.log(user?.value); // undefined | User
|
|
245
|
+
console.log(posts?.value); // undefined | Array<Post>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## `@reykjavik/webtools/fixIcelandicLocale`
|
|
251
|
+
|
|
252
|
+
As of early 2024, Google Chrome still does not support the Icelandic locale
|
|
253
|
+
`is`/`is-IS` in any way. Meanwhile other browsers have supported it for over a
|
|
254
|
+
decade.
|
|
255
|
+
|
|
256
|
+
This module does attempts to patches the following methods/classes by
|
|
257
|
+
substituting the `is` locale with `da` (Danish) and apply a few post-hoc fixes
|
|
258
|
+
to their return values.
|
|
259
|
+
|
|
260
|
+
- `Intl.Collator` and `String.prototype.localeCompare`
|
|
261
|
+
- `Intl.NumberFormat` and `Number.prototype.toLocaleString`
|
|
262
|
+
- `Intl.DateTimeFormat` and `Date.prototype.toLocaleDateString`
|
|
263
|
+
- `Intl.PluralRules`
|
|
264
|
+
- `Intl.ListFormat`
|
|
265
|
+
|
|
266
|
+
This provides usable (but not perfect) results, with some caveats listed
|
|
267
|
+
below.
|
|
268
|
+
|
|
269
|
+
To apply these patches, simply "side-effect import" this module at the top of
|
|
270
|
+
your app's entry point:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import '@reykjavik/webtools/fixIcelandicLocale';
|
|
274
|
+
|
|
275
|
+
// Then continue with your day and use `localeCompare` and other Intl.* methods
|
|
276
|
+
// as you normally would. (See "limitations" below.)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
(**NOTE** The patch is only applied in engines that fail a simple feature
|
|
280
|
+
detection test.)
|
|
281
|
+
|
|
282
|
+
### Limitations
|
|
283
|
+
|
|
284
|
+
**`Intl.Collator` and `localeCompare`:**
|
|
285
|
+
|
|
286
|
+
- It incorrectly treats `ð` and `d` as the same letter (most of the time), and
|
|
287
|
+
the acute-accented characters `á`, `é`, `í`, `ó`, `ú` and `ý` get lumped in
|
|
288
|
+
with their non-accented counterparts (unless the compared).
|
|
289
|
+
We fix this only for the first letter in the string, but not for the rest of
|
|
290
|
+
it.
|
|
291
|
+
|
|
292
|
+
**`Intl.NumberFormat` and `toLocaleString`:**
|
|
293
|
+
|
|
294
|
+
- The `style: "unit"` option is not supported and prints units in Danish. (Soo
|
|
295
|
+
many units and unit-variants…)
|
|
296
|
+
- The `currencyDisplay: "name"` option is not supported and prints the
|
|
297
|
+
currency's full name in Danish.
|
|
298
|
+
|
|
299
|
+
**`Intl.DateTimeFormat` and `toLocaleDateString`:**
|
|
300
|
+
|
|
301
|
+
- The `month: 'narrow'` and `weekday: 'narrow'` options are not supported, and
|
|
302
|
+
print the corresponding Danish initials.
|
|
303
|
+
- For `timeZoneName` the values `"long"`, `"shortGeneric"` and `"longGeneric"`
|
|
304
|
+
will appear in Danish.
|
|
305
|
+
- The `timeStyle: 'full'` option prints the timezone names in Danish
|
|
306
|
+
- The `dayPeriod` option has a couple of slight mismatches, at 5 am and 12
|
|
307
|
+
noon.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## `@reykjavik/webtools/SiteImprove`
|
|
312
|
+
|
|
313
|
+
Contains React helpers for loading SiteImprove's analytics scripts, and
|
|
314
|
+
perform page-view and custom event tracking in applications with client-side
|
|
315
|
+
(`pushState`) routing.
|
|
316
|
+
|
|
317
|
+
### `SiteImprove` component
|
|
318
|
+
|
|
319
|
+
A component for loading a SiteImprove analytics script and set up page-view
|
|
320
|
+
tracking across URL routes.
|
|
321
|
+
|
|
322
|
+
It also automatically logs all out-bound link clicks, to match the behavior of
|
|
323
|
+
the vanilla SiteImprove script.
|
|
324
|
+
|
|
325
|
+
**Props:**
|
|
326
|
+
|
|
327
|
+
The Component's props have detailed JSDoc comments (displayed in your code
|
|
328
|
+
editor), but there's a brief summary:
|
|
329
|
+
|
|
330
|
+
- `accountId?: string` — Your SiteImprove account ID. (alternative to
|
|
331
|
+
`scriptUrl` prop).
|
|
332
|
+
- `scriptUrl?: string` — The full SiteImprove analytics script URL.
|
|
333
|
+
(alternative to `accountId` prop).
|
|
334
|
+
- `hasConsented?: boolean` — Manual GDPR 'analytics' consent flag. Allows hard
|
|
335
|
+
opt-out, but defers to
|
|
336
|
+
[`CookieHubProvider` values](./README.md#usecookiehubconsent) if they are
|
|
337
|
+
available.
|
|
338
|
+
- `onLoad?: (e: unknown) => void` — Fires when the script has loaded.
|
|
339
|
+
- `onError?: (e: unknown) => void` — Fires if loading the script failed.
|
|
340
|
+
|
|
341
|
+
Example usage somewhere in your application:
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
import { SiteImprove } from '@reykjavik/webtools/SiteImprove';
|
|
345
|
+
|
|
346
|
+
// ideally emit this from your loader function
|
|
347
|
+
const siteImproveAccountId = '[ACCOUNT_ID]'; // e.g. "7654321"
|
|
348
|
+
|
|
349
|
+
const location = useRouter()
|
|
350
|
+
// ...then Inside root.tsx component:
|
|
351
|
+
<SiteImprove
|
|
352
|
+
accountId={siteImproveAccountId}
|
|
353
|
+
onError={(error) =>
|
|
354
|
+
Logger('error', 'An error occured initializing siteimprove', error)
|
|
355
|
+
}
|
|
356
|
+
/>;
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
In dev mode it does NOT load the SiteImprove script and merely logs page-view
|
|
360
|
+
events to the console.
|
|
361
|
+
|
|
362
|
+
### `pingSiteImprove` helper
|
|
363
|
+
|
|
364
|
+
**Syntax:**
|
|
365
|
+
`pingSiteImprove(category: string, action: string, label?: string): void`
|
|
366
|
+
|
|
367
|
+
A small helper for tracking custom UI events and reporting them to SiteImrove.
|
|
368
|
+
|
|
369
|
+
It safely manages GDPR consent, so you can use it unconditionally.
|
|
370
|
+
|
|
371
|
+
```js
|
|
372
|
+
import { pingSiteImprove } from '@reykjavik/webtools/SiteImprove';
|
|
373
|
+
|
|
374
|
+
const handleSubmit = () => {
|
|
375
|
+
// perform submit action...
|
|
376
|
+
if (success) {
|
|
377
|
+
pingSiteImprove('application', 'add_new');
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### `pingSiteImproveOutbound` helper
|
|
383
|
+
|
|
384
|
+
**Syntax:** `pingSiteImproveOutbound(ourl: string): void`
|
|
385
|
+
|
|
386
|
+
A small helper for reporting to SiteImrove when the user is programmatically
|
|
387
|
+
being sent to a different URL/resource.
|
|
388
|
+
|
|
389
|
+
```js
|
|
390
|
+
import { pingSiteImproveOutbound } from '@reykjavik/webtools/SiteImprove';
|
|
391
|
+
|
|
392
|
+
const handleSubmit = () => {
|
|
393
|
+
// perform submit action...
|
|
394
|
+
if (success) {
|
|
395
|
+
const fileUrl = '/download/report.pdf';
|
|
396
|
+
pingSiteImproveOutbound(fileUrl);
|
|
397
|
+
document.location.href = fileUrl;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
187
404
|
## `@reykjavik/webtools/CookieHubConsent`
|
|
188
405
|
|
|
189
406
|
Contains React helpers for loading CookieHub's consent manager and reading
|
|
@@ -257,62 +474,6 @@ this hook will return an empty object.
|
|
|
257
474
|
|
|
258
475
|
---
|
|
259
476
|
|
|
260
|
-
## `@reykjavik/webtools/async`
|
|
261
|
-
|
|
262
|
-
Contains a few small helpers for working with async functions and promises.
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
### `promiseAllObject`
|
|
267
|
-
|
|
268
|
-
**Syntax:**
|
|
269
|
-
`promiseAllObject<T extends PlainObj>(promisesMap: T>): Promise<{ [K in keyof T]: Awaited<T[K]>; }>`
|
|
270
|
-
|
|
271
|
-
A variation of `Promise.all()` that accepts an object with named promises and
|
|
272
|
-
returns a same-shaped object with the resolved values.
|
|
273
|
-
|
|
274
|
-
```ts
|
|
275
|
-
import { promiseAllObject } from '@reykjavik/webtools/async';
|
|
276
|
-
|
|
277
|
-
const { user, posts } = await promiseAllObject({
|
|
278
|
-
user: fetchUser(),
|
|
279
|
-
posts: fetchPosts(),
|
|
280
|
-
});
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
### `maxWait`
|
|
286
|
-
|
|
287
|
-
**Syntax:** `maxWait(timeout: number, promises: Array<any>): Promise<void>`
|
|
288
|
-
**Syntax:**
|
|
289
|
-
`maxWait<T extends PlainObj>(timeout: number, promises: T): Promise<{ [K in keyof T]: { value: Awaited<T[K]> } | undefined }>`
|
|
290
|
-
|
|
291
|
-
This somewhat esoteric helper resolves soon as all of the passed `promises`
|
|
292
|
-
have resolved, or after `timeout` milliseconds — whichever comes first.
|
|
293
|
-
|
|
294
|
-
If an object is passed, the resolved value will be an object with the same
|
|
295
|
-
keys, with undefined values for any promises that didn't resolve in time, and
|
|
296
|
-
the resolved values in a `value` container object.
|
|
297
|
-
|
|
298
|
-
```ts
|
|
299
|
-
import { maxWait } from '@reykjavik/webtools/async';
|
|
300
|
-
|
|
301
|
-
const user = fetchUser();
|
|
302
|
-
const posts = fetchPosts();
|
|
303
|
-
|
|
304
|
-
// Array of promises resolves to void
|
|
305
|
-
await maxWait(500, [user, posts]);
|
|
306
|
-
|
|
307
|
-
// Object of promises resolves to an object with any resolved values at that time
|
|
308
|
-
const { user, posts } = await maxWait(500, { user, posts });
|
|
309
|
-
|
|
310
|
-
console.log(user?.value); // undefined | User
|
|
311
|
-
console.log(posts?.value); // undefined | Array<Post>
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
477
|
## `@reykjavik/webtools/vanillaExtract`
|
|
317
478
|
|
|
318
479
|
Contains helpers for writing [vanilla-extract](https://vanilla-extract.style)
|
|
@@ -486,68 +647,14 @@ vanillaGlobal(`
|
|
|
486
647
|
|
|
487
648
|
---
|
|
488
649
|
|
|
489
|
-
##
|
|
490
|
-
|
|
491
|
-
As of early 2004, Google Chrome still does not support the Icelandic locale
|
|
492
|
-
`is`/`is-IS` in any way. Meanwhile other browsers have supported it for over a
|
|
493
|
-
decade.
|
|
494
|
-
|
|
495
|
-
This module does attempts to patches the following methods/classes by
|
|
496
|
-
substituting the `is` locale with `da` (Danish) and apply a few post-hoc fixes
|
|
497
|
-
to their return values.
|
|
498
|
-
|
|
499
|
-
- `Intl.Collator` and `String.prototype.localeCompare`
|
|
500
|
-
- `Intl.NumberFormat` and `Number.prototype.toLocaleString`
|
|
501
|
-
- `Intl.DateTimeFormat` and `Date.prototype.toLocaleDateString`
|
|
502
|
-
- `Intl.PluralRules`
|
|
503
|
-
- `Intl.ListFormat`
|
|
504
|
-
|
|
505
|
-
This provides usable (but not perfect) results, with some caveats listed
|
|
506
|
-
below.
|
|
507
|
-
|
|
508
|
-
To apply these patches, simply "side-effect import" this module at the top of
|
|
509
|
-
your app's entry point:
|
|
510
|
-
|
|
511
|
-
```ts
|
|
512
|
-
import '@reykjavik/webtools/fixIcelandicLocale';
|
|
513
|
-
|
|
514
|
-
// Then continue with your day and use `localeCompare` and other Intl.* methods
|
|
515
|
-
// as you normally would. (See "limitations" below.)
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
(**NOTE** The patch is only applied in engines that fail a simple feature
|
|
519
|
-
detection test.)
|
|
520
|
-
|
|
521
|
-
### Limitations
|
|
522
|
-
|
|
523
|
-
**`Intl.Collator` and `localeCompare`:**
|
|
524
|
-
|
|
525
|
-
- It incorrectly treats `ð` and `d` as the same letter (most of the time), and
|
|
526
|
-
the acute-accented characters `á`, `é`, `í`, `ó`, `ú` and `ý` get lumped in
|
|
527
|
-
with their non-accented counterparts (unless the compared).
|
|
528
|
-
We fix this only for the first letter in the string, but not for the rest of
|
|
529
|
-
it.
|
|
530
|
-
|
|
531
|
-
**`Intl.NumberFormat` and `toLocaleString`:**
|
|
532
|
-
|
|
533
|
-
- The `style: "unit"` option is not supported and prints units in Danish. (Soo
|
|
534
|
-
many units and unit-variants…)
|
|
535
|
-
- The `currencyDisplay: "name"` option is not supported and prints the
|
|
536
|
-
currency's full name in Danish.
|
|
537
|
-
|
|
538
|
-
**`Intl.DateTimeFormat` and `toLocaleDateString`:**
|
|
539
|
-
|
|
540
|
-
- The `month: 'narrow'` and `weekday: 'narrow'` options are not supported, and
|
|
541
|
-
print the corresponding Danish initials.
|
|
542
|
-
- For `timeZoneName` the values `"long"`, `"shortGeneric"` and `"longGeneric"`
|
|
543
|
-
will appear in Danish.
|
|
544
|
-
- The `timeStyle: 'full'` option prints the timezone names in Danish
|
|
545
|
-
- The `dayPeriod` option has a couple of slight mismatches, at 5 am and 12
|
|
546
|
-
noon.
|
|
650
|
+
## Framework Specific Tools
|
|
547
651
|
|
|
548
652
|
---
|
|
549
653
|
|
|
550
|
-
|
|
654
|
+
### Remix.run Tools
|
|
655
|
+
|
|
656
|
+
See [README-remix.md](./README-remix.md) for helpers and components
|
|
657
|
+
specifically designed for use in Remix.run projects.
|
|
551
658
|
|
|
552
659
|
---
|
|
553
660
|
|
|
@@ -556,24 +663,11 @@ detection test.)
|
|
|
556
663
|
<a name="reykjavikwebtoolsnexthttp"></a> <a name="makeerrorizeapphoc"></a>
|
|
557
664
|
<a name="showerrorpage-helper"></a> <a name="notmodified304-helper"></a>
|
|
558
665
|
<a name="reykjavikwebtoolsnextsiteimprove"></a>
|
|
559
|
-
<a name="siteimprove-component"></a> <a name="pingsiteimprove-helper"></a>
|
|
560
|
-
<a name="pingsiteimproveoutbound-helper"></a>
|
|
561
666
|
|
|
562
667
|
### Next.js Tools
|
|
563
668
|
|
|
564
|
-
|
|
565
|
-
designed for use in
|
|
566
|
-
|
|
567
|
-
See [README-nextjs.md](./README-nextjs.md) for more info.
|
|
568
|
-
|
|
569
|
-
---
|
|
570
|
-
|
|
571
|
-
### Remix.run Tools
|
|
572
|
-
|
|
573
|
-
This package contains some helpers and components that are specifically
|
|
574
|
-
designed for use in [Remix.run](https://remix.run) projects.
|
|
575
|
-
|
|
576
|
-
See [README-remix.md](./README-remix.md) for more info.
|
|
669
|
+
See [README-nextjs.md](./README-nextjs.md) for helpers and components
|
|
670
|
+
specifically designed for use in Next.js projects.
|
|
577
671
|
|
|
578
672
|
---
|
|
579
673
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { EitherObj } from '@reykjavik/hanna-utils';
|
|
2
3
|
declare global {
|
|
3
4
|
interface Window {
|
|
@@ -14,8 +15,8 @@ declare global {
|
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
type SiteImproveEvent = SiteImprovePageView | SiteImproveRequest | SiteImproveCustomEvent;
|
|
19
|
+
type SiteImprovePageView = [
|
|
19
20
|
type: 'trackdynamic',
|
|
20
21
|
data: {
|
|
21
22
|
/** New page URL */
|
|
@@ -26,7 +27,7 @@ export type SiteImprovePageView = [
|
|
|
26
27
|
title?: string;
|
|
27
28
|
}
|
|
28
29
|
];
|
|
29
|
-
|
|
30
|
+
type SiteImproveRequest = [
|
|
30
31
|
type: 'request',
|
|
31
32
|
data: {
|
|
32
33
|
/** Outbound URL */
|
|
@@ -36,33 +37,25 @@ export type SiteImproveRequest = [
|
|
|
36
37
|
autoonclick?: 1;
|
|
37
38
|
}
|
|
38
39
|
];
|
|
39
|
-
|
|
40
|
+
type SiteImproveCustomEvent = [
|
|
40
41
|
type: 'event',
|
|
41
42
|
category: string,
|
|
42
43
|
action: string,
|
|
43
44
|
label?: string
|
|
44
45
|
];
|
|
45
|
-
/**
|
|
46
|
-
* A small helper to send "trackdynamic" page view/load events to SiteImrove.
|
|
47
|
-
*
|
|
48
|
-
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-nextjs.md#pingsiteimprove-helper
|
|
49
|
-
*/
|
|
50
|
-
export declare const trackDynamicPageView: (url: string, refUrl?: string, title?: string) => void;
|
|
51
46
|
/**
|
|
52
47
|
* A small helper for tracking custom UI events and reporting them to SiteImrove.
|
|
53
48
|
*
|
|
54
|
-
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README
|
|
49
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#pingsiteimprove-helper
|
|
55
50
|
*/
|
|
56
51
|
export declare const pingSiteImprove: (category: string, action: string, label?: string) => void;
|
|
57
52
|
/**
|
|
58
53
|
* A small helper for reporting to SiteImrove when the user is programmatically
|
|
59
54
|
* being sent to a different URL/resource.
|
|
60
55
|
*
|
|
61
|
-
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README
|
|
56
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#pingsiteimproveoutbound-helper
|
|
62
57
|
*/
|
|
63
58
|
export declare const pingSiteImproveOutbound: (ourl: string) => void;
|
|
64
|
-
export declare const logOutboundLinks: () => () => void;
|
|
65
|
-
export declare const makeScriptUrl: (accountId: string) => string;
|
|
66
59
|
export type SiteImproveProps = EitherObj<{
|
|
67
60
|
/**
|
|
68
61
|
* Your SiteImprove account ID.
|
|
@@ -88,7 +81,7 @@ export type SiteImproveProps = EitherObj<{
|
|
|
88
81
|
* A value of `true` still defers to the 'analytics' consent state provided
|
|
89
82
|
* by the `CookieHubProvider` component (if present).
|
|
90
83
|
*/
|
|
91
|
-
|
|
84
|
+
hasConsented?: boolean;
|
|
92
85
|
/**
|
|
93
86
|
* Custom callback for when the SiteImprove script has loaded.
|
|
94
87
|
*/
|
|
@@ -98,3 +91,11 @@ export type SiteImproveProps = EitherObj<{
|
|
|
98
91
|
*/
|
|
99
92
|
onError?: (e: unknown) => void;
|
|
100
93
|
};
|
|
94
|
+
/**
|
|
95
|
+
* A component for loading a SiteImprove analytics script and set up page-view
|
|
96
|
+
* tracking across client-side (pushState, replaceState) routing.
|
|
97
|
+
*
|
|
98
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#siteimprove-component
|
|
99
|
+
*/
|
|
100
|
+
export declare const SiteImprove: (props: SiteImproveProps) => React.JSX.Element | null;
|
|
101
|
+
export {};
|
package/SiteImprove.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.SiteImprove = exports.pingSiteImproveOutbound = exports.pingSiteImprove = void 0;
|
|
27
|
+
const react_1 = __importStar(require("react"));
|
|
28
|
+
const CookieHubConsent_js_1 = require("./CookieHubConsent.js");
|
|
29
|
+
// END: Mock typing of SiteImprove's event tracking API
|
|
30
|
+
// --------------------------------------------------------------------------
|
|
31
|
+
//
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
const _emitEvent = typeof window === 'undefined'
|
|
34
|
+
? () => undefined
|
|
35
|
+
: (event) => {
|
|
36
|
+
let _sz = window._sz;
|
|
37
|
+
if (!_sz) {
|
|
38
|
+
_sz = window._sz = [];
|
|
39
|
+
_sz._jit_defined_ = true;
|
|
40
|
+
}
|
|
41
|
+
_sz.push(event);
|
|
42
|
+
if (process.env.NODE_ENV === 'development') {
|
|
43
|
+
console.info('SiteImprove:', event);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* A small helper to send "trackdynamic" page view/load events to SiteImrove.
|
|
49
|
+
*/
|
|
50
|
+
const trackDynamicPageView = (url, refUrl, title) => _emitEvent(['trackdynamic', { url, ref: refUrl, title }]);
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* A small helper for tracking custom UI events and reporting them to SiteImrove.
|
|
54
|
+
*
|
|
55
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#pingsiteimprove-helper
|
|
56
|
+
*/
|
|
57
|
+
const pingSiteImprove = (category, action, label) => {
|
|
58
|
+
if (process.env.NODE_ENV === 'development' &&
|
|
59
|
+
(!window._sz || window._sz._jit_defined_)) {
|
|
60
|
+
console.warn('`pingSiteImprove` was called before SiteImprove script was loaded.');
|
|
61
|
+
}
|
|
62
|
+
_emitEvent(['event', category, action, label]);
|
|
63
|
+
};
|
|
64
|
+
exports.pingSiteImprove = pingSiteImprove;
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* A small helper for reporting to SiteImrove when the user is programmatically
|
|
68
|
+
* being sent to a different URL/resource.
|
|
69
|
+
*
|
|
70
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#pingsiteimproveoutbound-helper
|
|
71
|
+
*/
|
|
72
|
+
const pingSiteImproveOutbound = (ourl) => {
|
|
73
|
+
if (process.env.NODE_ENV === 'development' &&
|
|
74
|
+
(!window._sz || window._sz._jit_defined_)) {
|
|
75
|
+
console.warn('`pingSiteImproveOutbound` was called before SiteImprove script was loaded.');
|
|
76
|
+
}
|
|
77
|
+
_emitEvent(['request', { ourl, ref: document.location.href }]);
|
|
78
|
+
};
|
|
79
|
+
exports.pingSiteImproveOutbound = pingSiteImproveOutbound;
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
const logOutboundLinks = () => {
|
|
82
|
+
// NOTE: It's normal for SiteImprove's outbound logging to happen also for internal
|
|
83
|
+
// links, but it's not a problem since SiteImprove filter the results themselves.
|
|
84
|
+
const captureLinkClicks = (e) => {
|
|
85
|
+
const link = e.target.closest('a[href]');
|
|
86
|
+
if (!link || link.$$bound) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
link.$$bound = true;
|
|
90
|
+
// Waiting for the bubble phase allows other click handlers to preventDefault()
|
|
91
|
+
link.addEventListener('click', (e) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
if (e.defaultPrevented) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Skip logging outbound request if SiteImprove has already done so.
|
|
97
|
+
// BTW, SiteImprove binds its autoonclick handlers on "mousedown"
|
|
98
|
+
// so they're guaranteed to have run before our "click" listener.
|
|
99
|
+
const events = (_b = (_a = window._sz) === null || _a === void 0 ? void 0 : _a.core) === null || _b === void 0 ? void 0 : _b.data;
|
|
100
|
+
const [type, data] = (events && events[events.length - 1]) || [];
|
|
101
|
+
if (type === 'request' && data.autoonclick && data.ourl === link.href) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
(0, exports.pingSiteImproveOutbound)(link.href);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
const { body } = document;
|
|
108
|
+
// bind 'click' listener to the capture phase
|
|
109
|
+
body.addEventListener('click', captureLinkClicks, true);
|
|
110
|
+
return () => body.removeEventListener('click', captureLinkClicks, true);
|
|
111
|
+
};
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
const makeScriptUrl = (accountId) => `https://siteimproveanalytics.com/js/siteanalyze_${accountId}.js`;
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
const useResolvedAnalyticsConsent = (hasConsented) => {
|
|
116
|
+
const { analytics } = (0, CookieHubConsent_js_1.useCookieHubConsent)();
|
|
117
|
+
return ((analytics && hasConsented !== false) || (analytics === undefined && hasConsented));
|
|
118
|
+
};
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
const mockSIGlobalIfNeeded = (props) => {
|
|
121
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
if (!window._sz) {
|
|
124
|
+
console.info('Mock loading SiteImprove in development mode.', props.scriptUrl || props.accountId);
|
|
125
|
+
window._sz = [];
|
|
126
|
+
}
|
|
127
|
+
}, 300);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
// ===========================================================================
|
|
131
|
+
let lastUrl;
|
|
132
|
+
const loc = typeof document !== 'undefined' ? document.location : {};
|
|
133
|
+
/**
|
|
134
|
+
* A component for loading a SiteImprove analytics script and set up page-view
|
|
135
|
+
* tracking across client-side (pushState, replaceState) routing.
|
|
136
|
+
*
|
|
137
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#siteimprove-component
|
|
138
|
+
*/
|
|
139
|
+
const SiteImprove = (props) => {
|
|
140
|
+
var _a;
|
|
141
|
+
const consented = useResolvedAnalyticsConsent(props.hasConsented);
|
|
142
|
+
const thisUrl = `${loc.pathname}${loc.search}${loc.hash}`;
|
|
143
|
+
(0, react_1.useEffect)(() => {
|
|
144
|
+
if (!consented) {
|
|
145
|
+
lastUrl = thisUrl;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (lastUrl === undefined) {
|
|
149
|
+
lastUrl = thisUrl;
|
|
150
|
+
}
|
|
151
|
+
const checkCurrentUrl = () => {
|
|
152
|
+
if (thisUrl !== lastUrl) {
|
|
153
|
+
trackDynamicPageView(thisUrl, lastUrl, document.title);
|
|
154
|
+
lastUrl = thisUrl;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
checkCurrentUrl();
|
|
158
|
+
window.addEventListener('popstate', checkCurrentUrl);
|
|
159
|
+
return () => window.removeEventListener('popstate', checkCurrentUrl);
|
|
160
|
+
}, [consented, thisUrl]);
|
|
161
|
+
(0, react_1.useEffect)(() => {
|
|
162
|
+
if (!consented) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
mockSIGlobalIfNeeded(props);
|
|
166
|
+
return logOutboundLinks();
|
|
167
|
+
},
|
|
168
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
169
|
+
[consented]);
|
|
170
|
+
if (!consented || process.env.NODE_ENV !== 'production') {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const scriptUrl = (_a = props.scriptUrl) !== null && _a !== void 0 ? _a : makeScriptUrl(props.accountId);
|
|
174
|
+
return react_1.default.createElement("script", { defer: true, src: scriptUrl, onLoad: props.onLoad, onError: props.onError });
|
|
175
|
+
};
|
|
176
|
+
exports.SiteImprove = SiteImprove;
|