js/plausible-events.js classifies every outbound click: Affiliate Click — host in AFFILIATES map, or rel=sponsored Outbound Click — any other external link props: network, destination, destination_path, placement (utm_medium), link_text, lang. Internal links, in-page anchors, mailto: and tel: are ignored; data-plausible-skip opts an element out. Adding a new partner is one line in AFFILIATES. Loaded on 1206 pages before </body>. Create the two goals in Plausible (Settings -> Goals -> Custom event) named exactly 'Affiliate Click' and 'Outbound Click' for them to show as conversions.
138 lines
4.4 KiB
JavaScript
138 lines
4.4 KiB
JavaScript
/*
|
|
* Outbound click tracking for Plausible.
|
|
*
|
|
* Sends two custom events so affiliate clicks can be separated from ordinary
|
|
* outbound links — and so a new partner can be added by editing AFFILIATES only:
|
|
*
|
|
* Affiliate Click props: network, destination, destination_path, placement, link_text, lang
|
|
* Outbound Click same props (network = "none")
|
|
*
|
|
* Goals must exist in Plausible (Settings -> Goals -> Custom event) with exactly
|
|
* these names: "Affiliate Click" and "Outbound Click".
|
|
*
|
|
* Adding a partner: AFFILIATES["host.tld"] = {network: "short-name"}.
|
|
* Links carrying rel="sponsored" are treated as affiliate even when unlisted.
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
// --- configuration -------------------------------------------------------
|
|
var AFFILIATES = {
|
|
"odoo4projects.com": { network: "odoo4projects" }
|
|
// "example-partner.com": { network: "example" },
|
|
};
|
|
|
|
var OWN_HOSTS = ["odoo-expertos.com"]; // apex + any subdomain of it
|
|
|
|
// Scroll-depth events are OFF by default: turn this on only if no other
|
|
// scroll tracking runs on the site, otherwise every scroll is counted twice.
|
|
var SCROLL_DEPTH = false;
|
|
var SCROLL_THRESHOLDS = [25, 50, 75, 100];
|
|
|
|
// --- helpers -------------------------------------------------------------
|
|
function trim(value, max) {
|
|
if (value === null || value === undefined) return "";
|
|
return String(value)
|
|
.replace(/[\r\n\t]+/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim()
|
|
.slice(0, max || 120);
|
|
}
|
|
|
|
function hostOf(url) {
|
|
try {
|
|
return url.hostname.toLowerCase().replace(/^www\./, "");
|
|
} catch (e) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function isOwnHost(host) {
|
|
for (var i = 0; i < OWN_HOSTS.length; i++) {
|
|
var own = OWN_HOSTS[i];
|
|
if (host === own || host.endsWith("." + own)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function paramName(url, name) {
|
|
try {
|
|
return url.searchParams.get(name) || "";
|
|
} catch (e) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function send(eventName, props) {
|
|
if (typeof window.plausible !== "function") return;
|
|
var clean = {};
|
|
for (var k in props) {
|
|
if (Object.prototype.hasOwnProperty.call(props, k) && props[k]) clean[k] = props[k];
|
|
}
|
|
window.plausible(eventName, { props: clean });
|
|
}
|
|
|
|
// --- click handler -------------------------------------------------------
|
|
function onClick(evt) {
|
|
if (evt.type === "auxclick" && evt.button !== 1) return;
|
|
|
|
var node = evt.target;
|
|
while (node && node.nodeName !== "A") node = node.parentNode;
|
|
if (!node || !node.href) return;
|
|
|
|
// explicit opt-out
|
|
if (node.hasAttribute("data-plausible-skip")) return;
|
|
|
|
var url;
|
|
try {
|
|
url = new URL(node.href);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") return; // mailto:, tel:, javascript:
|
|
|
|
var host = hostOf(url);
|
|
if (!host || isOwnHost(host)) return; // internal navigation
|
|
try {
|
|
if (url.origin === window.location.origin) return; // same origin, incl. in-page anchors
|
|
} catch (e) {}
|
|
|
|
var rel = (node.getAttribute("rel") || "").toLowerCase();
|
|
var affiliate = AFFILIATES[host];
|
|
var isAffiliate = !!affiliate || rel.indexOf("sponsored") !== -1;
|
|
|
|
var props = {
|
|
network: affiliate ? affiliate.network : isAffiliate ? host : "none",
|
|
destination: host,
|
|
destination_path: url.pathname || "/",
|
|
placement: paramName(url, "utm_medium") || "unknown",
|
|
link_text: trim(node.textContent, 60),
|
|
lang: trim((document.documentElement.lang || "").slice(0, 5), 5)
|
|
};
|
|
|
|
send(isAffiliate ? "Affiliate Click" : "Outbound Click", props);
|
|
}
|
|
|
|
document.addEventListener("click", onClick, true);
|
|
document.addEventListener("auxclick", onClick, true);
|
|
|
|
// --- optional scroll depth ----------------------------------------------
|
|
if (SCROLL_DEPTH) {
|
|
var fired = {};
|
|
var onScroll = function () {
|
|
var doc = document.documentElement;
|
|
var total = doc.scrollHeight - window.innerHeight;
|
|
if (total <= 0) return;
|
|
var pct = Math.round((window.scrollY / total) * 100);
|
|
for (var i = 0; i < SCROLL_THRESHOLDS.length; i++) {
|
|
var step = SCROLL_THRESHOLDS[i];
|
|
if (pct >= step && !fired[step]) {
|
|
fired[step] = true;
|
|
send("Scroll Depth", { depth: String(step) });
|
|
}
|
|
}
|
|
};
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
}
|
|
})();
|