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

Merge remote-tracking branch 'origin/work'

+82 -181
+2
readme
··· 28 28 29 29 bro idk what this is anymore but im a fan of it and will keep going 30 30 31 + i really gotta stop using Node.wraps... 32 + that literally means that my implementation around it is inadequate
+7 -1
src/domlink.ts
··· 1 - let _LCounter = 0; // is there a point to this? IDK but it feels useful. 1 + let _LCounter = 0; 2 + const NodeMap = new WeakMap<HTMLElement, Node>(); 2 3 3 4 /** Core wrapper for {@link HTMLElement DOM elements}. */ 4 5 export class Node { ··· 59 60 class(cls: string) { 60 61 this.wraps.classList.add(cls); 61 62 return this; 63 + } 64 + /** Hopefully, this points at the parent element. */ 65 + get parent(): Node | undefined{ 66 + if (this.wraps.parentElement == null) return undefined; 67 + return NodeMap.get(this.wraps.parentElement); 62 68 } 63 69 } 64 70 type NodeEquivalent = (Node | string);
-166
src/issuesearch.ts
··· 1 - import { Body, Button, Input, Label, Link, Node, Row, Table, TableCell, TableRow } from "./domlink.ts"; 2 - import { AtURI, AtURIString, GetRecord, ListRecords, XQuery } from "./support/atproto.ts"; 3 - import { Issue, Repo } from "./support/tangled.ts"; 4 - import * as Constellation from "./support/constellation.ts"; 5 - import { MiniDoc } from "./support/slingshot.ts"; 6 - Body.with(new Link("TypeScript source here!").to("https://tangled.sh/@hotsocket.fyi/domlink/blob/main/src/issuesearch.ts")); 7 - 8 - type recordListingItem<T> = { 9 - uri: string; 10 - cid: string; 11 - value: T; 12 - }; 13 - 14 - async function timeout<T>(time: number, promise: Promise<T>): Promise<T> { 15 - const result = await Promise.race([new Promise<null>(r=>setTimeout(r, time, null)), promise]); 16 - if (result == null) { 17 - throw new Error("timeout"); 18 - } 19 - return result; 20 - } 21 - 22 - type issueItem = ({handle: string;issue: Issue}); 23 - let allIssues:issueItem[] = []; 24 - let currentRepoLink = ""; // used to build issue links 25 - async function getIssues(repo: string) { 26 - // pick out repo info 27 - const repoSplat = repo.split("//"); 28 - let repoOwner: string; 29 - let repoName: string; 30 - if (repoSplat.length == 1) { 31 - const repoHalves = repo.split("/"); 32 - if (repoHalves.length == 1) throw new Error("invalid repo string"); 33 - repoOwner = repoHalves[0]; 34 - repoName = repoHalves[1]; 35 - } else { 36 - if (repoSplat[0] == "https:") { 37 - const repoUrlSplat = new URL(repo).pathname.split("/"); 38 - // [ "", "@tangled.sh", "core" ] 39 - if (repoUrlSplat.length < 2) throw new Error("invalid repo url"); 40 - repoOwner = repoUrlSplat[1]; 41 - repoName = repoUrlSplat[2]; 42 - } else { 43 - throw new Error("unknown repo uri scheme"); 44 - } 45 - } 46 - if (repoOwner.startsWith("@")) repoOwner = repoOwner.substring(1); 47 - currentRepoLink = `https://tangled.sh/@${repoOwner}/${repoName}`; 48 - statusText.text = "resolving owner did"; 49 - const repoOwnerDoc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", {identifier: repoOwner}); 50 - statusText.text = "finding repository"; 51 - const ownedRepos = await ListRecords<Repo>(repoOwnerDoc.did, "sh.tangled.repo", repoOwnerDoc.pds); 52 - const repoRecord = ownedRepos.records.find(x=>x.value.name == repoName)!; 53 - statusText.text = `finding issues...`; 54 - const allIssueRefs = await timeout(10000, Constellation.getLinks(AtURI.fromString(repoRecord.uri as AtURIString), "sh.tangled.repo.issue", ".repo")); 55 - let issueinc = 0; 56 - allIssues = (await Promise.all(allIssueRefs.map(async (issueRef) => { 57 - let issue: Issue; 58 - try { 59 - issue = (await timeout(2000, GetRecord<Issue>(issueRef))).value; 60 - } catch { return null; } 61 - if (issue == null) { 62 - console.log(`getRecord timed out: ${issueRef.toString()}`); 63 - return null; 64 - } 65 - let doc; 66 - try { 67 - doc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", { identifier: issue.owner }); 68 - } catch { return null; } 69 - statusText.text = `retrieved issue ${++issueinc}/${allIssueRefs.length} (${issue.issueId})`; 70 - return { 71 - handle: doc.handle, 72 - issue: issue 73 - }; 74 - }))).filter(x=>x as issueItem).sort((a,b)=>{ 75 - let x = 0; 76 - if (a && b) { 77 - x = b.issue.issueId-a.issue.issueId 78 - } 79 - return x; 80 - }) as issueItem[]; 81 - statusText.text = `got ${allIssues.length}/${allIssueRefs.length} issues!`; 82 - } 83 - function renderIssues(issues:issueItem[]) { 84 - const view_issues_list = new Table().add( 85 - new TableRow().with( 86 - new TableCell().add("Handle"), 87 - new TableCell().add("Issue#"), 88 - new TableCell().add("Title") 89 - ) 90 - ); 91 - issues.forEach(issue =>{ 92 - if (issue) { 93 - const view_issue_row = new TableRow(); 94 - view_issue_row.with( 95 - new TableCell().add(new Link(issue.handle).to(`https://tangled.sh/@${issue.handle}`)), 96 - new TableCell().add(new Link(issue.issue.issueId.toString()).to(`${currentRepoLink}/issues/${issue.issue.issueId}`)), 97 - new TableCell().add(issue.issue.title) 98 - ); 99 - view_issues_list.add(view_issue_row); 100 - } 101 - }); 102 - return view_issues_list; 103 - } 104 - 105 - 106 - const repoUrl = new Input(); 107 - let last: Node | null = null; 108 - const statusText = new Label("waiting"); 109 - const runButton = new Button("GO!",async ()=>{ 110 - (runButton.wraps as HTMLButtonElement).disabled = true; 111 - if (last) { 112 - try { 113 - Body.delete(last); 114 - } finally { 115 - last = null; 116 - } 117 - } 118 - allIssues = []; 119 - searchRow.style(x=>x.display="none"); 120 - try { 121 - await getIssues(repoUrl.value); 122 - last = renderIssues(allIssues); 123 - searchRow.style(x=>x.removeProperty("display")); 124 - } catch (e) { 125 - last = new Label("Failed"); 126 - console.error(e); 127 - } 128 - Body.add(last); 129 - searchInput.value = ""; 130 - (runButton.wraps as HTMLButtonElement).disabled = false; 131 - }); 132 - 133 - 134 - 135 - const searchInput = new Input(); 136 - searchInput.watch((search)=>{ 137 - if (allIssues.length > 0) { 138 - if (last) { 139 - Body.delete(last); 140 - } 141 - search = search.toLowerCase(); 142 - last = renderIssues(allIssues.filter((item)=>{ 143 - return item.issue.issueId == Number(item) || 144 - search.split(" ").every((word)=> ( 145 - item.handle.toLowerCase().includes(word) || 146 - item.issue.title.toLowerCase().includes(word) || 147 - item.issue.body.toLowerCase().includes(word))) 148 - })); 149 - Body.add(last); 150 - } 151 - }); 152 - const searchRow = new Row().with( 153 - "search", 154 - searchInput 155 - ); 156 - searchRow.style(x=>x.display="none"); 157 - 158 - 159 - 160 - Body.add(new Row().with( 161 - "List issues for repo: ", 162 - repoUrl, 163 - runButton, 164 - statusText 165 - )); 166 - Body.add(searchRow);
+1 -1
src/support/atproto.ts
··· 64 64 65 65 export type RecordResponse<T> = { 66 66 cid: string; 67 - uri: string; 67 + uri: AtURIString; 68 68 value: T; 69 69 } 70 70
+72 -6
src/support/tangled.ts
··· 1 + import * as x from "../extras.ts"; 2 + import { AtURI, ListRecords, XQuery } from "./atproto.ts"; 3 + import { getUriRecord, MiniDoc } from "./slingshot.ts"; 4 + import * as Constellation from "./constellation.ts"; 5 + 1 6 export type Issue = { 2 7 body: string; 3 8 createdAt: string; ··· 6 11 repo: string; 7 12 title: string; 8 13 }; 9 - export type Repo = { 10 - knot: string; 11 - name: string; 12 - owner: string; 13 - createdAt: string; 14 - }; 14 + export type RepoRef = { 15 + readonly owner: string; 16 + readonly name: string; 17 + }; 18 + type RepoCommon = { 19 + readonly description: string; 20 + readonly name: string; 21 + readonly knot: string; 22 + readonly spindle: string; 23 + readonly createdAt: string; 24 + } 25 + type RawRepo = RepoCommon & { 26 + readonly owner: string; 27 + }; 28 + export type Repo = RepoCommon & { 29 + readonly owner: MiniDoc; // The record itself returns a DID, but a MiniDoc is awfully handy. 30 + readonly uri: AtURI; 31 + }; 32 + 33 + export async function GetRepo(ref: RepoRef): Promise<Repo> { 34 + const repoOwnerDoc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", {identifier: ref.owner}); 35 + const ownedRepos = await ListRecords<RawRepo>(repoOwnerDoc.did, "sh.tangled.repo", repoOwnerDoc.pds); 36 + const repoRecord = ownedRepos.records.find(x=>x.value.name == ref.name)!; 37 + return { 38 + ...repoRecord.value, 39 + owner: repoOwnerDoc, 40 + uri: AtURI.fromString(repoRecord.uri) 41 + }; 42 + } 43 + export async function GetIssues(repo: Repo, timeout: number = 10000) { 44 + const allIssueRefs = await x.timeout(timeout, Constellation.links(repo.uri, "sh.tangled.repo.issue", ".repo")); 45 + const issues = allIssueRefs.map(async (uri)=>{ 46 + try { 47 + return (await x.timeout(timeout/5,getUriRecord<Issue>(uri))).value; 48 + } catch { 49 + console.log(`issue timed out: ${uri.toString()}`); 50 + return null; 51 + } 52 + }).filter(x=>x!=null) as unknown as Issue[]; // despite filtering them out, typescript still thinks there could be nulls 53 + return issues.sort((a,b)=>b.issueId-a.issueId); 54 + } 55 + 56 + /** Takes a string like `tangled.sh/core` or `https://tangled.sh/@tangled.sh/core` and returns a {@link RepoRef} */ 57 + export function StringRepoRef(repo: string): RepoRef { 58 + // pick out repo info 59 + const repoSplat = repo.split("//"); 60 + let repoOwner: string; 61 + let repoName: string; 62 + if (repoSplat.length == 1) { 63 + const repoHalves = repo.split("/"); 64 + if (repoHalves.length == 1) throw new Error("invalid repo string"); 65 + repoOwner = repoHalves[0]; 66 + repoName = repoHalves[1]; 67 + } else { 68 + if (repoSplat[0] == "https:") { 69 + const repoUrlSplat = new URL(repo).pathname.split("/"); 70 + // [ "", "@tangled.sh", "core" ] 71 + if (repoUrlSplat.length < 2) throw new Error("invalid repo url"); 72 + repoOwner = repoUrlSplat[1]; 73 + repoName = repoUrlSplat[2]; 74 + } else { 75 + throw new Error("unknown repo uri scheme"); 76 + } 77 + } 78 + if (repoOwner.startsWith("@")) repoOwner = repoOwner.substring(1); 79 + return {owner: repoOwner, name: repoName}; 80 + }
-7
src/windows/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 } from "../domlink.ts"; 4 - 5 - export class IssueSearch extends Column { 6 - 7 - }