decentralized and customizable links page on top of atproto ligo.at
atproto link-in-bio python uv
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

configure host, debounce and abort http requests

+42 -9
+42 -9
src/static/actor-typeahead.js
··· 6 6 * Copyright (c) 2026 ligo.at contributors 7 7 * 8 8 * Changes 9 + * 2026-03-17 10 + * Fix: Abort previous HTTP request before starting the next. 11 + * New: Add 250ms debounce to oninput. 12 + * New: `host` attribute can be set to "location" to use window.location. 9 13 * 2026-03-15 10 14 * Fix: Correctly count rows of actors. 11 15 * Fix: Disable browser autocomplete to not collide with the typeahead. ··· 122 126 } 123 127 124 128 /** 125 - * @attribute {string} [host] - The host to which to make the typeahead API call. 129 + * @attribute {string} [host] - The host to which to make the typeahead API call. If set to "location" it uses window.location. 126 130 * @attribute {number} [rows] - The maximum number of rows to display in the dropdown. 127 131 * 128 132 * @csspart menu - The dropdown menu. ··· 174 178 #index = -1; 175 179 /** @type {HTMLInputElement} */ 176 180 #input; 181 + /** @type {number | undefined} */ 182 + #oninputTimeoutId = undefined; 177 183 #pressed = false; 184 + /** @type {AbortController | undefined} */ 185 + #controller = undefined; 178 186 179 187 constructor() { 180 188 super(); ··· 201 209 return rows; 202 210 } 203 211 212 + /** @type {string} */ 213 + get #host() { 214 + const host = this.getAttribute("host"); 215 + return host === "location" 216 + ? window.location 217 + : host || "https://public.api.bsky.app"; 218 + } 219 + 204 220 /** @param {Event} evt */ 205 221 handleEvent(evt) { 206 222 switch (evt.type) { ··· 283 299 } 284 300 285 301 /** @param {InputEvent & { target: HTMLInputElement }} evt */ 286 - async #oninput(evt) { 302 + #oninput(evt) { 303 + clearTimeout(this.#oninputTimeoutId); 304 + this.#oninputTimeoutId = setTimeout( 305 + this.#oninputDebounced.bind(this), 306 + 250, 307 + evt, 308 + ); 309 + } 310 + 311 + /** @param {InputEvent & { target: HTMLInputElement }} evt */ 312 + async #oninputDebounced(evt) { 287 313 const query = evt.target?.value; 288 314 if (!query) { 289 315 this.#actors = []; ··· 291 317 return; 292 318 } 293 319 294 - const host = this.getAttribute("host") ?? "https://public.api.bsky.app"; 295 - const url = new URL("xrpc/app.bsky.actor.searchActorsTypeahead", host); 320 + const url = new URL( 321 + "xrpc/app.bsky.actor.searchActorsTypeahead", 322 + this.#host, 323 + ); 296 324 url.searchParams.set("q", query); 297 325 url.searchParams.set("limit", `${this.#rows}`); 298 326 299 - const res = await fetch(url); 300 - const json = await res.json(); 301 - this.#actors = json.actors; 302 - this.#index = -1; 303 - this.#render(); 327 + this.#controller?.abort(); 328 + this.#controller = new AbortController(); 329 + 330 + try { 331 + const res = await fetch(url, { signal: this.#controller.signal }); 332 + const json = await res.json(); 333 + this.#actors = json.actors; 334 + this.#index = -1; 335 + this.#render(); 336 + } catch {} 304 337 } 305 338 306 339 /** @param {Event} evt */