siteplane 0.2.0-next.0 → 0.2.0-rc.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 +20 -4
- 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
|
@@ -8,18 +8,21 @@ content as fallback in your code, a Draft Mode preview for the editor, Booking a
|
|
|
8
8
|
Run this in the website's folder, or tell your coding agent "Install Siteplane":
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx siteplane@
|
|
11
|
+
npx siteplane@latest init
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Version 0.2 is on the `next` tag until it becomes `latest`.
|
|
15
|
-
|
|
16
14
|
`init` opens the approval page in the browser. There you sign up or sign in, create the website in Siteplane or pick
|
|
17
15
|
an existing one, and click Connect. If you created the website in Siteplane first, its prompt names the site:
|
|
18
16
|
`npx siteplane@<version> init --site <site ID>`.
|
|
19
17
|
|
|
20
18
|
`init` then writes `siteplane.config.json`, installs this package, wraps `next.config` in `withSiteplane`, adds the
|
|
21
19
|
route `app/api/siteplane/[...siteplane]/route.ts` and `<SiteplaneBridge />`, and prints the task for the agent.
|
|
22
|
-
Siteplane needs no environment variables or secrets in your project
|
|
20
|
+
Siteplane needs no environment variables or secrets in your project, and it never deploys: deploy the way the project
|
|
21
|
+
always does.
|
|
22
|
+
|
|
23
|
+
The approval creates an agent connection for this one website. It may read the task, sync fields and check the
|
|
24
|
+
status; it cannot publish. It is listed under Settings → Connected agents in Siteplane, where it can be revoked, and
|
|
25
|
+
expires after 90 days without use.
|
|
23
26
|
|
|
24
27
|
## Field API
|
|
25
28
|
|
|
@@ -49,6 +52,13 @@ A website with `basePath` in `next.config` needs no extra setup. Check it with t
|
|
|
49
52
|
(`npx siteplane status --url https://www.example.com/docs`); Siteplane saves that address as the website URL and
|
|
50
53
|
reaches the website below it.
|
|
51
54
|
|
|
55
|
+
## Analytics
|
|
56
|
+
|
|
57
|
+
With Analytics on for the site, `init` also adds `<SiteplaneAnalytics siteId="…" />` to the root layout. It counts
|
|
58
|
+
page views, App Router navigations and clicks on elements with `data-siteplane-event="<name>"`, on links to other
|
|
59
|
+
websites, phone numbers and email addresses. It sets no cookies and uses no browser storage; Siteplane counts visitors
|
|
60
|
+
per day on its server. Development builds and the editor's preview send nothing.
|
|
61
|
+
|
|
52
62
|
## Booking
|
|
53
63
|
|
|
54
64
|
```ts
|
|
@@ -70,3 +80,9 @@ npx siteplane booking test
|
|
|
70
80
|
approval. The agent connection is stored in the user config directory (`$SITEPLANE_CONFIG_DIR`,
|
|
71
81
|
`$XDG_CONFIG_HOME/siteplane`, `%APPDATA%\siteplane` or `~/.config/siteplane`), never in the project; `--no-browser`
|
|
72
82
|
skips opening the browser.
|
|
83
|
+
|
|
84
|
+
## More
|
|
85
|
+
|
|
86
|
+
- Instructions for coding agents: https://siteplane.io/agents and https://siteplane.io/llms.txt
|
|
87
|
+
- Removing Siteplane: Settings → Export in Siteplane downloads every field with its published value and gives your
|
|
88
|
+
coding agent a task that writes the values into the code and removes this package.
|
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-
|
|
117
|
+
var PACKAGE_VERSION = "0.2.0-rc.0";
|
|
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-
|
|
4
|
+
"version": "0.2.0-rc.0",
|
|
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",
|