···2829bro idk what this is anymore but im a fan of it and will keep going
3000
···2829bro idk what this is anymore but im a fan of it and will keep going
3031+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.
023/** Core wrapper for {@link HTMLElement DOM elements}. */
4export class Node {
···59 class(cls: string) {
60 this.wraps.classList.add(cls);
61 return this;
0000062 }
63}
64type NodeEquivalent = (Node | string);
···1+let _LCounter = 0;
2+const NodeMap = new WeakMap<HTMLElement, Node>();
34/** Core wrapper for {@link HTMLElement DOM elements}. */
5export class Node {
···60 class(cls: string) {
61 this.wraps.classList.add(cls);
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);
68 }
69}
70type 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+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+6export type Issue = {
7 body: string;
8 createdAt: string;
···11 repo: string;
12 title: string;
13};
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-}