@we8/astro 0.1.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/README.md +157 -0
- package/components/ConsentBanner.astro +105 -0
- package/components/VisitBeacon.astro +58 -0
- package/components/We8Form.astro +94 -0
- package/dist/beacon.d.ts +32 -0
- package/dist/beacon.d.ts.map +1 -0
- package/dist/beacon.js +38 -0
- package/dist/beacon.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +58 -0
- package/dist/config.js.map +1 -0
- package/dist/consent.d.ts +61 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/consent.js +121 -0
- package/dist/consent.js.map +1 -0
- package/dist/content.d.ts +36 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +58 -0
- package/dist/content.js.map +1 -0
- package/dist/form.d.ts +22 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +39 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +40 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +37 -0
- package/dist/integration.js.map +1 -0
- package/dist/seo.d.ts +51 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +63 -0
- package/dist/seo.js.map +1 -0
- package/dist/session.d.ts +17 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +46 -0
- package/dist/session.js.map +1 -0
- package/dist/sitemap.d.ts +19 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +17 -0
- package/dist/sitemap.js.map +1 -0
- package/package.json +68 -0
package/dist/consent.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { getSessionId } from './session.js';
|
|
2
|
+
/** Where a visitor's consent decision is persisted in the browser. */
|
|
3
|
+
export const CONSENT_STORAGE_KEY = 'we8:consent';
|
|
4
|
+
/**
|
|
5
|
+
* The DOM event dispatched (and listened for) when a decision is recorded, so
|
|
6
|
+
* anything gated on consent (the visit beacon, an embedded map, a third-party
|
|
7
|
+
* script) can start the moment the visitor says yes, without a page reload.
|
|
8
|
+
*/
|
|
9
|
+
export const CONSENT_EVENT = 'we8:consent';
|
|
10
|
+
function defaultStorage() {
|
|
11
|
+
try {
|
|
12
|
+
return typeof localStorage !== 'undefined' ? localStorage : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Even referencing localStorage can throw in a sandboxed iframe.
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function defaultTarget() {
|
|
20
|
+
return typeof window !== 'undefined' ? window : null;
|
|
21
|
+
}
|
|
22
|
+
function isDecision(value) {
|
|
23
|
+
return value === 'accepted' || value === 'partial' || value === 'declined';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read the stored decision, or `null` when the visitor has not chosen yet (so
|
|
27
|
+
* a banner knows to show itself). A corrupt or unreadable store also reads as
|
|
28
|
+
* `null`: an unparseable decision is not a decision.
|
|
29
|
+
*/
|
|
30
|
+
export function readConsent(storage = defaultStorage()) {
|
|
31
|
+
if (!storage)
|
|
32
|
+
return null;
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = storage.getItem(CONSENT_STORAGE_KEY);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (!raw)
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
if (!isDecision(parsed.decision))
|
|
45
|
+
return null;
|
|
46
|
+
return {
|
|
47
|
+
decision: parsed.decision,
|
|
48
|
+
analytics: parsed.analytics === true,
|
|
49
|
+
performance: parsed.performance === true,
|
|
50
|
+
decidedAt: typeof parsed.decidedAt === 'string' ? parsed.decidedAt : '',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** True when the visitor has granted analytics consent. Convenience for gating a beacon. */
|
|
58
|
+
export function analyticsAllowed(storage = defaultStorage()) {
|
|
59
|
+
return readConsent(storage)?.analytics === true;
|
|
60
|
+
}
|
|
61
|
+
/** Persist a decision locally and announce it. Storage failures are non-fatal. */
|
|
62
|
+
export function writeConsent(state, storage = defaultStorage(), target = defaultTarget()) {
|
|
63
|
+
if (storage) {
|
|
64
|
+
try {
|
|
65
|
+
storage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(state));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// A blocked or full store must not break the banner; the decision still
|
|
69
|
+
// applies to this page view, it just will not survive a reload.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (target?.dispatchEvent && typeof CustomEvent === 'function') {
|
|
73
|
+
target.dispatchEvent(new CustomEvent(CONSENT_EVENT, { detail: state }));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Record a visitor's consent decision: persist it locally FIRST (so the
|
|
78
|
+
* decision is honored even if the network is down), announce it, then report
|
|
79
|
+
* it to the analytics API. Never rejects; a failed report is invisible to the
|
|
80
|
+
* visitor and does not weaken the decision.
|
|
81
|
+
*/
|
|
82
|
+
export async function recordConsent(client, choice, options = {}) {
|
|
83
|
+
const state = { ...choice, decidedAt: new Date().toISOString() };
|
|
84
|
+
writeConsent(state, options.storage === undefined ? defaultStorage() : options.storage, options.target === undefined ? defaultTarget() : options.target);
|
|
85
|
+
try {
|
|
86
|
+
await client.events.consent({
|
|
87
|
+
sessionId: options.sessionId ?? getSessionId(),
|
|
88
|
+
decision: state.decision,
|
|
89
|
+
analytics: state.analytics,
|
|
90
|
+
performance: state.performance,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Intentionally silent: the visitor's choice is already in effect.
|
|
95
|
+
}
|
|
96
|
+
return state;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Run `handler` once analytics consent exists: immediately if it already does,
|
|
100
|
+
* otherwise on the next consent decision that grants it. Returns a cleanup
|
|
101
|
+
* function that detaches the listener.
|
|
102
|
+
*/
|
|
103
|
+
export function onAnalyticsConsent(handler, options = {}) {
|
|
104
|
+
const storage = options.storage === undefined ? defaultStorage() : options.storage;
|
|
105
|
+
const target = options.target === undefined ? defaultTarget() : options.target;
|
|
106
|
+
if (analyticsAllowed(storage)) {
|
|
107
|
+
handler();
|
|
108
|
+
return () => undefined;
|
|
109
|
+
}
|
|
110
|
+
if (!target)
|
|
111
|
+
return () => undefined;
|
|
112
|
+
const listener = () => {
|
|
113
|
+
if (analyticsAllowed(storage)) {
|
|
114
|
+
target.removeEventListener(CONSENT_EVENT, listener);
|
|
115
|
+
handler();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
target.addEventListener(CONSENT_EVENT, listener);
|
|
119
|
+
return () => target.removeEventListener(CONSENT_EVENT, listener);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SiteDocumentKind, We8Document, We8PostDetail, We8PostList, We8PostListParams, We8SiteConfig } from '@we8/client';
|
|
2
|
+
import { type We8AstroConfig } from './config.js';
|
|
3
|
+
export interface BuildFetchOptions {
|
|
4
|
+
/** Explicit config, overriding the env-derived default. */
|
|
5
|
+
config?: We8AstroConfig;
|
|
6
|
+
/** A fetch implementation (tests, or a custom transport). */
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build-time: fetch a page of published posts. Call this from an Astro
|
|
11
|
+
* component's frontmatter (runs in Node during `astro build`). Requires
|
|
12
|
+
* `WE8_KEY` and `WE8_API_URL` in the environment.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPosts(params?: We8PostListParams, options?: BuildFetchOptions): Promise<We8PostList>;
|
|
15
|
+
/** Build-time: fetch a single published post (with its JSON-LD) by slug. */
|
|
16
|
+
export declare function getPost(slug: string, options?: BuildFetchOptions): Promise<We8PostDetail>;
|
|
17
|
+
/**
|
|
18
|
+
* Build-time: page through every published post and collect its slug. Handy for
|
|
19
|
+
* an Astro `getStaticPaths` (one page per post) and for sitemap merging. Uses
|
|
20
|
+
* the LARGEST page size the engine accepts and stops when it has seen `total`
|
|
21
|
+
* posts or an empty page.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getPostSlugs(options?: BuildFetchOptions): Promise<string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Build-time: fetch the site identity card (name, canonical URL, SEO
|
|
26
|
+
* fallbacks, publisher, posts path). Pair with `mergeSeo` for the code-first
|
|
27
|
+
* merge: your template's values win, this config fills the gaps.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getSiteConfig(options?: BuildFetchOptions): Promise<We8SiteConfig>;
|
|
30
|
+
/**
|
|
31
|
+
* Build-time: fetch a site document (design.md or llms.txt, ME3b). Content is
|
|
32
|
+
* `''` when the tenant never wrote one; a starter route can render that
|
|
33
|
+
* straight through (an empty file) or skip serving it, its choice.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getDocument(kind: SiteDocumentKind, options?: BuildFetchOptions): Promise<We8Document>;
|
|
36
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACd,MAAM,aAAa,CAAC;AACrB,OAAO,EAAoC,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAEpF,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAMD;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,MAAM,CAAC,EAAE,iBAAiB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAEtB;AAED,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAE7F;AAKD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAcrF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAErF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAEtB"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createBuildClient, resolveConfig } from './config.js';
|
|
2
|
+
function clientFor(options) {
|
|
3
|
+
return createBuildClient(options.config ?? resolveConfig(), options.fetch);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Build-time: fetch a page of published posts. Call this from an Astro
|
|
7
|
+
* component's frontmatter (runs in Node during `astro build`). Requires
|
|
8
|
+
* `WE8_KEY` and `WE8_API_URL` in the environment.
|
|
9
|
+
*/
|
|
10
|
+
export function getPosts(params, options = {}) {
|
|
11
|
+
return clientFor(options).posts.list(params);
|
|
12
|
+
}
|
|
13
|
+
/** Build-time: fetch a single published post (with its JSON-LD) by slug. */
|
|
14
|
+
export function getPost(slug, options = {}) {
|
|
15
|
+
return clientFor(options).posts.get(slug);
|
|
16
|
+
}
|
|
17
|
+
/** A safety cap on pagination so a bad `total` can never spin forever. */
|
|
18
|
+
const MAX_SLUG_PAGES = 1000;
|
|
19
|
+
/**
|
|
20
|
+
* Build-time: page through every published post and collect its slug. Handy for
|
|
21
|
+
* an Astro `getStaticPaths` (one page per post) and for sitemap merging. Uses
|
|
22
|
+
* the LARGEST page size the engine accepts and stops when it has seen `total`
|
|
23
|
+
* posts or an empty page.
|
|
24
|
+
*/
|
|
25
|
+
export async function getPostSlugs(options = {}) {
|
|
26
|
+
const client = clientFor(options);
|
|
27
|
+
const slugs = [];
|
|
28
|
+
// paginationQuerySchema caps pageSize at 50 (packages/shared); asking for
|
|
29
|
+
// more is a 400 from the real engine, which fakes can't see (Gate R5 catch:
|
|
30
|
+
// the original 100 here passed every fake-backed test and would have made
|
|
31
|
+
// every real getStaticPaths/mergeSitemap call fail at build time).
|
|
32
|
+
const pageSize = 50;
|
|
33
|
+
for (let page = 1; page <= MAX_SLUG_PAGES; page++) {
|
|
34
|
+
const { items, total } = await client.posts.list({ page, pageSize });
|
|
35
|
+
for (const post of items)
|
|
36
|
+
slugs.push(post.slug);
|
|
37
|
+
if (items.length === 0 || slugs.length >= total)
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
return slugs;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build-time: fetch the site identity card (name, canonical URL, SEO
|
|
44
|
+
* fallbacks, publisher, posts path). Pair with `mergeSeo` for the code-first
|
|
45
|
+
* merge: your template's values win, this config fills the gaps.
|
|
46
|
+
*/
|
|
47
|
+
export function getSiteConfig(options = {}) {
|
|
48
|
+
return clientFor(options).site.config();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build-time: fetch a site document (design.md or llms.txt, ME3b). Content is
|
|
52
|
+
* `''` when the tenant never wrote one; a starter route can render that
|
|
53
|
+
* straight through (an empty file) or skip serving it, its choice.
|
|
54
|
+
*/
|
|
55
|
+
export function getDocument(kind, options = {}) {
|
|
56
|
+
return clientFor(options).documents.get(kind);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAC;AASpF,SAAS,SAAS,CAAC,OAA0B;IAC3C,OAAO,iBAAiB,CAAC,OAAO,CAAC,MAAM,IAAI,aAAa,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CACtB,MAA0B,EAC1B,UAA6B,EAAE;IAE/B,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,UAA6B,EAAE;IACnE,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,0EAA0E;AAC1E,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA6B,EAAE;IAChE,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,0EAA0E;IAC1E,4EAA4E;IAC5E,0EAA0E;IAC1E,mEAAmE;IACnE,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,cAAc,EAAE,IAAI,EAAE,EAAE,CAAC;QAClD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrE,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK;YAAE,MAAM;IACzD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAA6B,EAAE;IAC3D,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,IAAsB,EACtB,UAA6B,EAAE;IAE/B,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { We8Client, We8FormSubmitResult } from '@we8/client';
|
|
2
|
+
export interface EnhanceFormOptions {
|
|
3
|
+
/** The form key to submit to (`/v1/forms/{formKey}/submissions`). */
|
|
4
|
+
formKey: string;
|
|
5
|
+
/**
|
|
6
|
+
* The name of the Turnstile token field to lift out of the form data and pass
|
|
7
|
+
* as the SDK's `turnstileToken`. Defaults to Cloudflare Turnstile's
|
|
8
|
+
* `cf-turnstile-response`. The engine expects the token as `turnstileToken`,
|
|
9
|
+
* so this mapping is what makes a stock Turnstile widget just work.
|
|
10
|
+
*/
|
|
11
|
+
turnstileField?: string;
|
|
12
|
+
onSuccess?: (result: We8FormSubmitResult) => void;
|
|
13
|
+
onError?: (error: unknown) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Progressively enhance a native `<form>`: intercept submit, POST it through
|
|
17
|
+
* the we8 SDK (so the page still works without JS as a plain form as long as the
|
|
18
|
+
* template provides a no-JS fallback), and route the outcome to `onSuccess` /
|
|
19
|
+
* `onError`. Returns a cleanup function that detaches the listener.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enhanceForm(form: HTMLFormElement, client: We8Client, options: EnhanceFormOptions): () => void;
|
|
22
|
+
//# sourceMappingURL=form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAmBD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,kBAAkB,GAC1B,MAAM,IAAI,CAiBZ"}
|
package/dist/form.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const DEFAULT_TURNSTILE_FIELD = 'cf-turnstile-response';
|
|
2
|
+
/** Collect a form's fields into a plain data object for `forms.submit`. */
|
|
3
|
+
function formToData(form) {
|
|
4
|
+
const data = {};
|
|
5
|
+
for (const [key, value] of new FormData(form).entries()) {
|
|
6
|
+
data[key] = typeof value === 'string' ? value : value.name;
|
|
7
|
+
}
|
|
8
|
+
return data;
|
|
9
|
+
}
|
|
10
|
+
function takeField(data, key) {
|
|
11
|
+
const value = data[key];
|
|
12
|
+
delete data[key];
|
|
13
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Progressively enhance a native `<form>`: intercept submit, POST it through
|
|
17
|
+
* the we8 SDK (so the page still works without JS as a plain form as long as the
|
|
18
|
+
* template provides a no-JS fallback), and route the outcome to `onSuccess` /
|
|
19
|
+
* `onError`. Returns a cleanup function that detaches the listener.
|
|
20
|
+
*/
|
|
21
|
+
export function enhanceForm(form, client, options) {
|
|
22
|
+
const handler = (event) => {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
const data = formToData(form);
|
|
25
|
+
const turnstileToken = takeField(data, options.turnstileField ?? DEFAULT_TURNSTILE_FIELD);
|
|
26
|
+
void client.forms
|
|
27
|
+
.submit(options.formKey, data, turnstileToken ? { turnstileToken } : {})
|
|
28
|
+
.then((result) => {
|
|
29
|
+
options.onSuccess?.(result);
|
|
30
|
+
form.reset();
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
33
|
+
options.onError?.(error);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
form.addEventListener('submit', handler);
|
|
37
|
+
return () => form.removeEventListener('submit', handler);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=form.js.map
|
package/dist/form.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAgBA,MAAM,uBAAuB,GAAG,uBAAuB,CAAC;AAExD,2EAA2E;AAC3E,SAAS,UAAU,CAAC,IAAqB;IACvC,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACxD,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IAC7D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,IAA6B,EAAE,GAAW;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAAqB,EACrB,MAAiB,EACjB,OAA2B;IAE3B,MAAM,OAAO,GAAG,CAAC,KAAkB,EAAQ,EAAE;QAC3C,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,IAAI,uBAAuB,CAAC,CAAC;QAC1F,KAAK,MAAM,CAAC,KAAK;aACd,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvE,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACf,OAAO,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IACF,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { we8 } from './integration.js';
|
|
2
|
+
export type { We8AstroIntegration, We8IntegrationOptions } from './integration.js';
|
|
3
|
+
export { API_URL_ENV, KEY_ENV, createBuildClient, resolveConfig, tryResolveConfig } from './config.js';
|
|
4
|
+
export type { EnvLike, We8AstroConfig } from './config.js';
|
|
5
|
+
export { getDocument, getPost, getPostSlugs, getPosts, getSiteConfig } from './content.js';
|
|
6
|
+
export type { BuildFetchOptions } from './content.js';
|
|
7
|
+
export { mergeSeo, postUrl, robotsTxt } from './seo.js';
|
|
8
|
+
export type { PageSeo, ResolvedSeo } from './seo.js';
|
|
9
|
+
export { mergeSitemap, we8SitemapUrl } from './sitemap.js';
|
|
10
|
+
export type { MergeSitemapOptions } from './sitemap.js';
|
|
11
|
+
export { getSessionId } from './session.js';
|
|
12
|
+
export type { StorageLike } from './session.js';
|
|
13
|
+
export { sendVisit, trackVisit } from './beacon.js';
|
|
14
|
+
export type { BeaconOptions, TrackVisitOptions } from './beacon.js';
|
|
15
|
+
export { CONSENT_EVENT, CONSENT_STORAGE_KEY, analyticsAllowed, onAnalyticsConsent, readConsent, recordConsent, writeConsent, } from './consent.js';
|
|
16
|
+
export type { ConsentState, EventTargetLike, RecordConsentOptions } from './consent.js';
|
|
17
|
+
export { enhanceForm } from './form.js';
|
|
18
|
+
export type { EnhanceFormOptions } from './form.js';
|
|
19
|
+
export { We8ApiError, createClient, isWe8ApiError } from '@we8/client';
|
|
20
|
+
export type { ConsentDecision, Cta, PostType, SiteDocumentKind, We8Author, We8Client, We8ClientOptions, We8Document, We8FormData, We8FormSubmitResult, We8Post, We8PostDetail, We8PostList, We8PostListParams, We8SiteConfig, } from '@we8/client';
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvG,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3F,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxD,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAKpD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EACV,eAAe,EACf,GAAG,EACH,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,OAAO,EACP,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,GACd,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { we8 } from './integration.js';
|
|
2
|
+
export { API_URL_ENV, KEY_ENV, createBuildClient, resolveConfig, tryResolveConfig } from './config.js';
|
|
3
|
+
export { getDocument, getPost, getPostSlugs, getPosts, getSiteConfig } from './content.js';
|
|
4
|
+
export { mergeSeo, postUrl, robotsTxt } from './seo.js';
|
|
5
|
+
export { mergeSitemap, we8SitemapUrl } from './sitemap.js';
|
|
6
|
+
export { getSessionId } from './session.js';
|
|
7
|
+
export { sendVisit, trackVisit } from './beacon.js';
|
|
8
|
+
export { CONSENT_EVENT, CONSENT_STORAGE_KEY, analyticsAllowed, onAnalyticsConsent, readConsent, recordConsent, writeConsent, } from './consent.js';
|
|
9
|
+
export { enhanceForm } from './form.js';
|
|
10
|
+
// Re-export the client factory plus key types so a template can `import
|
|
11
|
+
// { createClient } from '@we8/astro'` for its own custom calls without a
|
|
12
|
+
// second dependency.
|
|
13
|
+
export { We8ApiError, createClient, isWe8ApiError } from '@we8/client';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEvG,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,YAAY,GACb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,wEAAwE;AACxE,yEAAyE;AACzE,qBAAqB;AACrB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface We8IntegrationOptions {
|
|
2
|
+
/** Override the env-derived key (otherwise read from `WE8_PUBLISHABLE_KEY`). */
|
|
3
|
+
key?: string;
|
|
4
|
+
/** Override the env-derived API URL (otherwise read from `WE8_API_URL`). */
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Suppress the missing-configuration warnings. Set this in a template that
|
|
8
|
+
* deliberately falls back to its own data source (fixtures, a different
|
|
9
|
+
* backend) when we8 is not configured, so an intentional offline build is
|
|
10
|
+
* quiet rather than noisy.
|
|
11
|
+
*/
|
|
12
|
+
optional?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** A tiny slice of Astro's logger, all this integration uses. */
|
|
15
|
+
interface HookLogger {
|
|
16
|
+
info(message: string): void;
|
|
17
|
+
warn(message: string): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A structural match for Astro's `AstroIntegration`, declared locally so this
|
|
21
|
+
* package can be typechecked without Astro installed (Astro is an optional peer
|
|
22
|
+
* dependency). The object it describes is a valid `AstroIntegration` at runtime.
|
|
23
|
+
*/
|
|
24
|
+
export interface We8AstroIntegration {
|
|
25
|
+
name: string;
|
|
26
|
+
hooks: {
|
|
27
|
+
'astro:config:setup'?: (options: {
|
|
28
|
+
logger?: HookLogger;
|
|
29
|
+
}) => void;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The we8 Astro integration. Add it to `integrations` in `astro.config`. It
|
|
34
|
+
* checks configuration at build start and warns (rather than silently
|
|
35
|
+
* proceeding) when the key or the API URL is missing, so a misconfigured site
|
|
36
|
+
* fails loudly instead of shipping a dead beacon or empty content.
|
|
37
|
+
*/
|
|
38
|
+
export declare function we8(options?: We8IntegrationOptions): We8AstroIntegration;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=integration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,qBAAqB;IACpC,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iEAAiE;AACjE,UAAU,UAAU;IAClB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACL,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE;YAAE,MAAM,CAAC,EAAE,UAAU,CAAA;SAAE,KAAK,IAAI,CAAC;KACnE,CAAC;CACH;AAOD;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,qBAA0B,GAAG,mBAAmB,CA4B5E"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { API_URL_ENV, KEY_ENV } from './config.js';
|
|
2
|
+
function readEnv(key) {
|
|
3
|
+
const g = globalThis;
|
|
4
|
+
return g.process?.env?.[key];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The we8 Astro integration. Add it to `integrations` in `astro.config`. It
|
|
8
|
+
* checks configuration at build start and warns (rather than silently
|
|
9
|
+
* proceeding) when the key or the API URL is missing, so a misconfigured site
|
|
10
|
+
* fails loudly instead of shipping a dead beacon or empty content.
|
|
11
|
+
*/
|
|
12
|
+
export function we8(options = {}) {
|
|
13
|
+
return {
|
|
14
|
+
name: '@we8/astro',
|
|
15
|
+
hooks: {
|
|
16
|
+
'astro:config:setup': ({ logger }) => {
|
|
17
|
+
const key = options.key ?? readEnv(KEY_ENV);
|
|
18
|
+
const apiUrl = options.apiUrl ?? readEnv(API_URL_ENV);
|
|
19
|
+
if (key && apiUrl) {
|
|
20
|
+
logger?.info(`ready (API ${apiUrl}).`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (options.optional) {
|
|
24
|
+
logger?.info('not configured; the site builds from its own data source.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!key) {
|
|
28
|
+
logger?.warn(`${KEY_ENV} is not set; the visit beacon, consent, and forms will not authenticate until it is.`);
|
|
29
|
+
}
|
|
30
|
+
if (!apiUrl) {
|
|
31
|
+
logger?.warn(`${API_URL_ENV} is not set; build-time content (getPosts/getPost) will fail until it is.`);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAkCnD,SAAS,OAAO,CAAC,GAAW;IAC1B,MAAM,CAAC,GAAG,UAAwE,CAAC;IACnF,OAAO,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,UAAiC,EAAE;IACrD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE;YACL,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;gBACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;gBACtD,IAAI,GAAG,IAAI,MAAM,EAAE,CAAC;oBAClB,MAAM,EAAE,IAAI,CAAC,cAAc,MAAM,IAAI,CAAC,CAAC;oBACvC,OAAO;gBACT,CAAC;gBACD,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;oBACrB,MAAM,EAAE,IAAI,CAAC,2DAA2D,CAAC,CAAC;oBAC1E,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC,GAAG,EAAE,CAAC;oBACT,MAAM,EAAE,IAAI,CACV,GAAG,OAAO,sFAAsF,CACjG,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,MAAM,EAAE,IAAI,CACV,GAAG,WAAW,2EAA2E,CAC1F,CAAC;gBACJ,CAAC;YACH,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { PostType, We8SiteConfig } from '@we8/client';
|
|
2
|
+
/** The SEO values a page (or layout) declares for itself. All optional. */
|
|
3
|
+
export interface PageSeo {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
ogTitle?: string;
|
|
7
|
+
ogDescription?: string;
|
|
8
|
+
ogImageUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
/** The resolved head values after the code-first merge. */
|
|
11
|
+
export interface ResolvedSeo {
|
|
12
|
+
/** The full document title (template applied when configured). */
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
ogTitle?: string;
|
|
16
|
+
ogDescription?: string;
|
|
17
|
+
ogImageUrl?: string;
|
|
18
|
+
twitterHandle?: string;
|
|
19
|
+
/** True when the site is configured to discourage search engines. */
|
|
20
|
+
noindex: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The code-first merge (MC): the PAGE's values always win; the site config
|
|
24
|
+
* fills the gaps. Title semantics: a page title gets the site's titleTemplate
|
|
25
|
+
* applied ("%s" is the placeholder); a page WITHOUT its own title falls back
|
|
26
|
+
* to seo.defaultTitle (template NOT applied, it already names the site), then
|
|
27
|
+
* the site name. OG fields cascade page og -> page base -> site og -> site
|
|
28
|
+
* base, so a page description doubles as its og:description unless overridden.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mergeSeo(page: PageSeo, site: We8SiteConfig): ResolvedSeo;
|
|
31
|
+
/**
|
|
32
|
+
* A robots.txt body honoring the site's discourageSearchEngines flag. Serve it
|
|
33
|
+
* from a template endpoint (e.g. `src/pages/robots.txt.ts` returning this
|
|
34
|
+
* string); pass `sitemapUrl` (from `we8SitemapUrl()`) to advertise the sitemap
|
|
35
|
+
* when the site is indexable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function robotsTxt(site: We8SiteConfig, options?: {
|
|
38
|
+
sitemapUrl?: string;
|
|
39
|
+
}): string;
|
|
40
|
+
/**
|
|
41
|
+
* The absolute URL a post is published at on the tenant's own site: the
|
|
42
|
+
* type's section path from the resolved contentPaths map (every type present
|
|
43
|
+
* in the public site config), joined with the slug. The single source of
|
|
44
|
+
* truth for post URLs in templates; pairs with the engine's sitemap and
|
|
45
|
+
* JSON-LD, which resolve through the same rule.
|
|
46
|
+
*/
|
|
47
|
+
export declare function postUrl(post: {
|
|
48
|
+
slug: string;
|
|
49
|
+
type: PostType;
|
|
50
|
+
}, site: We8SiteConfig): string;
|
|
51
|
+
//# sourceMappingURL=seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG3D,2EAA2E;AAC3E,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CA6BxE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAO5F;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAI3F"}
|
package/dist/seo.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code-first merge (MC): the PAGE's values always win; the site config
|
|
3
|
+
* fills the gaps. Title semantics: a page title gets the site's titleTemplate
|
|
4
|
+
* applied ("%s" is the placeholder); a page WITHOUT its own title falls back
|
|
5
|
+
* to seo.defaultTitle (template NOT applied, it already names the site), then
|
|
6
|
+
* the site name. OG fields cascade page og -> page base -> site og -> site
|
|
7
|
+
* base, so a page description doubles as its og:description unless overridden.
|
|
8
|
+
*/
|
|
9
|
+
export function mergeSeo(page, site) {
|
|
10
|
+
const seo = site.seo;
|
|
11
|
+
const title = page.title
|
|
12
|
+
? seo.titleTemplate
|
|
13
|
+
? seo.titleTemplate.replace('%s', page.title)
|
|
14
|
+
: page.title
|
|
15
|
+
: (seo.defaultTitle ?? site.name ?? '');
|
|
16
|
+
const description = page.description ?? seo.metaDescription;
|
|
17
|
+
const resolved = {
|
|
18
|
+
title,
|
|
19
|
+
noindex: seo.discourageSearchEngines,
|
|
20
|
+
};
|
|
21
|
+
if (description !== undefined)
|
|
22
|
+
resolved.description = description;
|
|
23
|
+
const ogTitle = page.ogTitle ?? page.title ?? seo.ogTitle ?? seo.defaultTitle;
|
|
24
|
+
if (ogTitle !== undefined)
|
|
25
|
+
resolved.ogTitle = ogTitle;
|
|
26
|
+
const ogDescription = page.ogDescription ?? page.description ?? seo.ogDescription ?? seo.metaDescription;
|
|
27
|
+
if (ogDescription !== undefined)
|
|
28
|
+
resolved.ogDescription = ogDescription;
|
|
29
|
+
const ogImageUrl = page.ogImageUrl ?? seo.ogImageUrl;
|
|
30
|
+
if (ogImageUrl !== undefined)
|
|
31
|
+
resolved.ogImageUrl = ogImageUrl;
|
|
32
|
+
if (seo.twitterHandle !== undefined)
|
|
33
|
+
resolved.twitterHandle = seo.twitterHandle;
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A robots.txt body honoring the site's discourageSearchEngines flag. Serve it
|
|
38
|
+
* from a template endpoint (e.g. `src/pages/robots.txt.ts` returning this
|
|
39
|
+
* string); pass `sitemapUrl` (from `we8SitemapUrl()`) to advertise the sitemap
|
|
40
|
+
* when the site is indexable.
|
|
41
|
+
*/
|
|
42
|
+
export function robotsTxt(site, options = {}) {
|
|
43
|
+
if (site.seo.discourageSearchEngines) {
|
|
44
|
+
return 'User-agent: *\nDisallow: /\n';
|
|
45
|
+
}
|
|
46
|
+
const lines = ['User-agent: *', 'Allow: /'];
|
|
47
|
+
if (options.sitemapUrl)
|
|
48
|
+
lines.push('', `Sitemap: ${options.sitemapUrl}`);
|
|
49
|
+
return lines.join('\n') + '\n';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The absolute URL a post is published at on the tenant's own site: the
|
|
53
|
+
* type's section path from the resolved contentPaths map (every type present
|
|
54
|
+
* in the public site config), joined with the slug. The single source of
|
|
55
|
+
* truth for post URLs in templates; pairs with the engine's sitemap and
|
|
56
|
+
* JSON-LD, which resolve through the same rule.
|
|
57
|
+
*/
|
|
58
|
+
export function postUrl(post, site) {
|
|
59
|
+
const prefix = site.contentPaths[post.type] ?? site.postsPathPrefix;
|
|
60
|
+
const base = site.siteUrl.replace(/\/+$/, '');
|
|
61
|
+
return `${base}${prefix}/${post.slug}`;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=seo.js.map
|
package/dist/seo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.js","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AAyBA;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAa,EAAE,IAAmB;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IAErB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;QACtB,CAAC,CAAC,GAAG,CAAC,aAAa;YACjB,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;YAC7C,CAAC,CAAC,IAAI,CAAC,KAAK;QACd,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAE1C,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,eAAe,CAAC;IAC5D,MAAM,QAAQ,GAAgB;QAC5B,KAAK;QACL,OAAO,EAAE,GAAG,CAAC,uBAAuB;KACrC,CAAC;IACF,IAAI,WAAW,KAAK,SAAS;QAAE,QAAQ,CAAC,WAAW,GAAG,WAAW,CAAC;IAElE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,YAAY,CAAC;IAC9E,IAAI,OAAO,KAAK,SAAS;QAAE,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC;IAEtD,MAAM,aAAa,GACjB,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,eAAe,CAAC;IACrF,IAAI,aAAa,KAAK,SAAS;QAAE,QAAQ,CAAC,aAAa,GAAG,aAAa,CAAC;IAExE,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC;IACrD,IAAI,UAAU,KAAK,SAAS;QAAE,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAC;IAE/D,IAAI,GAAG,CAAC,aAAa,KAAK,SAAS;QAAE,QAAQ,CAAC,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC;IAEhF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAmB,EAAE,UAAmC,EAAE;IAClF,IAAI,IAAI,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC;QACrC,OAAO,8BAA8B,CAAC;IACxC,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAC5C,IAAI,OAAO,CAAC,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,YAAY,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACzE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,IAAsC,EAAE,IAAmB;IACjF,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC;IACpE,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9C,OAAO,GAAG,IAAI,GAAG,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The minimal storage surface `getSessionId` needs; `localStorage` satisfies it. */
|
|
2
|
+
export interface StorageLike {
|
|
3
|
+
getItem(key: string): string | null;
|
|
4
|
+
setItem(key: string, value: string): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A stable per-visitor session id, persisted in `localStorage` so views dedupe
|
|
8
|
+
* and a visitor's events group across a session. Pass a `storage` in tests. In
|
|
9
|
+
* an SSR context (no storage) it returns a fresh ephemeral id each call.
|
|
10
|
+
*
|
|
11
|
+
* The whole read/write is guarded: a storage that exists but is unusable (a
|
|
12
|
+
* sandboxed iframe, cookies blocked, quota exceeded, or Node's `localStorage`
|
|
13
|
+
* without a backing file) throws on `getItem`/`setItem`, and we degrade to an
|
|
14
|
+
* ephemeral id rather than letting analytics break the page.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getSessionId(storage?: StorageLike | null): string;
|
|
17
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAuBD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,WAAW,GAAG,IAAuB,GAAG,MAAM,CAcnF"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const SESSION_KEY = 'we8:sessionId';
|
|
2
|
+
function randomId() {
|
|
3
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
4
|
+
return crypto.randomUUID();
|
|
5
|
+
}
|
|
6
|
+
// Defensive fallback for a browser without crypto.randomUUID. Not used for
|
|
7
|
+
// anything security-sensitive: this id only dedupes views and groups a
|
|
8
|
+
// visitor's events.
|
|
9
|
+
return `s_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
|
|
10
|
+
}
|
|
11
|
+
function defaultStorage() {
|
|
12
|
+
try {
|
|
13
|
+
return typeof localStorage !== 'undefined' ? localStorage : null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Even referencing localStorage can throw in a sandboxed iframe.
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A stable per-visitor session id, persisted in `localStorage` so views dedupe
|
|
22
|
+
* and a visitor's events group across a session. Pass a `storage` in tests. In
|
|
23
|
+
* an SSR context (no storage) it returns a fresh ephemeral id each call.
|
|
24
|
+
*
|
|
25
|
+
* The whole read/write is guarded: a storage that exists but is unusable (a
|
|
26
|
+
* sandboxed iframe, cookies blocked, quota exceeded, or Node's `localStorage`
|
|
27
|
+
* without a backing file) throws on `getItem`/`setItem`, and we degrade to an
|
|
28
|
+
* ephemeral id rather than letting analytics break the page.
|
|
29
|
+
*/
|
|
30
|
+
export function getSessionId(storage = defaultStorage()) {
|
|
31
|
+
if (storage) {
|
|
32
|
+
try {
|
|
33
|
+
let id = storage.getItem(SESSION_KEY);
|
|
34
|
+
if (!id) {
|
|
35
|
+
id = randomId();
|
|
36
|
+
storage.setItem(SESSION_KEY, id);
|
|
37
|
+
}
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Fall through to an ephemeral id below.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return randomId();
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,GAAG,eAAe,CAAC;AAEpC,SAAS,QAAQ;IACf,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QAC7E,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IACD,2EAA2E;IAC3E,uEAAuE;IACvE,oBAAoB;IACpB,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,OAAO,OAAO,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;QACjE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,UAA8B,cAAc,EAAE;IACzE,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACtC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAChB,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACnC,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,yCAAyC;QAC3C,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,EAAE,CAAC;AACpB,CAAC"}
|