PageSourceSearch

https://dwz.li/scripts/main.js

js dwz.li collected 2026-10-06 13:16:54 UTC 11,688 bytes, 362 lines download raw bytes

1// log htmx on dev
2// htmx.logAll();
3
4// add text/html accept header to receive html instead of json for the requests
5document.body.addEventListener("htmx:configRequest", function(evt) {
6  evt.detail.headers["Accept"] = "text/html,*/*";
7});
8
9// redirect to homepage
10document.body.addEventListener("redirectToHomepage", function() {
11  setTimeout(() => {
12    window.location.replace("/");
13  }, 1500);
14});
15
16// reset form if event is sent from the backend
17function resetForm(id) {
18  return function() {
19    const form = document.getElementById(id);
20    if (!form) return;
21    form.reset();
22  }
23}
24document.body.addEventListener("resetChangePasswordForm", resetForm("change-password"));
25document.body.addEventListener("resetChangeEmailForm", resetForm("change-email"));
26
27// an htmx extension to use the specifed params in the path instead of the query or body
28htmx.defineExtension("path-params", {
29  onEvent: function(name, evt) {
30    if (name === "htmx:configRequest") {
31      evt.detail.path = evt.detail.path.replace(/{([^}]+)}/g, function(_, param) {
32        var val = evt.detail.parameters[param]
33        delete evt.detail.parameters[param]
34        return val === undefined ? "{" + param + "}" : encodeURIComponent(val)
35      })
36    }
37  }
38})
39
40// find closest element
41function closest(selector, elm) {
42  let element = elm || this;
43
44  while (element && element.nodeType === 1) {
45    if (element.matches(selector)) {
46      return element;
47    }
48
49    element = element.parentNode;
50  }
51
52  return null;
53};
54
55// get url query param
56function getQueryParams() {
57  const search = window.location.search.replace("?", "");
58  const query = {};
59  search.split("&").map(q => {
60    const keyvalue = q.split("=");
61    query[keyvalue[0]] = keyvalue[1];
62  });
63  return query;
64}
65
66// trim text
67function trimText(selector, length) {
68  const element = document.querySelector(selector);
69  if (!element) return;
70  let text = element.textContent;
71  if (typeof text !== "string") return;
72  text = text.trim();
73  if (text.length > length) {
74    element.textContent = text.split("").slice(0, length).join("") + "...";
75  }
76}
77
78function formatDateHour(selector) {
79  const element = document.querySelector(selector);
80  if (!element) return;
81  const dateString = element.dataset.date;
82  if (!dateString) return;
83  const date = new Date(dateString);
84  element.textContent = date.getHours() + ":" + date.getMinutes();
85}
86
87// show QR code
88function handleQRCode(element, id) {
89  const dialog = document.getElementById(id);
90  const dialogContent = dialog.querySelector(".content-wrapper");
91  if (!dialogContent) return;
92  openDialog(id, "qrcode");
93  dialogContent.textContent = "";
94  const qrcode = new QRCode(dialogContent, {
95    text: element.dataset.url,
96    width: 200,
97    height: 200,
98    colorDark : "#000000",
99    colorLight : "#ffffff",
100    correctLevel : QRCode.CorrectLevel.H
101  });   
102}
103
104// copy the link to clipboard
105function handleCopyLink(element) {
106  navigator.clipboard.writeText(element.dataset.url);
107}
108
109// copy the link and toggle copy button style
110function handleShortURLCopyLink(element) {
111  handleCopyLink(element);
112  const clipboard = element.parentNode.querySelector(".clipboard") || closest(".clipboard", element);
113  if (!clipboard || clipboard.classList.contains("copied")) return;
114  clipboard.classList.add("copied");
115  setTimeout(function() {
116    clipboard.classList.remove("copied");
117  }, 1000);
118}
119
120// open and close dialog
121function openDialog(id, name) {
122  const dialog = document.getElementById(id);
123  if (!dialog) return;
124  dialog.classList.add("open");
125  if (name) {
126    dialog.classList.add(name);
127  }
128}
129
130function closeDialog() {
131  const dialog = document.querySelector(".dialog");
132  if (!dialog) return;
133  while (dialog.classList.length > 0) {
134    dialog.classList.remove(dialog.classList[0]);
135  }
136  dialog.classList.add("dialog");
137}
138
139window.addEventListener("click", function(event) {
140  const dialog = document.querySelector(".dialog");
141  if (dialog && event.target === dialog) {
142    closeDialog();
143  }
144});
145
146// handle navigation in the table of links
147function setLinksLimit(event) {
148  const buttons = Array.from(document.querySelectorAll("table .nav .limit button"));
149  const limitInput = document.querySelector("#limit");
150  if (!limitInput || !buttons || !buttons.length) return;
151  limitInput.value = event.target.textContent;
152  buttons.forEach(b => {
153    b.disabled = b.textContent === event.target.textContent;
154  });
155}
156
157function setLinksSkip(event, action) {
158  const buttons = Array.from(document.querySelectorAll("table .nav .pagination button"));
159  const limitElm = document.querySelector("#limit");
160  const totalElm = document.querySelector("#total");
161  const skipElm = document.querySelector("#skip");
162  if (!buttons || !limitElm || !totalElm || !skipElm) return;
163  const skip = parseInt(skipElm.value);
164  const limit = parseInt(limitElm.value);
165  const total = parseInt(totalElm.value);
166  skipElm.value = action === "next" ? skip + limit : Math.max(skip - limit, 0);
167  document.querySelectorAll(".pagination .next").forEach(elm => {
168    elm.disabled = total <= parseInt(skipElm.value) + limit;
169  });
170  document.querySelectorAll(".pagination .prev").forEach(elm => {
171    elm.disabled = parseInt(skipElm.value) <= 0;
172  });
173}
174
175function updateLinksNav() {
176  const totalElm = document.querySelector("#total");
177  const skipElm = document.querySelector("#skip");
178  const limitElm = document.querySelector("#limit");
179  if (!totalElm || !skipElm || !limitElm) return;
180  const total = parseInt(totalElm.value);
181  const skip = parseInt(skipElm.value);
182  const limit = parseInt(limitElm.value);
183  document.querySelectorAll(".pagination .next").forEach(elm => {
184    elm.disabled = total <= skip + limit;
185  });
186  document.querySelectorAll(".pagination .prev").forEach(elm => {
187    elm.disabled = skip <= 0;
188  });
189}
190
191function resetTableNav() {
192  const totalElm = document.querySelector("#total");
193  const skipElm = document.querySelector("#skip");
194  const limitElm = document.querySelector("#limit");
195  if (!totalElm || !skipElm || !limitElm) return;
196  skipElm.value = 0;
197  limitElm.value = 10;
198  const total = parseInt(totalElm.value);
199  const skip = parseInt(skipElm.value);
200  const limit = parseInt(limitElm.value);
201  document.querySelectorAll(".pagination .next").forEach(elm => {
202    elm.disabled = total <= skip + limit;
203  });
204  document.querySelectorAll(".pagination .prev").forEach(elm => {
205    elm.disabled = skip <= 0;
206  });
207  document.querySelectorAll("table .nav .limit button").forEach(b => {
208    b.disabled = b.textContent === limit.toString();
209  });
210}
211
212// tab click
213function setTab(event, targetId) {
214  const tabs = Array.from(closest("nav", event.target).children);
215  tabs.forEach(function (tab) {
216    tab.classList.remove("active");
217  });
218  if (targetId) {
219    document.getElementById(targetId).classList.add("active");
220  } else {
221    event.target.classList.add("active");
222  }
223}
224
225// show clear search button
226function onSearchChange(event) {
227  const clearButton = event.target.parentElement.querySelector("button.clear");
228  if (!clearButton) return;
229  clearButton.style.display = event.target.value.length > 0 ? "block" : "none";
230}
231
232function clearSeachInput(event) {
233  event.preventDefault();
234  const button = closest("button", event.target);
235  const input = button.parentElement.querySelector("input");
236  if (!input) return;
237  input.value = "";
238  button.style.display = "none";
239  htmx.trigger("body", "reloadMainTable");
240}
241
242// detect if search inputs have value on load to show clear button
243function onSearchInputLoad() {
244  const linkSearchInput = document.getElementById("search");
245  if (!linkSearchInput) return;
246  const linkClearButton = linkSearchInput.parentElement.querySelector("button.clear")
247  linkClearButton.style.display = linkSearchInput.value.length > 0 ? "block" : "none";
248
249  const userSearchInput = document.getElementById("search_user");
250  if (!userSearchInput) return;
251  const userClearButton = userSearchInput.parentElement.querySelector("button.clear")
252  userClearButton.style.display = userSearchInput.value.length > 0 ? "block" : "none";
253
254  const domainSearchInput = document.getElementById("search_domain");
255  if (!domainSearchInput) return;
256  const domainClearButton = domainSearchInput.parentElement.querySelector("button.clear")
257  domainClearButton.style.display = domainSearchInput.value.length > 0 ? "block" : "none";
258}
259
260onSearchInputLoad();
261
262// create user checkbox control
263function canSendVerificationEmail() {
264  const canSendVerificationEmail = !document.getElementById("create-user-verified").checked && !document.getElementById("create-user-banned").checked;
265  const checkbox = document.getElementById("send-email-label");
266  if (canSendVerificationEmail)
267    checkbox.classList.remove("hidden");
268  if (!canSendVerificationEmail && !checkbox.classList.contains("hidden"))
269    checkbox.classList.add("hidden");
270}
271
272// htmx prefetch extension
273// https://github.com/bigskysoftware/htmx-extensions/blob/main/src/preload/README.md
274htmx.defineExtension("preload", {
275  onEvent: function(name, event) {
276    if (name !== "htmx:afterProcessNode") {
277      return
278    }
279    var attr = function(node, property) {
280      if (node == undefined) { return undefined }
281      return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
282    }
283    var load = function(node) {
284      var done = function(html) {
285        if (!node.preloadAlways) {
286          node.preloadState = "DONE"
287        }
288
289        if (attr(node, "preload-images") == "true") {
290          document.createElement("div").innerHTML = html
291        }
292      }
293
294      return function() {
295        if (node.preloadState !== "READY") {
296          return
297        }
298        var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
299        if (hxGet) {
300          htmx.ajax("GET", hxGet, {
301            source: node,
302            handler: function(elt, info) {
303              done(info.xhr.responseText)
304            }
305          })
306          return
307        }
308        if (node.getAttribute("href")) {
309          var r = new XMLHttpRequest()
310          r.open("GET", node.getAttribute("href"))
311          r.onload = function() { done(r.responseText) }
312          r.send()
313        }
314      }
315    }
316    var init = function(node) {
317      if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
318        return
319      }
320      if (node.preloadState !== undefined) {
321        return
322      }
323      var on = attr(node, "preload") || "mousedown"
324      const always = on.indexOf("always") !== -1
325      if (always) {
326        on = on.replace("always", "").trim()
327      }
328      node.addEventListener(on, function(evt) {
329        if (node.preloadState === "PAUSE") {
330          node.preloadState = "READY"
331          if (on === "mouseover") {
332            window.setTimeout(load(node), 100)
333          } else {
334            load(node)()
335          }
336        }
337      })
338      switch (on) {
339        case "mouseover":
340          node.addEventListener("touchstart", load(node))
341          node.addEventListener("mouseout", function(evt) {
342            if ((evt.target === node) && (node.preloadState === "READY")) {
343              node.preloadState = "PAUSE"
344            }
345          })
346          break
347
348        case "mousedown":
349          node.addEventListener("touchstart", load(node))
350          break
351      }
352      node.preloadState = "PAUSE"
353      node.preloadAlways = always
354      htmx.trigger(node, "preload:init")
355    }
356    const parent = event.target || event.detail.elt;
357    parent.querySelectorAll("[preload]").forEach(function(node) {
358      init(node)
359      node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
360    })
361  }
362})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.