Files
odoo-expertos.com/js/plausible-events.js
T
Hermes Agent 21b274f91f Track outbound clicks as Plausible custom events
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.
2026-10-24 18:42:02 +00:00

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 });
}
})();