the home of serif.blue
5
fork

Configure Feed

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

feat: use public bluesky api and add auto complete on users

+154 -17
+154 -17
bluesky-community-verifications.user.js
··· 121 121 122 122 while (hasMore) { 123 123 const url = cursor 124 - ? `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${user}&collection=app.bsky.graph.verification&cursor=${cursor}` 125 - : `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${user}&collection=app.bsky.graph.verification`; 124 + ? `https://public.api.bsky.app/xrpc/com.atproto.repo.listRecords?repo=${user}&collection=app.bsky.graph.verification&cursor=${cursor}` 125 + : `https://public.api.bsky.app/xrpc/com.atproto.repo.listRecords?repo=${user}&collection=app.bsky.graph.verification`; 126 126 127 127 const response = await fetch(url); 128 128 const data = await response.json(); ··· 472 472 }); 473 473 } 474 474 }; 475 + 476 + const searchUsers = async (searchQuery) => { 477 + if (!searchQuery || searchQuery.length < 2) return []; 478 + 479 + try { 480 + const response = await fetch( 481 + `https://public.api.bsky.app/xrpc/app.bsky.actor.searchActors?term=${encodeURIComponent(searchQuery)}&limit=5`, 482 + ); 483 + const data = await response.json(); 484 + return data.actors || []; 485 + } catch (error) { 486 + console.error("Error searching for users:", error); 487 + return []; 488 + } 489 + }; 490 + 491 + // Function to create and show the autocomplete dropdown 492 + const showAutocompleteResults = (results, inputElement) => { 493 + // Remove existing dropdown if any 494 + const existingDropdown = document.getElementById("autocomplete-dropdown"); 495 + if (existingDropdown) existingDropdown.remove(); 496 + 497 + if (results.length === 0) return; 498 + 499 + // Create dropdown 500 + const dropdown = document.createElement("div"); 501 + dropdown.id = "autocomplete-dropdown"; 502 + dropdown.style.cssText = ` 503 + position: absolute; 504 + background-color: #2A2E3D; 505 + border: 1px solid #444; 506 + border-radius: 4px; 507 + box-shadow: 0 4px 8px rgba(0,0,0,0.2); 508 + max-height: 300px; 509 + overflow-y: auto; 510 + width: ${inputElement.offsetWidth}px; 511 + z-index: 10002; 512 + margin-top: 2px; 513 + `; 514 + 515 + // Position dropdown below input 516 + const inputRect = inputElement.getBoundingClientRect(); 517 + dropdown.style.left = `${inputRect.left}px`; 518 + dropdown.style.top = `${inputRect.bottom}px`; 519 + 520 + // Add results to dropdown 521 + for (const user of results) { 522 + const userItem = document.createElement("div"); 523 + userItem.className = "autocomplete-item"; 524 + userItem.style.cssText = ` 525 + display: flex; 526 + align-items: center; 527 + padding: 8px 12px; 528 + cursor: pointer; 529 + color: white; 530 + border-bottom: 1px solid #444; 531 + `; 532 + userItem.onmouseover = () => { 533 + userItem.style.backgroundColor = "#3A3F55"; 534 + }; 535 + userItem.onmouseout = () => { 536 + userItem.style.backgroundColor = ""; 537 + }; 538 + 539 + // Add profile picture 540 + const avatar = document.createElement("img"); 541 + avatar.src = user.avatar || "https://bsky.app/static/default-avatar.png"; 542 + avatar.style.cssText = ` 543 + width: 32px; 544 + height: 32px; 545 + border-radius: 50%; 546 + margin-right: 10px; 547 + object-fit: cover; 548 + `; 549 + 550 + // Add user info 551 + const userInfo = document.createElement("div"); 552 + userInfo.style.cssText = ` 553 + display: flex; 554 + flex-direction: column; 555 + `; 556 + 557 + const displayName = document.createElement("div"); 558 + displayName.textContent = user.displayName || user.handle; 559 + displayName.style.fontWeight = "bold"; 560 + 561 + const handle = document.createElement("div"); 562 + handle.textContent = user.handle; 563 + handle.style.fontSize = "0.8em"; 564 + handle.style.opacity = "0.8"; 565 + 566 + userInfo.appendChild(displayName); 567 + userInfo.appendChild(handle); 568 + 569 + userItem.appendChild(avatar); 570 + userItem.appendChild(userInfo); 571 + 572 + // Handle click on user item 573 + userItem.addEventListener("click", () => { 574 + inputElement.value = user.handle; 575 + dropdown.remove(); 576 + }); 577 + 578 + dropdown.appendChild(userItem); 579 + } 580 + 581 + document.body.appendChild(dropdown); 582 + 583 + // Close dropdown when clicking outside 584 + document.addEventListener("click", function closeDropdown(e) { 585 + if (e.target !== inputElement && !dropdown.contains(e.target)) { 586 + dropdown.remove(); 587 + document.removeEventListener("click", closeDropdown); 588 + } 589 + }); 590 + }; 591 + 475 592 // Function to create the settings modal 476 593 const createSettingsModal = () => { 477 594 // Create modal container ··· 508 625 // Create input form 509 626 const form = document.createElement("div"); 510 627 form.innerHTML = ` 511 - <p>Add Bluesky handles you trust:</p> 512 - <div style="display: flex; margin-bottom: 15px;"> 513 - <input id="trustedUserInput" type="text" placeholder="username.bsky.social" style="flex: 1; padding: 8px; margin-right: 10px; border: 1px solid #ccc; border-radius: 4px;"> 514 - <button id="addTrustedUserBtn" style="background-color: #2D578D; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer;">Add</button> 515 - </div> 516 - `; 628 + <p>Add Bluesky handles you trust:</p> 629 + <div style="display: flex; margin-bottom: 15px; position: relative;"> 630 + <input id="trustedUserInput" type="text" placeholder="Search for a user..." style="flex: 1; padding: 8px; margin-right: 10px; border: 1px solid #ccc; border-radius: 4px;"> 631 + <button id="addTrustedUserBtn" style="background-color: #2D578D; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer;">Add</button> 632 + </div> 633 + `; 517 634 518 635 // Create trusted users list 519 636 const trustedUsersList = document.createElement("div"); ··· 574 691 575 692 // Add to document 576 693 document.body.appendChild(settingsModal); 694 + 695 + const userInput = document.getElementById("trustedUserInput"); 696 + 697 + // Add input event for autocomplete 698 + let debounceTimeout; 699 + userInput.addEventListener("input", (e) => { 700 + clearTimeout(debounceTimeout); 701 + debounceTimeout = setTimeout(async () => { 702 + const searchQuery = e.target.value.trim(); 703 + if (searchQuery.length >= 2) { 704 + const results = await searchUsers(searchQuery); 705 + showAutocompleteResults(results, userInput); 706 + } else { 707 + const dropdown = document.getElementById("autocomplete-dropdown"); 708 + if (dropdown) dropdown.remove(); 709 + } 710 + }, 300); // Debounce for 300ms 711 + }); 577 712 578 713 // Event listeners 579 714 closeButton.addEventListener("click", () => { ··· 588 723 addTrustedUser(handle); 589 724 input.value = ""; 590 725 updateTrustedUsersList(); 726 + 727 + // Remove dropdown if present 728 + const dropdown = document.getElementById("autocomplete-dropdown"); 729 + if (dropdown) dropdown.remove(); 591 730 } 592 731 }; 593 732 ··· 597 736 .addEventListener("click", addUserFromInput); 598 737 599 738 // Add keydown event to input for Enter key 600 - document 601 - .getElementById("trustedUserInput") 602 - .addEventListener("keydown", (e) => { 603 - if (e.key === "Enter") { 604 - e.preventDefault(); 605 - addUserFromInput(); 606 - } 607 - }); 739 + userInput.addEventListener("keydown", (e) => { 740 + if (e.key === "Enter") { 741 + e.preventDefault(); 742 + addUserFromInput(); 743 + } 744 + }); 608 745 609 746 // Close modal when clicking outside 610 747 settingsModal.addEventListener("click", (e) => { ··· 645 782 646 783 // Fetch user profile data 647 784 fetch( 648 - `https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=${handle}&collection=app.bsky.actor.profile&rkey=self`, 785 + `https://public.api.bsky.app/xrpc/com.atproto.repo.getRecord?repo=${handle}&collection=app.bsky.actor.profile&rkey=self`, 649 786 ) 650 787 .then((response) => response.json()) 651 788 .then((data) => {