index.js
3676 bytes
1document.addEventListener("DOMContentLoaded", function () {
2 if (navigator.standalone === true) {
3 document.addEventListener("click", function (event) {
4 const link = event.target.closest("a[data-open-in-safari]");
5 if (!link) return;
6
7 event.preventDefault();
8 window.location.href = `x-safari-${link.href}`;
9 });
10 }
11
12 const addVideoModal = document.getElementById("add-video-modal");
13 if (addVideoModal) {
14 addVideoModal.addEventListener("shown.bs.modal", function () {
15 const input = addVideoModal.querySelector("input");
16 if (input) input.focus();
17 });
18 }
19
20 const searchInput = document.getElementById("video-search");
21 if (!searchInput) return;
22
23 function performSearch() {
24 const videoCards = Array.from(document.querySelectorAll(".video-card"));
25 const query = searchInput.value.trim();
26
27 if (!query) {
28 videoCards.forEach((card) => (card.style.display = ""));
29 return;
30 }
31
32 const videos = videoCards.map((card) => ({
33 element: card,
34 title: card.dataset.title || "",
35 channelName: card.dataset.channelName || "",
36 }));
37
38 const fuse = new Fuse(videos, {
39 keys: ["title", "channelName"],
40 threshold: 0.4,
41 includeScore: true,
42 });
43
44 const results = fuse.search(query);
45 const resultElements = new Set(results.map((r) => r.item.element));
46
47 videoCards.forEach((card) => {
48 card.style.display = resultElements.has(card) ? "" : "none";
49 });
50 }
51
52 searchInput.addEventListener("input", performSearch);
53
54 if (searchInput.value.trim()) {
55 performSearch();
56 }
57});
58
59var currentVideoID = "";
60
61function openResolutionModal(button) {
62 currentVideoID = button.dataset.videoId;
63 document.getElementById("resolution-modal-title").textContent =
64 button.dataset.videoTitle;
65 new bootstrap.Modal(document.getElementById("resolution-modal")).show();
66}
67
68document.addEventListener("click", function (e) {
69 const btn = e.target.closest(".resolution-btn");
70 if (!btn || !currentVideoID) return;
71 const formData = new FormData();
72 formData.append("format", btn.dataset.height);
73 fetch("/videos/" + currentVideoID + "/download", {
74 method: "POST",
75 body: formData,
76 }).then(function (resp) {
77 if (resp.ok) {
78 bootstrap.Modal.getInstance(
79 document.getElementById("resolution-modal"),
80 ).hide();
81 location.reload();
82 }
83 });
84});
85
86function animateRemove(selector) {
87 const el = document.querySelector(selector);
88 if (!el) return;
89 const siblings = [...el.parentElement.children].filter(c => c !== el);
90 const oldRects = siblings.map(s => s.getBoundingClientRect());
91
92 el.style.transition = "opacity 0.15s ease-out";
93 el.style.opacity = "0";
94 setTimeout(() => {
95 el.remove();
96 siblings.forEach((s, i) => {
97 const newRect = s.getBoundingClientRect();
98 const dx = oldRects[i].left - newRect.left;
99 const dy = oldRects[i].top - newRect.top;
100 if (!dx && !dy) return;
101 s.style.transition = "none";
102 s.style.transform = `translate(${dx}px, ${dy}px)`;
103 s.offsetHeight; // force reflow
104 s.style.transition = "transform 0.2s ease-out";
105 s.style.transform = "";
106 });
107 }, 150);
108}
109
110function showFormError(modalId, errorText) {
111 const modal = document.getElementById(modalId);
112 if (!modal) return;
113 const field = modal.querySelector("input");
114 if (!field) return;
115 field.setCustomValidity(errorText);
116 field.onfocus = () => field.reportValidity();
117 field.onchange = () => field.setCustomValidity("");
118 field.reportValidity();
119}
120
121if ("serviceWorker" in navigator) {
122 navigator.serviceWorker
123 .register("/assets/sw.js")
124 .then((registration) => {
125 console.log(
126 "Service Worker registered with scope:",
127 registration.scope,
128 );
129 })
130 .catch((error) => {
131 console.log("Service Worker registration failed:", error);
132 });
133}