747 lines
22 KiB
JavaScript
747 lines
22 KiB
JavaScript
// log htmx on dev
|
|
// htmx.logAll();
|
|
|
|
// add text/html accept header to receive html instead of json for the requests
|
|
document.body.addEventListener('htmx:configRequest', function(evt) {
|
|
evt.detail.headers["Accept"] = "text/html,*/*";
|
|
});
|
|
|
|
// redirect to homepage
|
|
document.body.addEventListener("redirectToHomepage", function() {
|
|
setTimeout(() => {
|
|
window.location.replace("/");
|
|
}, 1500);
|
|
});
|
|
|
|
// reset form if event is sent from the backend
|
|
function resetForm(id) {
|
|
return function() {
|
|
const form = document.getElementById(id);
|
|
if (!form) return;
|
|
form.reset();
|
|
}
|
|
}
|
|
document.body.addEventListener('resetChangePasswordForm', resetForm("change-password"));
|
|
document.body.addEventListener('resetChangeEmailForm', resetForm("change-email"));
|
|
|
|
// an htmx extension to use the specifed params in the path instead of the query or body
|
|
htmx.defineExtension("path-params", {
|
|
onEvent: function(name, evt) {
|
|
if (name === "htmx:configRequest") {
|
|
evt.detail.path = evt.detail.path.replace(/{([^}]+)}/g, function(_, param) {
|
|
var val = evt.detail.parameters[param]
|
|
delete evt.detail.parameters[param]
|
|
return val === undefined ? '{' + param + '}' : encodeURIComponent(val)
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
// find closest element
|
|
function closest(selector, elm) {
|
|
let element = elm || this;
|
|
|
|
while (element && element.nodeType === 1) {
|
|
if (element.matches(selector)) {
|
|
return element;
|
|
}
|
|
|
|
element = element.parentNode;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
// get url query param
|
|
function getQueryParams() {
|
|
const search = window.location.search.replace("?", "");
|
|
const query = {};
|
|
search.split("&").map(q => {
|
|
const keyvalue = q.split("=");
|
|
query[keyvalue[0]] = keyvalue[1];
|
|
});
|
|
return query;
|
|
}
|
|
|
|
// trim text
|
|
function trimText(selector, length) {
|
|
const element = document.querySelector(selector);
|
|
if (!element) return;
|
|
let text = element.textContent;
|
|
if (typeof text !== "string") return;
|
|
text = text.trim();
|
|
if (text.length > length) {
|
|
element.textContent = text.split("").slice(0, length).join("") + "...";
|
|
}
|
|
}
|
|
|
|
function formatDateHour(selector) {
|
|
const element = document.querySelector(selector);
|
|
if (!element) return;
|
|
const dateString = element.dataset.date;
|
|
if (!dateString) return;
|
|
const date = new Date(dateString);
|
|
element.textContent = date.getHours() + ":" + date.getMinutes();
|
|
}
|
|
|
|
// show QR code
|
|
function handleQRCode(element) {
|
|
const dialog = document.querySelector("#link-dialog");
|
|
const dialogContent = dialog.querySelector(".content-wrapper");
|
|
if (!dialogContent) return;
|
|
openDialog("link-dialog", "qrcode");
|
|
dialogContent.textContent = "";
|
|
const qrcode = new QRCode(dialogContent, {
|
|
text: element.dataset.url,
|
|
width: 200,
|
|
height: 200,
|
|
colorDark : "#000000",
|
|
colorLight : "#ffffff",
|
|
correctLevel : QRCode.CorrectLevel.H
|
|
});
|
|
}
|
|
|
|
// copy the link to clipboard
|
|
function handleCopyLink(element) {
|
|
navigator.clipboard.writeText(element.dataset.url);
|
|
}
|
|
|
|
// copy the link and toggle copy button style
|
|
function handleShortURLCopyLink(element) {
|
|
handleCopyLink(element);
|
|
const clipboard = element.parentNode.querySelector(".clipboard") || closest(".clipboard", element);
|
|
if (!clipboard || clipboard.classList.contains("copied")) return;
|
|
clipboard.classList.add("copied");
|
|
setTimeout(function() {
|
|
clipboard.classList.remove("copied");
|
|
}, 1000);
|
|
}
|
|
|
|
// open and close dialog
|
|
function openDialog(id, name) {
|
|
const dialog = document.getElementById(id);
|
|
if (!dialog) return;
|
|
dialog.classList.add("open");
|
|
if (name) {
|
|
dialog.classList.add(name);
|
|
}
|
|
}
|
|
|
|
function closeDialog() {
|
|
const dialog = document.querySelector(".dialog");
|
|
if (!dialog) return;
|
|
while (dialog.classList.length > 0) {
|
|
dialog.classList.remove(dialog.classList[0]);
|
|
}
|
|
dialog.classList.add("dialog");
|
|
}
|
|
|
|
window.addEventListener("click", function(event) {
|
|
const dialog = document.querySelector(".dialog");
|
|
if (dialog && event.target === dialog) {
|
|
closeDialog();
|
|
}
|
|
});
|
|
|
|
// handle navigation in the table of links
|
|
function setLinksLimit(event) {
|
|
const buttons = Array.from(document.querySelectorAll('table .nav .limit button'));
|
|
const limitInput = document.querySelector('#limit');
|
|
if (!limitInput || !buttons || !buttons.length) return;
|
|
limitInput.value = event.target.textContent;
|
|
buttons.forEach(b => {
|
|
b.disabled = b.textContent === event.target.textContent;
|
|
});
|
|
}
|
|
|
|
function setLinksSkip(event, action) {
|
|
const buttons = Array.from(document.querySelectorAll('table .nav .pagination button'));
|
|
const limitElm = document.querySelector('#limit');
|
|
const totalElm = document.querySelector('#total');
|
|
const skipElm = document.querySelector('#skip');
|
|
if (!buttons || !limitElm || !totalElm || !skipElm) return;
|
|
const skip = parseInt(skipElm.value);
|
|
const limit = parseInt(limitElm.value);
|
|
const total = parseInt(totalElm.value);
|
|
skipElm.value = action === "next" ? skip + limit : Math.max(skip - limit, 0);
|
|
document.querySelectorAll('.pagination .next').forEach(elm => {
|
|
elm.disabled = total <= parseInt(skipElm.value) + limit;
|
|
});
|
|
document.querySelectorAll('.pagination .prev').forEach(elm => {
|
|
elm.disabled = parseInt(skipElm.value) <= 0;
|
|
});
|
|
}
|
|
|
|
function updateLinksNav() {
|
|
const totalElm = document.querySelector('#total');
|
|
const skipElm = document.querySelector('#skip');
|
|
const limitElm = document.querySelector('#limit');
|
|
if (!totalElm || !skipElm || !limitElm) return;
|
|
const total = parseInt(totalElm.value);
|
|
const skip = parseInt(skipElm.value);
|
|
const limit = parseInt(limitElm.value);
|
|
document.querySelectorAll('.pagination .next').forEach(elm => {
|
|
elm.disabled = total <= skip + limit;
|
|
});
|
|
document.querySelectorAll('.pagination .prev').forEach(elm => {
|
|
elm.disabled = skip <= 0;
|
|
});
|
|
}
|
|
|
|
function resetLinkNav() {
|
|
const totalElm = document.querySelector('#total');
|
|
const skipElm = document.querySelector('#skip');
|
|
const limitElm = document.querySelector('#limit');
|
|
if (!totalElm || !skipElm || !limitElm) return;
|
|
skipElm.value = 0;
|
|
limitElm.value = 10;
|
|
const skip = parseInt(skipElm.value);
|
|
const limit = parseInt(limitElm.value);
|
|
document.querySelectorAll('.pagination .next').forEach(elm => {
|
|
elm.disabled = total <= skip + limit;
|
|
});
|
|
document.querySelectorAll('.pagination .prev').forEach(elm => {
|
|
elm.disabled = skip <= 0;
|
|
});
|
|
document.querySelectorAll('table .nav .limit button').forEach(b => {
|
|
b.disabled = b.textContent === limit.toString();
|
|
});
|
|
}
|
|
|
|
// create views chart label
|
|
function createViewsChartLabel(ctx) {
|
|
const period = ctx.dataset.period;
|
|
let labels = [];
|
|
|
|
if (period === "day") {
|
|
const nowHour = new Date().getHours();
|
|
for (let i = 23; i >= 0; --i) {
|
|
let h = nowHour - i;
|
|
if (h < 0) h = 24 + h;
|
|
labels.push(`${Math.floor(h)}:00`);
|
|
}
|
|
}
|
|
|
|
if (period === "week") {
|
|
const nowDay = new Date().getDate();
|
|
for (let i = 6; i >= 0; --i) {
|
|
const date = new Date(new Date().setDate(nowDay - i));
|
|
labels.push(`${date.getDate()} ${date.toLocaleString("default",{month:"short"})}`);
|
|
}
|
|
}
|
|
|
|
if (period === "month") {
|
|
const nowDay = new Date().getDate();
|
|
for (let i = 29; i >= 0; --i) {
|
|
const date = new Date(new Date().setDate(nowDay - i));
|
|
labels.push(`${date.getDate()} ${date.toLocaleString("default",{month:"short"})}`);
|
|
}
|
|
}
|
|
|
|
if (period === "year") {
|
|
const nowMonth = new Date().getMonth();
|
|
for (let i = 11; i >= 0; --i) {
|
|
const date = new Date(new Date().setMonth(nowMonth - i));
|
|
labels.push(`${date.toLocaleString("default",{month:"short"})} ${date.toLocaleString("default",{year:"numeric"})}`);
|
|
}
|
|
}
|
|
|
|
return labels;
|
|
}
|
|
|
|
// create views chart
|
|
function createViewsChart() {
|
|
const canvases = document.querySelectorAll("canvas.visits");
|
|
if (!canvases || !canvases.length) return;
|
|
|
|
canvases.forEach(ctx => {
|
|
const data = JSON.parse(ctx.dataset.data);
|
|
const period = ctx.dataset.period;
|
|
|
|
const labels = createViewsChartLabel(ctx);
|
|
const maxTicksLimitX = period === "year" ? 6 : period === "month" ? 15 : 12;
|
|
|
|
const gradient = ctx.getContext("2d").createLinearGradient(0, 0, 0, 300);
|
|
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
|
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
|
|
|
new Chart(ctx, {
|
|
type: "line",
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
label: "Views",
|
|
data,
|
|
tension: 0.3,
|
|
|
|
elements: {
|
|
point: {
|
|
pointRadius: 0,
|
|
pointHoverRadius: 4
|
|
}
|
|
},
|
|
fill: {
|
|
target: "start",
|
|
},
|
|
backgroundColor: gradient,
|
|
borderColor: "rgb(179, 157, 219)",
|
|
borderWidth: 1,
|
|
}]
|
|
},
|
|
options: {
|
|
plugins: {
|
|
legend: {
|
|
display: false,
|
|
},
|
|
tooltip: {
|
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
titleColor: "#333",
|
|
titleFont: { weight: "normal", size: 15 },
|
|
bodyFont: { weight: "normal", size: 16 },
|
|
bodyColor: "rgb(179, 157, 219)",
|
|
padding: 12,
|
|
cornerRadius: 2,
|
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
borderWidth: 1,
|
|
displayColors: false,
|
|
}
|
|
},
|
|
responsive: true,
|
|
interaction: {
|
|
intersect: false,
|
|
usePointStyle: true,
|
|
mode: "index",
|
|
},
|
|
scales: {
|
|
y: {
|
|
grace: "10%",
|
|
beginAtZero: true,
|
|
ticks: {
|
|
maxTicksLimit: 5
|
|
}
|
|
},
|
|
x: {
|
|
ticks: {
|
|
maxTicksLimit: maxTicksLimitX,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// reset the display: block style that chart.js applies automatically
|
|
ctx.style.display = "";
|
|
});
|
|
}
|
|
|
|
// beautify browser lables
|
|
function beautifyBrowserName(name) {
|
|
if (name === "firefox") return "Firefox";
|
|
if (name === "chrome") return "Chrome";
|
|
if (name === "edge") return "Edge";
|
|
if (name === "opera") return "Opera";
|
|
if (name === "safari") return "Safari";
|
|
if (name === "other") return "Other";
|
|
if (name === "ie") return "IE";
|
|
return name;
|
|
}
|
|
|
|
// create browsers chart
|
|
function createBrowsersChart() {
|
|
const canvases = document.querySelectorAll("canvas.browsers");
|
|
if (!canvases || !canvases.length) return;
|
|
|
|
canvases.forEach(ctx => {
|
|
const data = JSON.parse(ctx.dataset.data);
|
|
const period = ctx.dataset.period;
|
|
|
|
const gradient = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
const gradientHover = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
|
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
|
gradientHover.addColorStop(0, "rgba(179, 157, 219, 0.9)");
|
|
gradientHover.addColorStop(1, "rgba(179, 157, 219, 0.4)");
|
|
|
|
new Chart(ctx, {
|
|
type: "bar",
|
|
data: {
|
|
labels: data.map(d => beautifyBrowserName(d.name)),
|
|
datasets: [{
|
|
label: "Views",
|
|
data: data.map(d => d.value),
|
|
backgroundColor: gradient,
|
|
borderColor: "rgba(179, 157, 219, 1)",
|
|
borderWidth: 1,
|
|
hoverBackgroundColor: gradientHover,
|
|
hoverBorderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: "y",
|
|
plugins: {
|
|
legend: {
|
|
display: false,
|
|
},
|
|
tooltip: {
|
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
titleColor: "#333",
|
|
titleFont: { weight: "normal", size: 15 },
|
|
bodyFont: { weight: "normal", size: 16 },
|
|
bodyColor: "rgb(179, 157, 219)",
|
|
padding: 12,
|
|
cornerRadius: 2,
|
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
borderWidth: 1,
|
|
displayColors: false,
|
|
}
|
|
},
|
|
responsive: true,
|
|
interaction: {
|
|
intersect: false,
|
|
mode: "index",
|
|
axis: "y"
|
|
},
|
|
scales: {
|
|
x: {
|
|
grace: "5%",
|
|
beginAtZero: true,
|
|
ticks: {
|
|
maxTicksLimit: 6,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// reset the display: block style that chart.js applies automatically
|
|
ctx.style.display = "";
|
|
});
|
|
}
|
|
|
|
// create referrers chart
|
|
function createReferrersChart() {
|
|
const canvases = document.querySelectorAll("canvas.referrers");
|
|
if (!canvases || !canvases.length) return;
|
|
|
|
canvases.forEach(ctx => {
|
|
const data = JSON.parse(ctx.dataset.data);
|
|
const period = ctx.dataset.period;
|
|
let max = Array.from(data).sort((a, b) => a.value > b.value ? -1 : 1)[0];
|
|
|
|
let tooltipEnabled = true;
|
|
let hoverBackgroundColor = "rgba(179, 157, 219, 1)";
|
|
let hoverBorderWidth = 2;
|
|
let borderColor = "rgba(179, 157, 219, 1)";
|
|
if (data.length === 0) {
|
|
data.push({ name: "No views.", value: 1 });
|
|
max = { value: 1000 };
|
|
tooltipEnabled = false;
|
|
hoverBackgroundColor = "rgba(179, 157, 219, 0.1)";
|
|
hoverBorderWidth = 1;
|
|
borderColor = "rgba(179, 157, 219, 0.2)";
|
|
}
|
|
|
|
new Chart(ctx, {
|
|
type: "doughnut",
|
|
data: {
|
|
labels: data.map(d => d.name.replace(/\[dot\]/g, ".")),
|
|
datasets: [{
|
|
label: "Views",
|
|
data: data.map(d => d.value),
|
|
backgroundColor: data.map(d => `rgba(179, 157, 219, ${Math.max((d.value / max.value) - 0.2, 0.1).toFixed(2)})`),
|
|
borderWidth: 1,
|
|
borderColor,
|
|
hoverBackgroundColor,
|
|
hoverBorderWidth,
|
|
}]
|
|
},
|
|
options: {
|
|
plugins: {
|
|
legend: {
|
|
position: "left",
|
|
labels: {
|
|
boxWidth: 25,
|
|
font: { size: 11 }
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: tooltipEnabled,
|
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
titleColor: "#333",
|
|
titleFont: { weight: "normal", size: 15 },
|
|
bodyFont: { weight: "normal", size: 16 },
|
|
bodyColor: "rgb(179, 157, 219)",
|
|
padding: 12,
|
|
cornerRadius: 2,
|
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
borderWidth: 1,
|
|
displayColors: false,
|
|
}
|
|
},
|
|
responsive: false,
|
|
}
|
|
});
|
|
|
|
// reset the display: block style that chart.js applies automatically
|
|
ctx.style.display = "";
|
|
});
|
|
}
|
|
|
|
|
|
// beautify browser lables
|
|
function beautifyOsName(name) {
|
|
if (name === "android") return "Android";
|
|
if (name === "ios") return "iOS";
|
|
if (name === "linux") return "Linux";
|
|
if (name === "macos") return "macOS";
|
|
if (name === "windows") return "Windows";
|
|
if (name === "other") return "Other";
|
|
return name;
|
|
}
|
|
|
|
|
|
// create operation systems chart
|
|
function createOsChart() {
|
|
const canvases = document.querySelectorAll("canvas.os");
|
|
if (!canvases || !canvases.length) return;
|
|
|
|
canvases.forEach(ctx => {
|
|
const data = JSON.parse(ctx.dataset.data);
|
|
const period = ctx.dataset.period;
|
|
|
|
const gradient = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
const gradientHover = ctx.getContext("2d").createLinearGradient(500, 0, 0, 0);
|
|
gradient.addColorStop(0, "rgba(179, 157, 219, 0.95)");
|
|
gradient.addColorStop(1, "rgba(179, 157, 219, 0.05)");
|
|
gradientHover.addColorStop(0, "rgba(179, 157, 219, 0.9)");
|
|
gradientHover.addColorStop(1, "rgba(179, 157, 219, 0.4)");
|
|
|
|
new Chart(ctx, {
|
|
type: "bar",
|
|
data: {
|
|
labels: data.map(d => beautifyOsName(d.name)),
|
|
datasets: [{
|
|
label: "Views",
|
|
data: data.map(d => d.value),
|
|
backgroundColor: gradient,
|
|
borderColor: "rgba(179, 157, 219, 1)",
|
|
borderWidth: 1,
|
|
hoverBackgroundColor: gradientHover,
|
|
hoverBorderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: "y",
|
|
plugins: {
|
|
legend: {
|
|
display: false,
|
|
},
|
|
tooltip: {
|
|
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
|
titleColor: "#333",
|
|
titleFont: { weight: "normal", size: 15 },
|
|
bodyFont: { weight: "normal", size: 16 },
|
|
bodyColor: "rgb(179, 157, 219)",
|
|
padding: 12,
|
|
cornerRadius: 2,
|
|
borderColor: "rgba(0, 0, 0, 0.1)",
|
|
borderWidth: 1,
|
|
displayColors: false,
|
|
}
|
|
},
|
|
responsive: true,
|
|
interaction: {
|
|
intersect: false,
|
|
mode: "index",
|
|
axis: "y"
|
|
},
|
|
scales: {
|
|
x: {
|
|
grace:"5%",
|
|
beginAtZero: true,
|
|
ticks: {
|
|
maxTicksLimit: 6,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// reset the display: block style that chart.js applies automatically
|
|
ctx.style.display = "";
|
|
});
|
|
}
|
|
|
|
// add data to the map
|
|
function feedMapData(period) {
|
|
const map = document.querySelector("svg.map");
|
|
const paths = map.querySelectorAll("path");
|
|
if (!map || !paths || !paths.length) return;
|
|
|
|
let data = JSON.parse(map.dataset[period || "day"]);
|
|
if (!data) return;
|
|
|
|
let max = data.sort((a, b) => a.value > b.value ? -1 : 1)[0];
|
|
|
|
if (!max) max = { value: 1 }
|
|
|
|
data = data.reduce((a, c) => ({ ...a, [c.name]: c.value }), {});
|
|
|
|
for (let i = 0; i < paths.length; ++i) {
|
|
const id = paths[i].dataset.id;
|
|
const views = data[id] || 0;
|
|
paths[i].dataset.views = views;
|
|
const colorLevel = Math.ceil((views / max.value) * 6);
|
|
const classList = paths[i].classList;
|
|
for (let j = 1; j < 7; j++) {
|
|
paths[i].classList.remove(`color-${j}`);
|
|
}
|
|
paths[i].classList.add(`color-${colorLevel}`)
|
|
paths[i].dataset.views = views;
|
|
}
|
|
}
|
|
|
|
// handle map tooltip hover
|
|
function mapTooltipHoverOver() {
|
|
const tooltip = document.querySelector("#map-tooltip");
|
|
if (!tooltip) return;
|
|
if (!event.target.dataset.id) return mapTooltipHoverOut();
|
|
if (!tooltip.classList.contains("active")) {
|
|
tooltip.classList.add("visible");
|
|
}
|
|
tooltip.dataset.tooltip = `${event.target.ariaLabel}: ${event.target.dataset.views || 0}`;
|
|
const rect = event.target.getBoundingClientRect();
|
|
tooltip.style.top = rect.top + (rect.height / 2) + "px";
|
|
tooltip.style.left = rect.left + (rect.width / 2) + "px";
|
|
event.target.classList.add("active");
|
|
}
|
|
function mapTooltipHoverOut() {
|
|
const tooltip = document.querySelector("#map-tooltip");
|
|
const map = document.querySelector("svg.map");
|
|
const paths = map.querySelectorAll("path");
|
|
if (!tooltip || !map) return;
|
|
tooltip.classList.remove("visible");
|
|
for (let i = 0; i < paths.length; ++i) {
|
|
paths[i].classList.remove("active");
|
|
}
|
|
}
|
|
|
|
// create stats charts
|
|
function createCharts() {
|
|
createViewsChart();
|
|
createBrowsersChart();
|
|
createReferrersChart();
|
|
createOsChart();
|
|
feedMapData();
|
|
}
|
|
|
|
// change stats period for showing charts and data
|
|
function changeStatsPeriod(event) {
|
|
const period = event.target.dataset.period;
|
|
if (!period) return;
|
|
const canvases = document.querySelector("#stats").querySelectorAll("[data-period]");
|
|
const buttons = document.querySelector("#stats").querySelectorAll(".nav");
|
|
if (!buttons || !canvases) return;
|
|
buttons.forEach(b => b.disabled = false);
|
|
event.target.disabled = true;
|
|
canvases.forEach(canvas => {
|
|
if (canvas.dataset.period === period) {
|
|
canvas.classList.remove("hidden");
|
|
} else {
|
|
canvas.classList.add("hidden");
|
|
}
|
|
});
|
|
feedMapData(period);
|
|
}
|
|
|
|
// htmx prefetch extension
|
|
// https://github.com/bigskysoftware/htmx-extensions/blob/main/src/preload/README.md
|
|
htmx.defineExtension('preload', {
|
|
onEvent: function(name, event) {
|
|
if (name !== 'htmx:afterProcessNode') {
|
|
return
|
|
}
|
|
var attr = function(node, property) {
|
|
if (node == undefined) { return undefined }
|
|
return node.getAttribute(property) || node.getAttribute('data-' + property) || attr(node.parentElement, property)
|
|
}
|
|
var load = function(node) {
|
|
var done = function(html) {
|
|
if (!node.preloadAlways) {
|
|
node.preloadState = 'DONE'
|
|
}
|
|
|
|
if (attr(node, 'preload-images') == 'true') {
|
|
document.createElement('div').innerHTML = html
|
|
}
|
|
}
|
|
|
|
return function() {
|
|
if (node.preloadState !== 'READY') {
|
|
return
|
|
}
|
|
var hxGet = node.getAttribute('hx-get') || node.getAttribute('data-hx-get')
|
|
if (hxGet) {
|
|
htmx.ajax('GET', hxGet, {
|
|
source: node,
|
|
handler: function(elt, info) {
|
|
done(info.xhr.responseText)
|
|
}
|
|
})
|
|
return
|
|
}
|
|
if (node.getAttribute('href')) {
|
|
var r = new XMLHttpRequest()
|
|
r.open('GET', node.getAttribute('href'))
|
|
r.onload = function() { done(r.responseText) }
|
|
r.send()
|
|
}
|
|
}
|
|
}
|
|
var init = function(node) {
|
|
if (node.getAttribute('href') + node.getAttribute('hx-get') + node.getAttribute('data-hx-get') == '') {
|
|
return
|
|
}
|
|
if (node.preloadState !== undefined) {
|
|
return
|
|
}
|
|
var on = attr(node, 'preload') || 'mousedown'
|
|
const always = on.indexOf('always') !== -1
|
|
if (always) {
|
|
on = on.replace('always', '').trim()
|
|
}
|
|
node.addEventListener(on, function(evt) {
|
|
if (node.preloadState === 'PAUSE') {
|
|
node.preloadState = 'READY'
|
|
if (on === 'mouseover') {
|
|
window.setTimeout(load(node), 100)
|
|
} else {
|
|
load(node)()
|
|
}
|
|
}
|
|
})
|
|
switch (on) {
|
|
case 'mouseover':
|
|
node.addEventListener('touchstart', load(node))
|
|
node.addEventListener('mouseout', function(evt) {
|
|
if ((evt.target === node) && (node.preloadState === 'READY')) {
|
|
node.preloadState = 'PAUSE'
|
|
}
|
|
})
|
|
break
|
|
|
|
case 'mousedown':
|
|
node.addEventListener('touchstart', load(node))
|
|
break
|
|
}
|
|
node.preloadState = 'PAUSE'
|
|
node.preloadAlways = always
|
|
htmx.trigger(node, 'preload:init')
|
|
}
|
|
const parent = event.target || event.detail.elt;
|
|
parent.querySelectorAll("[preload]").forEach(function(node) {
|
|
init(node)
|
|
node.querySelectorAll('a,[hx-get],[data-hx-get]').forEach(init)
|
|
})
|
|
}
|
|
}) |