Skip to content

Commit 31bdbe4

Browse files
authored
Website: Set up our GoatCounter instance (#1343)
Signed-off-by: Marek Kaput <[email protected]>
1 parent e314b1d commit 31bdbe4

File tree

2 files changed

+331
-0
lines changed

2 files changed

+331
-0
lines changed

website/.vitepress/config.mjs

+8
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ export default withMermaid(
145145
["meta", { property: "og:image:type", content: "image/png" }],
146146
["meta", { property: "og:image:width", content: "1280" }],
147147
["meta", { property: "og:image:height", content: "640" }],
148+
[
149+
"script",
150+
{
151+
"data-goatcounter": "https://gc-scarb.swmtest.xyz/count",
152+
async: true,
153+
src: `${base}count.js`,
154+
},
155+
],
148156
],
149157

150158
lastUpdated: true,

website/public/count.js

+323
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
// GoatCounter: https://www.goatcounter.com
2+
// This file is released under the ISC license: https://opensource.org/licenses/ISC
3+
(function () {
4+
"use strict";
5+
6+
if (window.goatcounter && window.goatcounter.vars)
7+
// Compatibility with very old version; do not use.
8+
window.goatcounter = window.goatcounter.vars;
9+
else window.goatcounter = window.goatcounter || {};
10+
11+
// Load settings from data-goatcounter-settings.
12+
var s = document.querySelector("script[data-goatcounter]");
13+
if (s && s.dataset.goatcounterSettings) {
14+
try {
15+
var set = JSON.parse(s.dataset.goatcounterSettings);
16+
} catch (err) {
17+
console.error("invalid JSON in data-goatcounter-settings: " + err);
18+
}
19+
for (var k in set)
20+
if (
21+
[
22+
"no_onload",
23+
"no_events",
24+
"allow_local",
25+
"allow_frame",
26+
"path",
27+
"title",
28+
"referrer",
29+
"event",
30+
].indexOf(k) > -1
31+
)
32+
window.goatcounter[k] = set[k];
33+
}
34+
35+
var enc = encodeURIComponent;
36+
37+
// Get all data we're going to send off to the counter endpoint.
38+
var get_data = function (vars) {
39+
var data = {
40+
p: vars.path === undefined ? goatcounter.path : vars.path,
41+
r: vars.referrer === undefined ? goatcounter.referrer : vars.referrer,
42+
t: vars.title === undefined ? goatcounter.title : vars.title,
43+
e: !!(vars.event || goatcounter.event),
44+
s: [
45+
window.screen.width,
46+
window.screen.height,
47+
window.devicePixelRatio || 1,
48+
],
49+
b: is_bot(),
50+
q: location.search,
51+
};
52+
53+
var rcb, pcb, tcb; // Save callbacks to apply later.
54+
if (typeof data.r === "function") rcb = data.r;
55+
if (typeof data.t === "function") tcb = data.t;
56+
if (typeof data.p === "function") pcb = data.p;
57+
58+
if (is_empty(data.r)) data.r = document.referrer;
59+
if (is_empty(data.t)) data.t = document.title;
60+
if (is_empty(data.p)) data.p = get_path();
61+
62+
if (rcb) data.r = rcb(data.r);
63+
if (tcb) data.t = tcb(data.t);
64+
if (pcb) data.p = pcb(data.p);
65+
return data;
66+
};
67+
68+
// Check if a value is "empty" for the purpose of get_data().
69+
var is_empty = function (v) {
70+
return v === null || v === undefined || typeof v === "function";
71+
};
72+
73+
// See if this looks like a bot; there is some additional filtering on the
74+
// backend, but these properties can't be fetched from there.
75+
var is_bot = function () {
76+
// Headless browsers are probably a bot.
77+
var w = window,
78+
d = document;
79+
if (w.callPhantom || w._phantom || w.phantom) return 150;
80+
if (w.__nightmare) return 151;
81+
if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate)
82+
return 152;
83+
if (navigator.webdriver) return 153;
84+
return 0;
85+
};
86+
87+
// Object to urlencoded string, starting with a ?.
88+
var urlencode = function (obj) {
89+
var p = [];
90+
for (var k in obj)
91+
if (
92+
obj[k] !== "" &&
93+
obj[k] !== null &&
94+
obj[k] !== undefined &&
95+
obj[k] !== false
96+
)
97+
p.push(enc(k) + "=" + enc(obj[k]));
98+
return "?" + p.join("&");
99+
};
100+
101+
// Show a warning in the console.
102+
var warn = function (msg) {
103+
if (console && "warn" in console) console.warn("goatcounter: " + msg);
104+
};
105+
106+
// Get the endpoint to send requests to.
107+
var get_endpoint = function () {
108+
var s = document.querySelector("script[data-goatcounter]");
109+
if (s && s.dataset.goatcounter) return s.dataset.goatcounter;
110+
return goatcounter.endpoint || window.counter; // counter is for compat; don't use.
111+
};
112+
113+
// Get current path.
114+
var get_path = function () {
115+
var loc = location,
116+
c = document.querySelector('link[rel="canonical"][href]');
117+
if (c) {
118+
// May be relative or point to different domain.
119+
var a = document.createElement("a");
120+
a.href = c.href;
121+
if (
122+
a.hostname.replace(/^www\./, "") ===
123+
location.hostname.replace(/^www\./, "")
124+
)
125+
loc = a;
126+
}
127+
return loc.pathname + loc.search || "/";
128+
};
129+
130+
// Run function after DOM is loaded.
131+
var on_load = function (f) {
132+
if (document.body === null)
133+
document.addEventListener(
134+
"DOMContentLoaded",
135+
function () {
136+
f();
137+
},
138+
false,
139+
);
140+
else f();
141+
};
142+
143+
// Filter some requests that we (probably) don't want to count.
144+
goatcounter.filter = function () {
145+
if (
146+
"visibilityState" in document &&
147+
document.visibilityState === "prerender"
148+
)
149+
return "visibilityState";
150+
if (!goatcounter.allow_frame && location !== parent.location)
151+
return "frame";
152+
if (
153+
!goatcounter.allow_local &&
154+
location.hostname.match(
155+
/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/,
156+
)
157+
)
158+
return "localhost";
159+
if (!goatcounter.allow_local && location.protocol === "file:")
160+
return "localfile";
161+
if (localStorage && localStorage.getItem("skipgc") === "t")
162+
return "disabled with #toggle-goatcounter";
163+
return false;
164+
};
165+
166+
// Get URL to send to GoatCounter.
167+
window.goatcounter.url = function (vars) {
168+
var data = get_data(vars || {});
169+
if (data.p === null)
170+
// null from user callback.
171+
return;
172+
data.rnd = Math.random().toString(36).substr(2, 5); // Browsers don't always listen to Cache-Control.
173+
174+
var endpoint = get_endpoint();
175+
if (!endpoint) return warn("no endpoint found");
176+
177+
return endpoint + urlencode(data);
178+
};
179+
180+
// Count a hit.
181+
window.goatcounter.count = function (vars) {
182+
var f = goatcounter.filter();
183+
if (f) return warn("not counting because of: " + f);
184+
var url = goatcounter.url(vars);
185+
if (!url) return warn("not counting because path callback returned null");
186+
187+
if (!navigator.sendBeacon(url)) {
188+
// This mostly fails due to being blocked by CSP; try again with an
189+
// image-based fallback.
190+
var img = document.createElement("img");
191+
img.src = url;
192+
img.style.position = "absolute"; // Affect layout less.
193+
img.style.bottom = "0px";
194+
img.style.width = "1px";
195+
img.style.height = "1px";
196+
img.loading = "eager";
197+
img.setAttribute("alt", "");
198+
img.setAttribute("aria-hidden", "true");
199+
200+
var rm = function () {
201+
if (img && img.parentNode) img.parentNode.removeChild(img);
202+
};
203+
img.addEventListener("load", rm, false);
204+
document.body.appendChild(img);
205+
}
206+
};
207+
208+
// Get a query parameter.
209+
window.goatcounter.get_query = function (name) {
210+
var s = location.search.substr(1).split("&");
211+
for (var i = 0; i < s.length; i++)
212+
if (s[i].toLowerCase().indexOf(name.toLowerCase() + "=") === 0)
213+
return s[i].substr(name.length + 1);
214+
};
215+
216+
// Track click events.
217+
window.goatcounter.bind_events = function () {
218+
if (!document.querySelectorAll)
219+
// Just in case someone uses an ancient browser.
220+
return;
221+
222+
var send = function (elem) {
223+
return function () {
224+
goatcounter.count({
225+
event: true,
226+
path: elem.dataset.goatcounterClick || elem.name || elem.id || "",
227+
title:
228+
elem.dataset.goatcounterTitle ||
229+
elem.title ||
230+
(elem.innerHTML || "").substr(0, 200) ||
231+
"",
232+
referrer:
233+
elem.dataset.goatcounterReferrer ||
234+
elem.dataset.goatcounterReferral ||
235+
"",
236+
});
237+
};
238+
};
239+
240+
Array.prototype.slice
241+
.call(document.querySelectorAll("*[data-goatcounter-click]"))
242+
.forEach(function (elem) {
243+
if (elem.dataset.goatcounterBound) return;
244+
var f = send(elem);
245+
elem.addEventListener("click", f, false);
246+
elem.addEventListener("auxclick", f, false); // Middle click.
247+
elem.dataset.goatcounterBound = "true";
248+
});
249+
};
250+
251+
// Add a "visitor counter" frame or image.
252+
window.goatcounter.visit_count = function (opt) {
253+
on_load(function () {
254+
opt = opt || {};
255+
opt.type = opt.type || "html";
256+
opt.append = opt.append || "body";
257+
opt.path = opt.path || get_path();
258+
opt.attr = opt.attr || {
259+
width: "200",
260+
height: opt.no_branding ? "60" : "80",
261+
};
262+
263+
opt.attr["src"] =
264+
get_endpoint() + "er/" + enc(opt.path) + "." + enc(opt.type) + "?";
265+
if (opt.no_branding) opt.attr["src"] += "&no_branding=1";
266+
if (opt.style) opt.attr["src"] += "&style=" + enc(opt.style);
267+
if (opt.start) opt.attr["src"] += "&start=" + enc(opt.start);
268+
if (opt.end) opt.attr["src"] += "&end=" + enc(opt.end);
269+
270+
var tag = { png: "img", svg: "img", html: "iframe" }[opt.type];
271+
if (!tag) return warn("visit_count: unknown type: " + opt.type);
272+
273+
if (opt.type === "html") {
274+
opt.attr["frameborder"] = "0";
275+
opt.attr["scrolling"] = "no";
276+
}
277+
278+
var d = document.createElement(tag);
279+
for (var k in opt.attr) d.setAttribute(k, opt.attr[k]);
280+
281+
var p = document.querySelector(opt.append);
282+
if (!p) return warn("visit_count: append not found: " + opt.append);
283+
p.appendChild(d);
284+
});
285+
};
286+
287+
// Make it easy to skip your own views.
288+
if (location.hash === "#toggle-goatcounter") {
289+
if (localStorage.getItem("skipgc") === "t") {
290+
localStorage.removeItem("skipgc", "t");
291+
alert("GoatCounter tracking is now ENABLED in this browser.");
292+
} else {
293+
localStorage.setItem("skipgc", "t");
294+
alert(
295+
"GoatCounter tracking is now DISABLED in this browser until " +
296+
location +
297+
" is loaded again.",
298+
);
299+
}
300+
}
301+
302+
if (!goatcounter.no_onload)
303+
on_load(function () {
304+
// 1. Page is visible, count request.
305+
// 2. Page is not yet visible; wait until it switches to 'visible' and count.
306+
// See #487
307+
if (
308+
!("visibilityState" in document) ||
309+
document.visibilityState === "visible"
310+
)
311+
goatcounter.count();
312+
else {
313+
var f = function (e) {
314+
if (document.visibilityState !== "visible") return;
315+
document.removeEventListener("visibilitychange", f);
316+
goatcounter.count();
317+
};
318+
document.addEventListener("visibilitychange", f);
319+
}
320+
321+
if (!goatcounter.no_events) goatcounter.bind_events();
322+
});
323+
})();

0 commit comments

Comments
 (0)