@reforma/site 0.0.1 → 0.0.3
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/analytics.d.ts +20 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +118 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +6 -14
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type AnalyticsOptions = {
|
|
2
|
+
/** Test-only: allow re-init and clear history hooks. */
|
|
3
|
+
reset?: boolean;
|
|
4
|
+
/** Default false — skip localhost / 127.0.0.1. */
|
|
5
|
+
captureOnLocalhost?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type TrackProps = {
|
|
8
|
+
url?: string;
|
|
9
|
+
referrer?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Start first-party pageview collection for a published Reforma site.
|
|
13
|
+
* Reads `NEXT_PUBLIC_REFORMA_ANALYTICS*` env baked on publish. Safe to call
|
|
14
|
+
* multiple times — only the first call installs history hooks.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initAnalytics(options?: AnalyticsOptions): void;
|
|
17
|
+
/** Send a named event. Today only `pageview` is stored by the API. */
|
|
18
|
+
export declare function track(name: string, props?: TrackProps): void;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,KAAK,gBAAgB,GAAG;IACpB,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,KAAK,UAAU,GAAG;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AASF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,IAAI,CAqClE;AAED,sEAAsE;AACtE,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,UAAe,GAAG,IAAI,CA6ChE"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
let started = false;
|
|
2
|
+
let enabled = false;
|
|
3
|
+
let writeKey = '';
|
|
4
|
+
let endpoint = '';
|
|
5
|
+
let lastPath = '';
|
|
6
|
+
let unhook = null;
|
|
7
|
+
/**
|
|
8
|
+
* Start first-party pageview collection for a published Reforma site.
|
|
9
|
+
* Reads `NEXT_PUBLIC_REFORMA_ANALYTICS*` env baked on publish. Safe to call
|
|
10
|
+
* multiple times — only the first call installs history hooks.
|
|
11
|
+
*/
|
|
12
|
+
export function initAnalytics(options = {}) {
|
|
13
|
+
if (typeof window === 'undefined') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (options.reset) {
|
|
17
|
+
unhook?.();
|
|
18
|
+
unhook = null;
|
|
19
|
+
started = false;
|
|
20
|
+
lastPath = '';
|
|
21
|
+
}
|
|
22
|
+
if (started) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
started = true;
|
|
26
|
+
enabled = process.env.NEXT_PUBLIC_REFORMA_ANALYTICS === '1';
|
|
27
|
+
writeKey = process.env.NEXT_PUBLIC_REFORMA_ANALYTICS_KEY?.trim() ?? '';
|
|
28
|
+
endpoint = process.env.NEXT_PUBLIC_REFORMA_ANALYTICS_ENDPOINT?.trim() ?? '';
|
|
29
|
+
if (!enabled || !writeKey || !endpoint) {
|
|
30
|
+
enabled = false;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!options.captureOnLocalhost && isLocalHost(window.location.hostname)) {
|
|
34
|
+
enabled = false;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
track('pageview');
|
|
38
|
+
unhook = installHistoryHook(() => {
|
|
39
|
+
track('pageview');
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Send a named event. Today only `pageview` is stored by the API. */
|
|
43
|
+
export function track(name, props = {}) {
|
|
44
|
+
if (!enabled || name !== 'pageview' || typeof window === 'undefined') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const url = props.url ?? window.location.href;
|
|
48
|
+
const path = pathOf(url);
|
|
49
|
+
if (path === lastPath) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
lastPath = path;
|
|
53
|
+
const payload = JSON.stringify({
|
|
54
|
+
key: writeKey,
|
|
55
|
+
url,
|
|
56
|
+
referrer: props.referrer ?? (document.referrer || undefined),
|
|
57
|
+
});
|
|
58
|
+
const body = typeof Blob === 'undefined'
|
|
59
|
+
? payload
|
|
60
|
+
: new Blob([payload], { type: 'application/json' });
|
|
61
|
+
try {
|
|
62
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
63
|
+
const ok = navigator.sendBeacon(endpoint, body);
|
|
64
|
+
if (ok) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
void fetch(endpoint, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
body: payload,
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
keepalive: true,
|
|
73
|
+
mode: 'cors',
|
|
74
|
+
credentials: 'omit',
|
|
75
|
+
}).catch(() => {
|
|
76
|
+
// Fire-and-forget — never break the host site.
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Ignore beacon failures.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function installHistoryHook(onNavigate) {
|
|
84
|
+
const { pushState, replaceState } = history;
|
|
85
|
+
const notify = () => {
|
|
86
|
+
queueMicrotask(onNavigate);
|
|
87
|
+
};
|
|
88
|
+
history.pushState = function (...args) {
|
|
89
|
+
const result = pushState.apply(this, args);
|
|
90
|
+
notify();
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
history.replaceState = function (...args) {
|
|
94
|
+
const result = replaceState.apply(this, args);
|
|
95
|
+
notify();
|
|
96
|
+
return result;
|
|
97
|
+
};
|
|
98
|
+
window.addEventListener('popstate', notify);
|
|
99
|
+
return () => {
|
|
100
|
+
history.pushState = pushState;
|
|
101
|
+
history.replaceState = replaceState;
|
|
102
|
+
window.removeEventListener('popstate', notify);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function isLocalHost(hostname) {
|
|
106
|
+
return hostname === 'localhost'
|
|
107
|
+
|| hostname === '127.0.0.1'
|
|
108
|
+
|| hostname === '[::1]'
|
|
109
|
+
|| hostname.endsWith('.local');
|
|
110
|
+
}
|
|
111
|
+
function pathOf(url) {
|
|
112
|
+
try {
|
|
113
|
+
return new URL(url, window.location.origin).pathname;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return url;
|
|
117
|
+
}
|
|
118
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/root.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAErG,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAEjE,KAAK,SAAS,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,UAAU,CAAC;CAC1B,CAAC;AAEF,KAAK,gBAAgB,GAAG,SAAS,GAAG;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAAE,CAAC;AAe/E,8EAA8E;AAC9E,wBAAgB,OAAO,qBAEtB;AAMD,wBAAgB,QAAQ,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,WAAgC,EAChC,WAAkB,GACrB,EAAE;IACC,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC7B,+
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAgE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAErG,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C,MAAM,MAAM,YAAY,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAEjE,KAAK,SAAS,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,UAAU,CAAC;CAC1B,CAAC;AAEF,KAAK,gBAAgB,GAAG,SAAS,GAAG;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAAE,CAAC;AAe/E,8EAA8E;AAC9E,wBAAgB,OAAO,qBAEtB;AAMD,wBAAgB,QAAQ,CAAC,EACrB,QAAQ,EACR,KAAK,EACL,WAAgC,EAChC,WAAkB,GACrB,EAAE;IACC,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC7B,+BA6FA"}
|
package/dist/root.js
CHANGED
|
@@ -20,7 +20,7 @@ function modeNames(modes) {
|
|
|
20
20
|
}
|
|
21
21
|
export function SiteRoot({ children, modes, appearances = defaultAppearances, systemModes = null, }) {
|
|
22
22
|
const defaultMode = modes.default ?? '';
|
|
23
|
-
const [
|
|
23
|
+
const [modeState, setModeState] = useState({
|
|
24
24
|
mode: defaultMode,
|
|
25
25
|
siteMode: defaultMode,
|
|
26
26
|
preview: null,
|
|
@@ -29,19 +29,11 @@ export function SiteRoot({ children, modes, appearances = defaultAppearances, sy
|
|
|
29
29
|
});
|
|
30
30
|
const latestState = useRef(null);
|
|
31
31
|
const selectVisitorMode = useRef(null);
|
|
32
|
-
const modesRef = useRef(modes);
|
|
33
|
-
const appearancesRef = useRef(appearances);
|
|
34
|
-
const systemModesRef = useRef(systemModes);
|
|
35
|
-
modesRef.current = modes;
|
|
36
|
-
appearancesRef.current = appearances;
|
|
37
|
-
systemModesRef.current = systemModes;
|
|
38
32
|
// Hydrate the server default first, then apply visitor preferences without masking mismatches.
|
|
39
33
|
useLayoutEffect(() => {
|
|
40
34
|
const storageKey = 'project-mode';
|
|
41
|
-
const names = modeNames(
|
|
42
|
-
const defaultMode =
|
|
43
|
-
const appearances = appearancesRef.current;
|
|
44
|
-
const systemModes = systemModesRef.current;
|
|
35
|
+
const names = modeNames(modes);
|
|
36
|
+
const defaultMode = modes.default ?? '';
|
|
45
37
|
const valid = (mode) => mode !== null && names.includes(mode) ? mode : null;
|
|
46
38
|
const media = systemModes ? window.matchMedia('(prefers-color-scheme: dark)') : null;
|
|
47
39
|
let preference = latestState.current?.preference ?? null;
|
|
@@ -65,7 +57,7 @@ export function SiteRoot({ children, modes, appearances = defaultAppearances, sy
|
|
|
65
57
|
document.documentElement.setAttribute('data-appearance', appearance);
|
|
66
58
|
document.documentElement.style.colorScheme = appearance;
|
|
67
59
|
latestState.current = next;
|
|
68
|
-
|
|
60
|
+
setModeState(next);
|
|
69
61
|
window.dispatchEvent(new CustomEvent('project:mode-change', { detail: next }));
|
|
70
62
|
};
|
|
71
63
|
const onStorage = (event) => {
|
|
@@ -112,6 +104,6 @@ export function SiteRoot({ children, modes, appearances = defaultAppearances, sy
|
|
|
112
104
|
media?.removeEventListener('change', publishMode);
|
|
113
105
|
window.removeEventListener('reforma:token-mode', onPreview);
|
|
114
106
|
};
|
|
115
|
-
}, []);
|
|
116
|
-
return (_jsx(ModeContext, { value: { ...
|
|
107
|
+
}, [appearances, modes, systemModes]);
|
|
108
|
+
return (_jsx(ModeContext, { value: { ...modeState, setMode: mode => selectVisitorMode.current?.(mode) }, children: children }));
|
|
117
109
|
}
|