https://domlink.deployments.hotsocket.fyi/

oh god its git happening

+92
+92
src/windows/tangled/issuesearch.ts
···
··· 1 + // rewrite of @/issuesearch.ts to be a self-contained class that can be shoved into a window easily 2 + 3 + import { Column, Container, Node, Input, Link, Row, Table, TableCell, TableRow, Button } from "@/domlink.ts"; 4 + import { ListRecords } from "@/support/atproto.ts"; 5 + import { getUriRecord, resolveMiniDoc } from "@/support/slingshot.ts"; 6 + import { Issue, Repo, RepoRecord } from "@/support/tangled.ts"; 7 + import * as Constellation from "@/support/constellation.ts"; 8 + import { timeout } from "@/extras.ts"; 9 + 10 + export class IssueSearch extends Column { 11 + repoInput = new Input(); 12 + searchInput = new Input(); 13 + searchRow = new Row().with("Search: ", this.searchInput).style(s=>s.display="none"); 14 + renderOut: Node = new Container().with("<nothing yet>"); 15 + repoInfo: RepoRecord = {} as RepoRecord; 16 + issues: Issue[] = []; 17 + constructor() { 18 + super(); 19 + this.class("IssueSearch"); 20 + this.with( 21 + new Row().with("Repo: ", this.repoInput, new Button("Get", async ()=>{await this.getIssues(this.repoInput.value);})), 22 + this.searchRow, 23 + this.renderOut 24 + ); 25 + this.searchInput.watch((search)=>{ 26 + if (this.issues.length > 0) { 27 + this.render(this.issues.filter((issue)=>{ 28 + return issue.issueId == Number(search) || 29 + search.split(" ").every((word)=> ( 30 + issue.owner.toLowerCase().includes(word) || 31 + issue.title.toLowerCase().includes(word) || 32 + issue.body.toLowerCase().includes(word))) 33 + })); 34 + } 35 + }); 36 + } 37 + async getIssues(repo: string) { 38 + this.repoInfo = IssueSearch.parseRepoInfo(repo); 39 + const doc = await resolveMiniDoc(this.repoInfo.owner); 40 + const ownedRepos = await ListRecords<Repo>(doc.did, "sh.tangled.repo", doc.pds); 41 + const repoRecord = ownedRepos.records.find(x=>x.value.name == this.repoInfo.name)!; 42 + const links = await Constellation.getLinks(repoRecord.uri, "sh.tangled.repo.issue", ".repo"); 43 + this.issues = (await Promise.all(links.map(link => { 44 + try { 45 + return timeout(2000, getUriRecord<Issue>(link)); 46 + } catch { 47 + return null; 48 + } 49 + }))).filter(x=>x).map(x=>x!.value).sort((a,b)=>a.issueId-b.issueId); 50 + this.searchRow.style(s=>s.removeProperty("display")); 51 + this.render(this.issues); 52 + } 53 + render(issues: Issue[]) { 54 + this.remove(this.renderOut); 55 + this.renderOut = new Table().with( 56 + new TableRow().with( 57 + new TableCell().with("Handle"), 58 + new TableCell().with("Issue#"), 59 + new TableCell().with("Title")), 60 + ...issues.map(issue => new TableRow().with( 61 + new TableCell().with(new Link(issue.owner).to(`https://tangled.sh/@${issue.owner}`)), 62 + new TableCell().with(new Link(issue.issueId.toString()).to(`https://tangled.sh/@${this.repoInfo.owner}/${this.repoInfo.name}/issues/${issue.issueId}`)), 63 + new TableCell().with(issue.title) 64 + )) 65 + ); 66 + this.with(this.renderOut); 67 + } 68 + 69 + static parseRepoInfo(repo: string) { 70 + const repoSplat = repo.split("//"); 71 + let owner: string; 72 + let name: string; 73 + if (repoSplat.length == 1) { 74 + const repoHalves = repo.split("/"); 75 + if (repoHalves.length == 1) throw new Error("invalid repo string"); 76 + owner = repoHalves[0]; 77 + name = repoHalves[1]; 78 + } else { 79 + if (repoSplat[0] == "https:") { 80 + const repoUrlSplat = new URL(repo).pathname.split("/"); 81 + // [ "", "@tangled.sh", "core" ] 82 + if (repoUrlSplat.length < 2) throw new Error("invalid repo url"); 83 + owner = repoUrlSplat[1]; 84 + name = repoUrlSplat[2]; 85 + } else { 86 + throw new Error("unknown repo uri scheme"); 87 + } 88 + } 89 + if (owner.startsWith("@")) owner = owner.substring(1); 90 + return { owner, name }; 91 + } 92 + }