siteplane 0.2.0-next.0 → 0.2.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/bin/siteplane.js +1 -1
- package/dist/{chunk-YDCNEIOB.js → chunk-BLJCWIYD.js} +1 -0
- package/dist/components.d.ts +8 -5
- package/dist/components.js +14 -4
- package/dist/index.js +1 -1
- package/dist/internal/analytics.js +91 -0
- package/dist/route.js +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -49,6 +49,13 @@ A website with `basePath` in `next.config` needs no extra setup. Check it with t
|
|
|
49
49
|
(`npx siteplane status --url https://www.example.com/docs`); Siteplane saves that address as the website URL and
|
|
50
50
|
reaches the website below it.
|
|
51
51
|
|
|
52
|
+
## Analytics
|
|
53
|
+
|
|
54
|
+
With Analytics on for the site, `init` also adds `<SiteplaneAnalytics siteId="…" />` to the root layout. It counts
|
|
55
|
+
page views, App Router navigations and clicks on elements with `data-siteplane-event="<name>"`, on links to other
|
|
56
|
+
websites, phone numbers and email addresses. It sets no cookies and uses no browser storage; Siteplane counts visitors
|
|
57
|
+
per day on its server. Development builds and the editor's preview send nothing.
|
|
58
|
+
|
|
52
59
|
## Booking
|
|
53
60
|
|
|
54
61
|
```ts
|
package/dist/bin/siteplane.js
CHANGED
package/dist/components.d.ts
CHANGED
|
@@ -8,12 +8,15 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
8
8
|
*/
|
|
9
9
|
declare function SiteplaneBridge(): Promise<react_jsx_runtime.JSX.Element | null>;
|
|
10
10
|
/**
|
|
11
|
-
* `<SiteplaneAnalytics siteId="…" />` (Vertrag 54 §10)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `<SiteplaneAnalytics siteId="…" />` (Vertrag 54 §10): page views, App Router
|
|
12
|
+
* navigations and clicks, without cookies or browser storage, sent to
|
|
13
|
+
* Siteplane's collector. In Draft Mode, which only the editor's preview uses,
|
|
14
|
+
* it renders nothing, so previews never count. The client part reads the
|
|
15
|
+
* search parameters and therefore sits in its own Suspense boundary, as with
|
|
16
|
+
* Vercel's `<Analytics />`.
|
|
14
17
|
*/
|
|
15
|
-
declare function SiteplaneAnalytics(
|
|
18
|
+
declare function SiteplaneAnalytics({ siteId }: {
|
|
16
19
|
siteId: string;
|
|
17
|
-
}): null
|
|
20
|
+
}): Promise<react_jsx_runtime.JSX.Element | null>;
|
|
18
21
|
|
|
19
22
|
export { SiteplaneAnalytics, SiteplaneBridge };
|
package/dist/components.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isDraftMode,
|
|
2
3
|
readPreviewToken,
|
|
3
4
|
signingKeys,
|
|
4
5
|
siteConfig,
|
|
5
6
|
verifySitePreviewToken
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BLJCWIYD.js";
|
|
7
8
|
import "./chunk-KZ3AQ6OA.js";
|
|
8
9
|
|
|
10
|
+
// ../shared/dist/site-analytics.js
|
|
11
|
+
var SITE_ANALYTICS_SEND_PATH = "/api/analytics/send";
|
|
12
|
+
function siteAnalyticsEndpoint(apiOrigin) {
|
|
13
|
+
return `${apiOrigin}${SITE_ANALYTICS_SEND_PATH}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
// src/components.tsx
|
|
17
|
+
import { Suspense } from "react";
|
|
18
|
+
import { AnalyticsTracker } from "siteplane/internal/analytics";
|
|
10
19
|
import { BridgeLoader } from "siteplane/internal/bridge-loader";
|
|
11
20
|
import { jsx } from "react/jsx-runtime";
|
|
12
21
|
async function SiteplaneBridge() {
|
|
@@ -23,9 +32,10 @@ async function readEditorOrigin() {
|
|
|
23
32
|
return null;
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
|
-
function SiteplaneAnalytics(
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
async function SiteplaneAnalytics({ siteId }) {
|
|
36
|
+
if (await isDraftMode()) return null;
|
|
37
|
+
const endpoint = siteAnalyticsEndpoint(siteConfig?.apiOrigin ?? "https://siteplane.io");
|
|
38
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(AnalyticsTracker, { siteId, endpoint }) });
|
|
29
39
|
}
|
|
30
40
|
export {
|
|
31
41
|
SiteplaneAnalytics,
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/analytics-tracker.tsx
|
|
4
|
+
import { usePathname, useSearchParams } from "next/navigation";
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
|
|
7
|
+
// ../shared/dist/site-analytics.js
|
|
8
|
+
var SITE_ANALYTICS_EVENT_ATTRIBUTE = "data-siteplane-event";
|
|
9
|
+
var SITE_ANALYTICS_LINK_EVENTS = {
|
|
10
|
+
outbound: "outbound-link",
|
|
11
|
+
tel: "tel-link",
|
|
12
|
+
mailto: "mailto-link"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/analytics.ts
|
|
16
|
+
function createAnalytics(config, deps = {}) {
|
|
17
|
+
const send = deps.send ?? sendEvent;
|
|
18
|
+
let lastUrl = null;
|
|
19
|
+
const page = () => ({
|
|
20
|
+
website: config.siteId,
|
|
21
|
+
hostname: location.hostname,
|
|
22
|
+
url: location.pathname + location.search,
|
|
23
|
+
screen: `${screen.width}x${screen.height}`
|
|
24
|
+
});
|
|
25
|
+
const emit = (payload) => send(config.endpoint, { type: "event", payload });
|
|
26
|
+
const onClick = (event) => {
|
|
27
|
+
if (event.type === "auxclick" && event.button !== 1) return;
|
|
28
|
+
const element = event.target instanceof Element ? event.target : null;
|
|
29
|
+
const name = element?.closest(`[${SITE_ANALYTICS_EVENT_ATTRIBUTE}]`)?.getAttribute(SITE_ANALYTICS_EVENT_ATTRIBUTE)?.trim();
|
|
30
|
+
if (name) return emit({ ...page(), name: name.slice(0, 100) });
|
|
31
|
+
const link = element?.closest("a[href]");
|
|
32
|
+
if (!(link instanceof HTMLAnchorElement)) return;
|
|
33
|
+
const kind = link.protocol === "tel:" ? SITE_ANALYTICS_LINK_EVENTS.tel : link.protocol === "mailto:" ? SITE_ANALYTICS_LINK_EVENTS.mailto : /^https?:$/u.test(link.protocol) && link.host !== location.host ? SITE_ANALYTICS_LINK_EVENTS.outbound : null;
|
|
34
|
+
if (kind) emit({ ...page(), name: kind, data: { url: link.href } });
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
/** Counts the current page once; a later page names the previous one as referrer. */
|
|
38
|
+
pageView() {
|
|
39
|
+
const current = page();
|
|
40
|
+
if (current.url === lastUrl) return;
|
|
41
|
+
const referrer = lastUrl === null ? document.referrer : `${location.origin}${lastUrl}`;
|
|
42
|
+
lastUrl = current.url;
|
|
43
|
+
emit(referrer ? { ...current, referrer } : current);
|
|
44
|
+
},
|
|
45
|
+
/** Listens for clicks (and middle clicks) until the returned function runs. */
|
|
46
|
+
start() {
|
|
47
|
+
document.addEventListener("click", onClick, true);
|
|
48
|
+
document.addEventListener("auxclick", onClick, true);
|
|
49
|
+
return () => {
|
|
50
|
+
document.removeEventListener("click", onClick, true);
|
|
51
|
+
document.removeEventListener("auxclick", onClick, true);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function sendEvent(endpoint, body) {
|
|
57
|
+
try {
|
|
58
|
+
void fetch(endpoint, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
headers: { "content-type": "text/plain" },
|
|
62
|
+
credentials: "omit",
|
|
63
|
+
keepalive: true
|
|
64
|
+
}).catch(() => void 0);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/analytics-tracker.tsx
|
|
70
|
+
function AnalyticsTracker({ siteId, endpoint }) {
|
|
71
|
+
const pathname = usePathname();
|
|
72
|
+
const search = useSearchParams()?.toString() ?? "";
|
|
73
|
+
const analytics = useRef(null);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (process.env.NODE_ENV !== "production") return;
|
|
76
|
+
const instance = createAnalytics({ siteId, endpoint });
|
|
77
|
+
analytics.current = instance;
|
|
78
|
+
const stop = instance.start();
|
|
79
|
+
return () => {
|
|
80
|
+
stop();
|
|
81
|
+
analytics.current = null;
|
|
82
|
+
};
|
|
83
|
+
}, [siteId, endpoint]);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
analytics.current?.pageView();
|
|
86
|
+
}, [pathname, search, siteId, endpoint]);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
AnalyticsTracker
|
|
91
|
+
};
|
package/dist/route.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
siteConfig,
|
|
6
6
|
verifySitePreviewToken,
|
|
7
7
|
verifySiteRevalidation
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BLJCWIYD.js";
|
|
9
9
|
import "./chunk-KZ3AQ6OA.js";
|
|
10
10
|
|
|
11
11
|
// src/route.ts
|
|
@@ -114,7 +114,7 @@ async function readBoundedText(request) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// src/version.ts
|
|
117
|
-
var PACKAGE_VERSION = "0.2.0-next.
|
|
117
|
+
var PACKAGE_VERSION = "0.2.0-next.1";
|
|
118
118
|
|
|
119
119
|
// src/route.ts
|
|
120
120
|
var embeddable = { httpOnly: true, secure: true, sameSite: "none", partitioned: true, path: "/" };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "siteplane",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.2.0-next.
|
|
4
|
+
"version": "0.2.0-next.1",
|
|
5
5
|
"description": "Siteplane for Next.js websites: editable content with code fallbacks, Draft Mode preview, Booking and the siteplane CLI.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"./internal/bridge-loader": "./dist/internal/bridge-loader.js",
|
|
38
38
|
"./internal/bridge": "./dist/internal/bridge.js",
|
|
39
|
+
"./internal/analytics": "./dist/internal/analytics.js",
|
|
39
40
|
"./package.json": "./package.json"
|
|
40
41
|
},
|
|
41
42
|
"types": "./dist/index.d.ts",
|