From dcf14cfdeffb0d1f5f96599cf41d79d4572595ba Mon Sep 17 00:00:00 2001 From: Tirey Morris Date: Wed, 24 Jul 2024 14:55:43 -0500 Subject: [PATCH] use terser for public js --- bun.lockb | Bin 152029 -> 153245 bytes package.json | 6 +- public/scripts/hyperwave.js | 233 +----------------------------------- src/util/hyperwave.js | 232 +++++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 234 deletions(-) create mode 100644 src/util/hyperwave.js diff --git a/bun.lockb b/bun.lockb index c0e839f2a22104b952609f20166c3c8f34181132..907d9bb78f1b1b7022b52f881ba564d1f16ddffa 100755 GIT binary patch delta 16224 zcmeHud3Y5?-gS4A3+cdRhXe>j78L>sSx9ama9KnRxnUDX2zy8_AtC!sAYn0K6L1|Y zpg;$OK^^211(%5nD8i_Kh`1r3BCC){SY!0hzW4jbdN}vg@6=xP ztLmmZ{lhwa^+x?qpqE^7dJi`$DDy!+Hk6i|T~wHn zyF(uzDO>F!pe&8AX3B{`|c_cYQu<^^CjnL0J4ARGD&*ml^{z^r#tqS);l zJ!TY4_H>4ajoi+*Wvtn=CF2yOG19%&XXU1)crdpvl;f23x43Y652O6{@uu!b6l3&u zrL!lP)o-3?_S%!1I*l#GO)`7o1hX1%b(x;YIjkoG`Khl1a}>+Kjlk_Eo8@xch1n^d zX-YP1cHiyEE6B)BQ7*%#XShAayr(K3V@_Dn87raCla+y}GT+-FFvpkfE(|Fu$WW$u zLfplL1u6U8WaHphkw3{-`%Drpq&dD#zD1gUJ6P13^z1C$ZN()p| z;P&KZO?N9Nu%1D%4}m$hJz!4K)D%zd3`}3UTyv=lvc;JC&JNWu+4UT^J1q;n@}v~A zQRU5ivv^d2c(;CZX-J{DMD4)rbW<=LrLM(kMT+7NI~mOChJx9;Yr4s)Ss6tcsCEZz z&P+*ojIorvF>>0khnl_I3Y(7jI+*no6dQBXR7p!IbcamI@vKJoIGhSGw}HR*cF7yh zG`LyU_rkm}xBl8u?Ju@94`{rJqUBz9ke6_vtA~V^kg}KCrWT&3xQWPihs3JVrVc!5N7M6w*&CL=X-4|jU zL`7Vf=Fnpk6eSu~9g*MFp}z&I7Ym5+AcyWdSW&P=k+!*;L+=G^q>*-Eh(mjFum~RR z)V2>6nZupBGQ=!bhs|n(hKNdt>ml|=0W^flPQV%nOBENw9k$Lx6(wF&1;y$Gh=oD- z5f|=tXnz?hf=4*@P@FPTAl4ITOJ4@7hhc5bi}8gKuFz6Tb|)DaiL9J&vVCiX;QuYkxbb&V{0VVTvkK(i6vS;0Km z2E&RGRYPL6g(JjPqZ(c_A68FTiU^N$XdjOi!Q-7;{c$35ywf C8}7wQ8KG z9PiYRL7-Dmp-fUt9D9MRAC# z&awI;#7y7FAL!8EgVhUpv5FYMWmxoWb39#f9y*P*@Xii>J1p!cI!I@S)?ktdPI2lZ zCz%uHV@$^~SnQN;Y=(W7g&|`)8l;$BizNwjXuVU!hHg&17$RrW5cMsv9)JZOMuCvY zX4SBQ9JXRu(V_~D`YK}VmEjC}SDfiqkCE$fSiMA5SggL!(lxrdUN_C`7@Q>Bp$$kA zm1$0W4n%Wa!-qTc*I;pY^^Cq(!{U}VyV4&gB^#-0RQ5D1E?#|O%*SBSbnbelU2`>T z58_JeG<esFvFY!RH}wAIPM$;SPlXCP+!Jp1-yj;KU(CBzj>)EhIn*$jb;|qg6 zk5d|lgioFb&U0!pc_I@u6;`m*wkFT$1&)UOh|w=_3d2)<^F?O9(-xi2+2up|M%x7*a2Y3{PL}9*@}IacTt~Q3?9W zBZ3Q^y0g%9KCA(}bulb1mU&It3#&h@7DielTxscU*l^t)`Yc$9u&^-LyGK-zI{{s<|7agnK5EaFJbytbmJPt}+{N|L1 z%o3;eVTssK;#4n-ijuxs@JtarvsY;&#kl{#?>}-&*mnc<0eGvT0&p!O)aIs8?z8m& zfJ=>{|3SguEmsFASXv|8iT#m+)7uE+6|?P&5GB z+X!SX8Mdww$V|t^GXmKMD23}A6~NJqa3^L(@Gm2fS?!-3e^8n^w05FhQ z-$Ij1l}9XL5tyMiGvi`_yaZq%v%<#!>W>2qWa>`>)Sm(vYO^nF2{3&npeQ0|PTf+P zs|?d*R`V>t33}er$<)_cyw1|eoQIbzo6P$DYVmq7>v;vJ3%m|+&wU6mfK4B$B^VWK zH`A5c%!WR)^gFR1(muD+Yco5v4eWw5hGTW&DI1}eA`vS+$go^-O<{H2XssRQvE2;t5AtUkMWR~|~ zr53Xt9X8u7t&a$&u7xG|gIS=BWw!<6pVH3aAWLs=*&UdOKxPN;wz#vE-UZBtx>|ZD zn8D&wZ^U3W9AOoVv#g{Z7md#98TN+VKXX#snsa>@xo* zj11+E#k$y7=Fb5r$(DL2=BURZT@_Vjm>p45=5KiQL~2NzgdcW19c%{|Qv7e211m;8 z@@z0W{IFH-PR#T&q;n-7Rjplxh!Mz)K4#gqnbPCHms3kmXi>31*R%Ryi`G zt5jqBjF=->4dyJZv3MPryWnLo1DQ_wDwxu1mQHRA`vc1+w}brynE8)^nRJ{#wYh%4o74*g`Qrz}1V=C~^@`)4o%nb9*AS6Mol&He&rxl5K#X7sXUlNr5Y*iGi*p?QeBJdF*5?0$9E$z=dmf6NzM6VF{3RlyEd~vf6ZDDe`H`qL15-> zZxtZ3;!c)Lrt5XK?ApubT#}z5 z|G4q~CG>lt-ht;macNstz4)3VZT*_}olBqpw(r)%@pIz4m<*?FyZ^L(55-*xy)vcI!-GCLHl$lO{qjeh-fi&0p;>El zYHqH4wj$(_kY9%`i`z5s`r@0jx}WSc=xBPw>Q62{o-eYG1SEX(^a%G)M>Zr6IrrUj z2j3}wW$vppuI!x^KlDVvmjgTHf3(EM-M#7H2^SrEo7>)A`QF>VR3CXXz9{Y?zm}ti zR+azUJ{*@4Rk_*dCiuAc@NkmoeAF)P{?VnL6!||Ui36~{fpto>$Lrq3$LwO^5tmva zzJL{T9D_RQ!W*x-N0Y>9SSMjsiST1dV&w_DSbofIsxtHto2lEmAv-h%b3 zP){X^sTFqNKIKwt#Aa9lm3GnMv`f7uQcfp{ov=PS?b1|LG_6P!#Xs9cQH4vZql)b? zJD?z~+LKIc-Kh*jq>L|8Sjnu$T@F~kdYvGKf1Z6T^*jsC?h z#$Rx${^I2e7~(~{u>ayxTZ!afFhp2eVYLzZMGWzhU1VQ$scpqOumUdIMcYd*HArM! z!VqD72CKbjbva2CU$KihmtAT{u?tq`t9B7`#iiaYO0HlDVf_H>UeV<$#&-?lyXsQA zh=Z_VsxiK6F14#zat-5yRRt?V^sL7Cu48=FF15R;fED*E#&_MNhKp6#F+Nx|up-5v zUopNL7~iihHA+;&8eN0&-EgT6@$wCf?c@tLcfXe-NN{8y3{`6 zotsI^12C^`Z@JXIBI8!l@}00ggVld|D>X?LtEzqZ9Mz@Xw|p1O&YEhMAu8g@Pr0okUU5s#s@;5IuKlPNgW8MDO6DyEPMJuSZRZ>+6Tf=SwSJL zE`-502*c$n8-!~VYAB45gX%(9Uk}2@x)4UmY6_!$A&jpFAxXYm4?n&-1iLSUWSQ&> z;cW_ADU6l64q>Vvglrwcc=-;6fcg;H`azf|GyEXzr0^Mq6xpgigyIGe=G2FfDtA%n zY=;oi0D@bVG=Ok`!VeVEWfwby#SI}mVTX_*4^oI}1ffqu2-D<}h7e9usG^W9dp3fw zvN43!jUeR83JP&eAPjB{Az!X)4B;Au8VVjcs0oDiO(AS-0-;D&QyAS0!uX~TX2_SD zLhx-4!QKo)iA-(=;cW_ADa?|3a|lygK*(+mVUB!6lYL;N8ulqLRZlKP1Jg0x6>2>>lFpBtdcCU);%Hcp7gV_`YB zm70z}Px7&Q|9~+h0e|`=!&_meQa%S0&yUKkfgArO%d+-r%=IWQDGi_9s z_w)XtSYf_=wFlDQz$h@7Fo()hTJ&o&r5md2;N-Y0B)T#tXo z1M6*w@c(K>md2a%kC})t-O~7cb*H7xFtt)nt+LA!i@|J^9r@DI_>7;OV@JNRG(I6< zV-0})mNpm6D4!O8Z)x)^ts%5Uh_jygmd0mm)qEV!#uiv2AF1C$oPm?cXcItVYY2~6 z8eeboSvfnvikYt&upVGoY-!CAf6Gwth7ua9X#sp|X^(LvEX=2SR{$2^O9yIvFlJ)} z;R#C%K%CFG8Tfx7Cbt6kuz(dm1%ab%4e(7J)1J2SwL$!_r3p)G7YN}9zyixGaV3X@ zaOMe_xj?-~E?c1X&>AXo_X0Jd2_1nCGRpuu!X#Pph#F-70P=^xc3_7bwNULSXD?I( zOTUKAW#ck^3mgCr0*8R_04`M*fP-P@{{$=r*lBi>ed025*|$ei)e-jdz#4!rasLdg2A-24=zi%6MCSkx17*Nm02|VHzs-B4 zFvR&7mydY)*q4ul`9L`X$ONVVSwJ?B1AK`5W5MHq5x_`b6mUQAAP@)eQG7q3uZG9I z(TF&Jo3Kc?Uyxh{&H^U^Zi_j<5#T7$73c_9`{lBz@+Z?QI_0qzLy0RFi` zE^r?(5Qu>m3-kv108UlLFIMkqzXfUU10MiE&|d|=25bWIfPA@hu{w76I7Al$PXLbr ze5+M}w)v`K2rv}j3n#uCY7MjnngDUgHwbtT;75d%hXKM?k}X;lM+{NMIC@2qXdV ze4*w-rXjbOiVkZ36P})$SNzERX>3 z9oXH#c*Ms6gMlFc-)C&&k;?O9Ch#zX3h)^4L%;xFAn*V%2&hKdb)XL7Rp9f$Dd0x} zon_frq}2x)-v*7R8_5 z>eRe-c-=v+U8=^G-s1S{VlM1J4#4qbgC7L4fOLTUp9rSQjR5F^96Mc-tKScx=fMSy zKZ)RlU+fblZjiU6}6M6bJxX z0?gY2+#IlOd~W$8-U?_71Onbc4nmyy*`c^n{BWcL0gi$_?EvuTU}bj!?SU=;3q^x@ zhV})z0>MB>z?F z+*^s4dG_%PWFy`)u@~%~7I;STKo|fpPk+mP0DM1B4DRZ|0H=Hyz@vrJ%FLWn9xa>= z9y6ap>N*aH**`M?ZdI#2`@15Bqj6QJ*SZTb&qavm_BQ_e&> z)+2xo6%)MA@Y<7B!az87Q2H%b@8LaGdH3aNklM=ozX|WR5t=$7Dl{@YG_ri~3bndZ zsrUOR?`IN74-1V9jl?XZ<`(3jqJvkTN_%(Bi3>=H35^MjP=5PH#ruf`QX)bl!a~D- z`xgmDvF_T17@8&k6nj&807W11Km zj5JeHPnCS;x8~WL+bQiw%5f+g7>yh)v3V-phlWkN=pS)AN5V+?C)VypjyA~Qb119$ z;}g$k-Of>FrRkXK^ITKkq{g>?B5b+kBehc7GFGnt7>P3y<@aFqqHOeu+A6SSEY2rP-oB{jBa^?sRIfQt z8DqRl#>s)7puqO=viKAAnZSSU`@>e}vnR;Vo$8c8@7IUBwkOnkzV+kVjG1w8{Ie&> zO*_>xEn}iQvl}k#nTV4Stw#92a?i~0*^4k*`2+?bp9B646AIAdr<+l>w5oFs?s#yrGLl9^~#!wNpf*0sr| zvy~j3ydyci1D@+*t|Pdu&Hg3)xBY8ji^8RQvdsDP_dC)9w>p#MUp`eM+MGdyI)=OH z?BOMg>W#(W%EgTg?SWhSbDye_+Q+G~(;oEr9+xcGgPxlG2$uWysJ=7~QJmqH4L`$h z%G|Q^XUP1iTTcH>jfnPs&3NP6cN$Tc)vt#|6TV9_fBYk3XS0Ez@3vZP2SiG?=u}MKwFn3AO2i@ zMk~mct@okZd$Q$_eQH1(?+1}rQJ8(d__ObosBKbYa2714$d?h#h&8)Mj z&mjlAjBf6lE*pQ1+JC2K$npEN@0czpd<{3gV_De53sgTXSQ3vDuOS$_SoDj>t!IK!v@C!R+-jd{fC+qH42gnEZs}TeC;E=_m zva+}br>FiH^UJhvVf)dg`_DbFCk=0GVDlVV74>A7bK=^nb5?%G3?I2MXwA~=qvX5$ z)pnC_Vk>ax?|k(28#{&#f2m=rSre$g6hr4UH+W!~3Nu_%L delta 15432 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 P-K!s{dbYQIE9_qYviRTL diff --git a/package.json b/package.json index 54eeee2..7bf1eb4 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,13 @@ "name": "hyperwave", "version": "0.3.0", "scripts": { - "build": "bun build:css && bun build:server", + "build": "bun build:css && bun build:server && bun build:hyperwavejs", "build:css": "unocss \"src/**/*.tsx\" -o public/styles/uno.css", "build:server": "bun build --compile ./src/server.tsx --outfile ./dist/server", + "build:hyperwavejs": "terser src/util/hyperwave.js -o public/scripts/hyperwave.js --compress --mangle", "css:watch": "unocss --watch \"src/**/*.tsx\" -o public/styles/uno.css", "db": "bun run src/db.ts", - "dev": "bun install && DEBUG=true concurrently --restart-tries=3 \"bun css:watch\" \"bun server:watch\"", + "dev": "bun install && DEBUG=true concurrently --restart-tries=3 \"bun css:watch\" \"bun server:watch\" \"bun build:hyperwavejs\"", "prettier": "bunx prettier --write src/ test/ --plugin prettier-plugin-tailwindcss", "server:watch": "nodemon --watch src --ext ts,tsx --exec 'bun run --hot src/server.tsx'", "start": "bun run build && pm2 start; pm2 reload all --update-env", @@ -22,6 +23,7 @@ "cheerio": "^1.0.0-rc.12", "hono": "^4.5.0", "nodemon": "^3.1.4", + "terser": "^5.31.3", "unocss": "^0.61.5", "uuid": "^10.0.0", "zod": "^3.23.8" diff --git a/public/scripts/hyperwave.js b/public/scripts/hyperwave.js index 90087f9..ff1897a 100644 --- a/public/scripts/hyperwave.js +++ b/public/scripts/hyperwave.js @@ -1,232 +1 @@ -/** - * Configuration object for hyperwave. - * @typedef {Object} HyperwaveConfig - * @property {boolean} debug - If true, enables debug mode which logs additional information. - * @property {string} defaultMethod - The default HTTP method used for fetching content. - * @property {number} defaultDebounceDelay - The default delay in milliseconds for debouncing events. - * @property {number} defaultLimit - The default number of items to fetch per pagination request. - * @property {number} defaultTotalItems - The default total number of items available for pagination. - * @property {string} logPrefix - Prefix to use for log messages to distinguish them. - */ - -/** @type {HyperwaveConfig} */ -const hyperwaveConfig = { - debug: true, - defaultMethod: "GET", - defaultDebounceDelay: 50, - defaultLimit: 32, - defaultTotalItems: 2048, - logPrefix: "hyperwave:", -}; - -/** - * Logs messages to the console if debug mode is enabled. - * @param {string} level - The log level, e.g., "log" or "error". - * @param {...any} messages - The messages or objects to log. - */ -const log = (level, ...messages) => { - if (hyperwaveConfig.debug) { - console[level](`${hyperwaveConfig.logPrefix}`, ...messages); - } -}; - -/** - * Creates a debounced version of the provided function. - * @param {Function} func - The function to debounce. - * @param {number} delay - The delay in milliseconds to wait before invoking the function. - * @returns {Function} - The debounced function. - */ -const createDebouncedFunction = (func, delay) => { - let timeoutId; - return (...args) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => func.apply(this, args), delay); - }; -}; - -/** - * Fetches content from a specified URL using the provided options. - * @param {string} url - The URL to fetch content from. - * @param {Object} [fetchOptions={}] - Options to customize the fetch request. - * @param {string} [fetchOptions.method] - The HTTP method to use. - * @param {Object} [fetchOptions.headers] - Headers to include in the request. - * @returns {Promise} - A promise that resolves to the fetched content or null if an error occurs. - */ -const fetchContent = async (url, fetchOptions = {}) => { - const options = { - method: fetchOptions.method || hyperwaveConfig.defaultMethod, - headers: { Accept: "text/html", ...fetchOptions.headers }, - ...fetchOptions, - }; - - try { - const response = await fetch(url, options); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - const content = await response.text(); - log("log", `Content fetched from ${url}`, content.length); - return content; - } catch (error) { - log("error", `Error fetching from ${url}:`, error); - return null; - } -}; - -/** - * Updates a target element with new content, either replacing or appending based on mode. - * @param {HTMLElement} targetElement - The element to update with new content. - * @param {string} content - The HTML content to update. - * @param {string} mode - The mode of updating, either "replace" or "append". - */ -const updateTargetElement = (targetElement, content, mode = "replace") => { - if (mode === "replace") { - targetElement.innerHTML = content; - } else if (mode === "append") { - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = content; - while (tempDiv.firstChild) { - targetElement.appendChild(tempDiv.firstChild); - } - } - attachHyperwaveHandlers(targetElement); -}; - -/** - * Constructs a URL for pagination with the given offset and limit. - * @param {HTMLElement} triggerElement - The element that triggered the pagination. - * @param {number} offset - The offset for pagination. - * @param {number} limit - The limit for pagination. - * @returns {string} - The constructed URL for pagination. - */ -const buildPaginationUrl = (triggerElement, offset, limit) => { - const url = new URL( - triggerElement.getAttribute("href"), - window.location.origin, - ); - url.searchParams.set("offset", offset); - url.searchParams.set("limit", limit); - return url.toString(); -}; - -/** - * Handles pagination by fetching the next set of content and updating the target element. - * @param {HTMLElement} triggerElement - The element that triggered the pagination. - * @param {Object} fetchOptions - Options to customize the fetch request. - * @returns {Function} - A function to handle fetching the next page of content. - */ -const handlePagination = (triggerElement, fetchOptions) => { - let offset = parseInt(triggerElement.getAttribute("offset") || "0", 10); - const limit = parseInt( - triggerElement.getAttribute("limit") || hyperwaveConfig.defaultLimit, - 10, - ); - const totalItems = parseInt( - triggerElement.getAttribute("total") || hyperwaveConfig.defaultTotalItems, - 10, - ); - const mode = triggerElement.getAttribute("update-mode") || "replace"; - - let isFetching = false; - - return async () => { - if (isFetching || offset >= totalItems) return; - - isFetching = true; - const url = buildPaginationUrl(triggerElement, offset, limit); - const content = await fetchContent(url, fetchOptions); - if (content) { - const target = document.querySelector( - triggerElement.getAttribute("target"), - ); - updateTargetElement(target, content, mode); - offset += limit; - triggerElement.setAttribute("offset", offset); - log("log", `Content updated. New offset set: ${offset}`); - } - isFetching = false; - }; -}; - -/** - * Sets up event handlers for the given trigger element based on its attributes. - * @param {HTMLElement} triggerElement - The element to set up event handlers for. - */ -const setupEventHandlers = (triggerElement) => { - const method = - triggerElement.getAttribute("method") || hyperwaveConfig.defaultMethod; - const trigger = triggerElement.getAttribute("trigger") || "click"; - const debounceDelay = parseInt( - triggerElement.getAttribute("debounce") || - hyperwaveConfig.defaultDebounceDelay, - 10, - ); - const scrollThreshold = parseInt( - triggerElement.getAttribute("scroll-threshold") || "300", - 10, - ); - - const fetchOptions = { - method: method.toUpperCase(), - headers: { Accept: "text/html" }, - }; - - log( - "log", - `Setting up event handlers: method=${method}, trigger=${trigger}, debounceDelay=${debounceDelay}, scrollThreshold=${scrollThreshold}`, - ); - - const loadNextPage = handlePagination(triggerElement, fetchOptions); - - const eventHandler = createDebouncedFunction(async (event) => { - if (trigger !== "scroll") event.preventDefault(); - - if (trigger === "scroll") { - const nearBottom = - document.body.offsetHeight - (window.innerHeight + window.scrollY) <= - scrollThreshold; - if (!nearBottom) return; - } - - await loadNextPage(); - }, debounceDelay); - - if (trigger === "DOMContentLoaded") { - loadNextPage(); - } else { - const targetElement = trigger === "scroll" ? window : triggerElement; - targetElement.addEventListener(trigger, eventHandler); - triggerElement._hyperwaveHandler = eventHandler; - - if (trigger === "scroll") { - loadNextPage(); - } - } -}; - -/** - * Attaches hyperwave event handlers to all elements within a given root element. - * @param {HTMLElement} rootElement - The root element to search for trigger elements. - */ -const attachHyperwaveHandlers = (rootElement) => { - const elements = Array.from(rootElement.querySelectorAll("[href]")).filter( - (element) => !["A", "LINK"].includes(element.tagName), - ); - elements.forEach((element) => setupEventHandlers(element)); -}; - -document.addEventListener("DOMContentLoaded", () => { - log("log", "DOMContentLoaded event triggered"); - attachHyperwaveHandlers(document); - - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - log("log", "MutationObserver detected new element:", node); - attachHyperwaveHandlers(node); - } - }); - }); - }); - - observer.observe(document.body, { childList: true, subtree: true }); -}); +const hyperwaveConfig={debug:!0,defaultMethod:"GET",defaultDebounceDelay:50,defaultLimit:32,defaultTotalItems:2048,logPrefix:"hyperwave:"},log=(e,...t)=>{hyperwaveConfig.debug&&console[e](`${hyperwaveConfig.logPrefix}`,...t)},createDebouncedFunction=(e,t)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout((()=>e.apply(this,n)),t)}},fetchContent=async(e,t={})=>{const r={method:t.method||hyperwaveConfig.defaultMethod,headers:{Accept:"text/html",...t.headers},...t};try{const t=await fetch(e,r);if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const n=await t.text();return log("log",`Content fetched from ${e}`,n.length),n}catch(t){return log("error",`Error fetching from ${e}:`,t),null}},updateTargetElement=(e,t,r="replace")=>{if("replace"===r)e.innerHTML=t;else if("append"===r){const r=document.createElement("div");for(r.innerHTML=t;r.firstChild;)e.appendChild(r.firstChild)}attachHyperwaveHandlers(e)},buildPaginationUrl=(e,t,r)=>{const n=new URL(e.getAttribute("href"),window.location.origin);return n.searchParams.set("offset",t),n.searchParams.set("limit",r),n.toString()},handlePagination=(e,t)=>{let r=parseInt(e.getAttribute("offset")||"0",10);const n=parseInt(e.getAttribute("limit")||hyperwaveConfig.defaultLimit,10),o=parseInt(e.getAttribute("total")||hyperwaveConfig.defaultTotalItems,10),a=e.getAttribute("update-mode")||"replace";let l=!1;return async()=>{if(l||r>=o)return;l=!0;const i=buildPaginationUrl(e,r,n),d=await fetchContent(i,t);if(d){const t=document.querySelector(e.getAttribute("target"));updateTargetElement(t,d,a),r+=n,e.setAttribute("offset",r),log("log",`Content updated. New offset set: ${r}`)}l=!1}},setupEventHandlers=e=>{const t=e.getAttribute("method")||hyperwaveConfig.defaultMethod,r=e.getAttribute("trigger")||"click",n=parseInt(e.getAttribute("debounce")||hyperwaveConfig.defaultDebounceDelay,10),o=parseInt(e.getAttribute("scroll-threshold")||"300",10),a={method:t.toUpperCase(),headers:{Accept:"text/html"}};log("log",`Setting up event handlers: method=${t}, trigger=${r}, debounceDelay=${n}, scrollThreshold=${o}`);const l=handlePagination(e,a),i=createDebouncedFunction((async e=>{if("scroll"!==r&&e.preventDefault(),"scroll"===r){if(!(document.body.offsetHeight-(window.innerHeight+window.scrollY)<=o))return}await l()}),n);if("DOMContentLoaded"===r)l();else{("scroll"===r?window:e).addEventListener(r,i),e._hyperwaveHandler=i,"scroll"===r&&l()}},attachHyperwaveHandlers=e=>{Array.from(e.querySelectorAll("[href]")).filter((e=>!["A","LINK"].includes(e.tagName))).forEach((e=>setupEventHandlers(e)))};document.addEventListener("DOMContentLoaded",(()=>{log("log","DOMContentLoaded event triggered"),attachHyperwaveHandlers(document);new MutationObserver((e=>{e.forEach((e=>{e.addedNodes.forEach((e=>{e.nodeType===Node.ELEMENT_NODE&&(log("log","MutationObserver detected new element:",e),attachHyperwaveHandlers(e))}))}))})).observe(document.body,{childList:!0,subtree:!0})})); \ No newline at end of file diff --git a/src/util/hyperwave.js b/src/util/hyperwave.js new file mode 100644 index 0000000..90087f9 --- /dev/null +++ b/src/util/hyperwave.js @@ -0,0 +1,232 @@ +/** + * Configuration object for hyperwave. + * @typedef {Object} HyperwaveConfig + * @property {boolean} debug - If true, enables debug mode which logs additional information. + * @property {string} defaultMethod - The default HTTP method used for fetching content. + * @property {number} defaultDebounceDelay - The default delay in milliseconds for debouncing events. + * @property {number} defaultLimit - The default number of items to fetch per pagination request. + * @property {number} defaultTotalItems - The default total number of items available for pagination. + * @property {string} logPrefix - Prefix to use for log messages to distinguish them. + */ + +/** @type {HyperwaveConfig} */ +const hyperwaveConfig = { + debug: true, + defaultMethod: "GET", + defaultDebounceDelay: 50, + defaultLimit: 32, + defaultTotalItems: 2048, + logPrefix: "hyperwave:", +}; + +/** + * Logs messages to the console if debug mode is enabled. + * @param {string} level - The log level, e.g., "log" or "error". + * @param {...any} messages - The messages or objects to log. + */ +const log = (level, ...messages) => { + if (hyperwaveConfig.debug) { + console[level](`${hyperwaveConfig.logPrefix}`, ...messages); + } +}; + +/** + * Creates a debounced version of the provided function. + * @param {Function} func - The function to debounce. + * @param {number} delay - The delay in milliseconds to wait before invoking the function. + * @returns {Function} - The debounced function. + */ +const createDebouncedFunction = (func, delay) => { + let timeoutId; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => func.apply(this, args), delay); + }; +}; + +/** + * Fetches content from a specified URL using the provided options. + * @param {string} url - The URL to fetch content from. + * @param {Object} [fetchOptions={}] - Options to customize the fetch request. + * @param {string} [fetchOptions.method] - The HTTP method to use. + * @param {Object} [fetchOptions.headers] - Headers to include in the request. + * @returns {Promise} - A promise that resolves to the fetched content or null if an error occurs. + */ +const fetchContent = async (url, fetchOptions = {}) => { + const options = { + method: fetchOptions.method || hyperwaveConfig.defaultMethod, + headers: { Accept: "text/html", ...fetchOptions.headers }, + ...fetchOptions, + }; + + try { + const response = await fetch(url, options); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + const content = await response.text(); + log("log", `Content fetched from ${url}`, content.length); + return content; + } catch (error) { + log("error", `Error fetching from ${url}:`, error); + return null; + } +}; + +/** + * Updates a target element with new content, either replacing or appending based on mode. + * @param {HTMLElement} targetElement - The element to update with new content. + * @param {string} content - The HTML content to update. + * @param {string} mode - The mode of updating, either "replace" or "append". + */ +const updateTargetElement = (targetElement, content, mode = "replace") => { + if (mode === "replace") { + targetElement.innerHTML = content; + } else if (mode === "append") { + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = content; + while (tempDiv.firstChild) { + targetElement.appendChild(tempDiv.firstChild); + } + } + attachHyperwaveHandlers(targetElement); +}; + +/** + * Constructs a URL for pagination with the given offset and limit. + * @param {HTMLElement} triggerElement - The element that triggered the pagination. + * @param {number} offset - The offset for pagination. + * @param {number} limit - The limit for pagination. + * @returns {string} - The constructed URL for pagination. + */ +const buildPaginationUrl = (triggerElement, offset, limit) => { + const url = new URL( + triggerElement.getAttribute("href"), + window.location.origin, + ); + url.searchParams.set("offset", offset); + url.searchParams.set("limit", limit); + return url.toString(); +}; + +/** + * Handles pagination by fetching the next set of content and updating the target element. + * @param {HTMLElement} triggerElement - The element that triggered the pagination. + * @param {Object} fetchOptions - Options to customize the fetch request. + * @returns {Function} - A function to handle fetching the next page of content. + */ +const handlePagination = (triggerElement, fetchOptions) => { + let offset = parseInt(triggerElement.getAttribute("offset") || "0", 10); + const limit = parseInt( + triggerElement.getAttribute("limit") || hyperwaveConfig.defaultLimit, + 10, + ); + const totalItems = parseInt( + triggerElement.getAttribute("total") || hyperwaveConfig.defaultTotalItems, + 10, + ); + const mode = triggerElement.getAttribute("update-mode") || "replace"; + + let isFetching = false; + + return async () => { + if (isFetching || offset >= totalItems) return; + + isFetching = true; + const url = buildPaginationUrl(triggerElement, offset, limit); + const content = await fetchContent(url, fetchOptions); + if (content) { + const target = document.querySelector( + triggerElement.getAttribute("target"), + ); + updateTargetElement(target, content, mode); + offset += limit; + triggerElement.setAttribute("offset", offset); + log("log", `Content updated. New offset set: ${offset}`); + } + isFetching = false; + }; +}; + +/** + * Sets up event handlers for the given trigger element based on its attributes. + * @param {HTMLElement} triggerElement - The element to set up event handlers for. + */ +const setupEventHandlers = (triggerElement) => { + const method = + triggerElement.getAttribute("method") || hyperwaveConfig.defaultMethod; + const trigger = triggerElement.getAttribute("trigger") || "click"; + const debounceDelay = parseInt( + triggerElement.getAttribute("debounce") || + hyperwaveConfig.defaultDebounceDelay, + 10, + ); + const scrollThreshold = parseInt( + triggerElement.getAttribute("scroll-threshold") || "300", + 10, + ); + + const fetchOptions = { + method: method.toUpperCase(), + headers: { Accept: "text/html" }, + }; + + log( + "log", + `Setting up event handlers: method=${method}, trigger=${trigger}, debounceDelay=${debounceDelay}, scrollThreshold=${scrollThreshold}`, + ); + + const loadNextPage = handlePagination(triggerElement, fetchOptions); + + const eventHandler = createDebouncedFunction(async (event) => { + if (trigger !== "scroll") event.preventDefault(); + + if (trigger === "scroll") { + const nearBottom = + document.body.offsetHeight - (window.innerHeight + window.scrollY) <= + scrollThreshold; + if (!nearBottom) return; + } + + await loadNextPage(); + }, debounceDelay); + + if (trigger === "DOMContentLoaded") { + loadNextPage(); + } else { + const targetElement = trigger === "scroll" ? window : triggerElement; + targetElement.addEventListener(trigger, eventHandler); + triggerElement._hyperwaveHandler = eventHandler; + + if (trigger === "scroll") { + loadNextPage(); + } + } +}; + +/** + * Attaches hyperwave event handlers to all elements within a given root element. + * @param {HTMLElement} rootElement - The root element to search for trigger elements. + */ +const attachHyperwaveHandlers = (rootElement) => { + const elements = Array.from(rootElement.querySelectorAll("[href]")).filter( + (element) => !["A", "LINK"].includes(element.tagName), + ); + elements.forEach((element) => setupEventHandlers(element)); +}; + +document.addEventListener("DOMContentLoaded", () => { + log("log", "DOMContentLoaded event triggered"); + attachHyperwaveHandlers(document); + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + log("log", "MutationObserver detected new element:", node); + attachHyperwaveHandlers(node); + } + }); + }); + }); + + observer.observe(document.body, { childList: true, subtree: true }); +});