···28282929bro idk what this is anymore but im a fan of it and will keep going
30303131+i really gotta stop using Node.wraps...
3232+that literally means that my implementation around it is inadequate
+7-1
src/domlink.ts
···11-let _LCounter = 0; // is there a point to this? IDK but it feels useful.
11+let _LCounter = 0;
22+const NodeMap = new WeakMap<HTMLElement, Node>();
2334/** Core wrapper for {@link HTMLElement DOM elements}. */
45export class Node {
···5960 class(cls: string) {
6061 this.wraps.classList.add(cls);
6162 return this;
6363+ }
6464+ /** Hopefully, this points at the parent element. */
6565+ get parent(): Node | undefined{
6666+ if (this.wraps.parentElement == null) return undefined;
6767+ return NodeMap.get(this.wraps.parentElement);
6268 }
6369}
6470type NodeEquivalent = (Node | string);
-166
src/issuesearch.ts
···11-import { Body, Button, Input, Label, Link, Node, Row, Table, TableCell, TableRow } from "./domlink.ts";
22-import { AtURI, AtURIString, GetRecord, ListRecords, XQuery } from "./support/atproto.ts";
33-import { Issue, Repo } from "./support/tangled.ts";
44-import * as Constellation from "./support/constellation.ts";
55-import { MiniDoc } from "./support/slingshot.ts";
66-Body.with(new Link("TypeScript source here!").to("https://tangled.sh/@hotsocket.fyi/domlink/blob/main/src/issuesearch.ts"));
77-88-type recordListingItem<T> = {
99- uri: string;
1010- cid: string;
1111- value: T;
1212-};
1313-1414-async function timeout<T>(time: number, promise: Promise<T>): Promise<T> {
1515- const result = await Promise.race([new Promise<null>(r=>setTimeout(r, time, null)), promise]);
1616- if (result == null) {
1717- throw new Error("timeout");
1818- }
1919- return result;
2020-}
2121-2222-type issueItem = ({handle: string;issue: Issue});
2323-let allIssues:issueItem[] = [];
2424-let currentRepoLink = ""; // used to build issue links
2525-async function getIssues(repo: string) {
2626- // pick out repo info
2727- const repoSplat = repo.split("//");
2828- let repoOwner: string;
2929- let repoName: string;
3030- if (repoSplat.length == 1) {
3131- const repoHalves = repo.split("/");
3232- if (repoHalves.length == 1) throw new Error("invalid repo string");
3333- repoOwner = repoHalves[0];
3434- repoName = repoHalves[1];
3535- } else {
3636- if (repoSplat[0] == "https:") {
3737- const repoUrlSplat = new URL(repo).pathname.split("/");
3838- // [ "", "@tangled.sh", "core" ]
3939- if (repoUrlSplat.length < 2) throw new Error("invalid repo url");
4040- repoOwner = repoUrlSplat[1];
4141- repoName = repoUrlSplat[2];
4242- } else {
4343- throw new Error("unknown repo uri scheme");
4444- }
4545- }
4646- if (repoOwner.startsWith("@")) repoOwner = repoOwner.substring(1);
4747- currentRepoLink = `https://tangled.sh/@${repoOwner}/${repoName}`;
4848- statusText.text = "resolving owner did";
4949- const repoOwnerDoc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", {identifier: repoOwner});
5050- statusText.text = "finding repository";
5151- const ownedRepos = await ListRecords<Repo>(repoOwnerDoc.did, "sh.tangled.repo", repoOwnerDoc.pds);
5252- const repoRecord = ownedRepos.records.find(x=>x.value.name == repoName)!;
5353- statusText.text = `finding issues...`;
5454- const allIssueRefs = await timeout(10000, Constellation.getLinks(AtURI.fromString(repoRecord.uri as AtURIString), "sh.tangled.repo.issue", ".repo"));
5555- let issueinc = 0;
5656- allIssues = (await Promise.all(allIssueRefs.map(async (issueRef) => {
5757- let issue: Issue;
5858- try {
5959- issue = (await timeout(2000, GetRecord<Issue>(issueRef))).value;
6060- } catch { return null; }
6161- if (issue == null) {
6262- console.log(`getRecord timed out: ${issueRef.toString()}`);
6363- return null;
6464- }
6565- let doc;
6666- try {
6767- doc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", { identifier: issue.owner });
6868- } catch { return null; }
6969- statusText.text = `retrieved issue ${++issueinc}/${allIssueRefs.length} (${issue.issueId})`;
7070- return {
7171- handle: doc.handle,
7272- issue: issue
7373- };
7474- }))).filter(x=>x as issueItem).sort((a,b)=>{
7575- let x = 0;
7676- if (a && b) {
7777- x = b.issue.issueId-a.issue.issueId
7878- }
7979- return x;
8080- }) as issueItem[];
8181- statusText.text = `got ${allIssues.length}/${allIssueRefs.length} issues!`;
8282-}
8383-function renderIssues(issues:issueItem[]) {
8484- const view_issues_list = new Table().add(
8585- new TableRow().with(
8686- new TableCell().add("Handle"),
8787- new TableCell().add("Issue#"),
8888- new TableCell().add("Title")
8989- )
9090- );
9191- issues.forEach(issue =>{
9292- if (issue) {
9393- const view_issue_row = new TableRow();
9494- view_issue_row.with(
9595- new TableCell().add(new Link(issue.handle).to(`https://tangled.sh/@${issue.handle}`)),
9696- new TableCell().add(new Link(issue.issue.issueId.toString()).to(`${currentRepoLink}/issues/${issue.issue.issueId}`)),
9797- new TableCell().add(issue.issue.title)
9898- );
9999- view_issues_list.add(view_issue_row);
100100- }
101101- });
102102- return view_issues_list;
103103-}
104104-105105-106106-const repoUrl = new Input();
107107-let last: Node | null = null;
108108-const statusText = new Label("waiting");
109109-const runButton = new Button("GO!",async ()=>{
110110- (runButton.wraps as HTMLButtonElement).disabled = true;
111111- if (last) {
112112- try {
113113- Body.delete(last);
114114- } finally {
115115- last = null;
116116- }
117117- }
118118- allIssues = [];
119119- searchRow.style(x=>x.display="none");
120120- try {
121121- await getIssues(repoUrl.value);
122122- last = renderIssues(allIssues);
123123- searchRow.style(x=>x.removeProperty("display"));
124124- } catch (e) {
125125- last = new Label("Failed");
126126- console.error(e);
127127- }
128128- Body.add(last);
129129- searchInput.value = "";
130130- (runButton.wraps as HTMLButtonElement).disabled = false;
131131-});
132132-133133-134134-135135-const searchInput = new Input();
136136-searchInput.watch((search)=>{
137137- if (allIssues.length > 0) {
138138- if (last) {
139139- Body.delete(last);
140140- }
141141- search = search.toLowerCase();
142142- last = renderIssues(allIssues.filter((item)=>{
143143- return item.issue.issueId == Number(item) ||
144144- search.split(" ").every((word)=> (
145145- item.handle.toLowerCase().includes(word) ||
146146- item.issue.title.toLowerCase().includes(word) ||
147147- item.issue.body.toLowerCase().includes(word)))
148148- }));
149149- Body.add(last);
150150- }
151151-});
152152-const searchRow = new Row().with(
153153- "search",
154154- searchInput
155155-);
156156-searchRow.style(x=>x.display="none");
157157-158158-159159-160160-Body.add(new Row().with(
161161- "List issues for repo: ",
162162- repoUrl,
163163- runButton,
164164- statusText
165165-));
166166-Body.add(searchRow);
···11+import * as x from "../extras.ts";
22+import { AtURI, ListRecords, XQuery } from "./atproto.ts";
33+import { getUriRecord, MiniDoc } from "./slingshot.ts";
44+import * as Constellation from "./constellation.ts";
55+16export type Issue = {
27 body: string;
38 createdAt: string;
···611 repo: string;
712 title: string;
813};
99-export type Repo = {
1010- knot: string;
1111- name: string;
1212- owner: string;
1313- createdAt: string;
1414-};1414+export type RepoRef = {
1515+ readonly owner: string;
1616+ readonly name: string;
1717+};
1818+type RepoCommon = {
1919+ readonly description: string;
2020+ readonly name: string;
2121+ readonly knot: string;
2222+ readonly spindle: string;
2323+ readonly createdAt: string;
2424+}
2525+type RawRepo = RepoCommon & {
2626+ readonly owner: string;
2727+};
2828+export type Repo = RepoCommon & {
2929+ readonly owner: MiniDoc; // The record itself returns a DID, but a MiniDoc is awfully handy.
3030+ readonly uri: AtURI;
3131+};
3232+3333+export async function GetRepo(ref: RepoRef): Promise<Repo> {
3434+ const repoOwnerDoc = await XQuery<MiniDoc>("com.bad-example.identity.resolveMiniDoc", {identifier: ref.owner});
3535+ const ownedRepos = await ListRecords<RawRepo>(repoOwnerDoc.did, "sh.tangled.repo", repoOwnerDoc.pds);
3636+ const repoRecord = ownedRepos.records.find(x=>x.value.name == ref.name)!;
3737+ return {
3838+ ...repoRecord.value,
3939+ owner: repoOwnerDoc,
4040+ uri: AtURI.fromString(repoRecord.uri)
4141+ };
4242+}
4343+export async function GetIssues(repo: Repo, timeout: number = 10000) {
4444+ const allIssueRefs = await x.timeout(timeout, Constellation.links(repo.uri, "sh.tangled.repo.issue", ".repo"));
4545+ const issues = allIssueRefs.map(async (uri)=>{
4646+ try {
4747+ return (await x.timeout(timeout/5,getUriRecord<Issue>(uri))).value;
4848+ } catch {
4949+ console.log(`issue timed out: ${uri.toString()}`);
5050+ return null;
5151+ }
5252+ }).filter(x=>x!=null) as unknown as Issue[]; // despite filtering them out, typescript still thinks there could be nulls
5353+ return issues.sort((a,b)=>b.issueId-a.issueId);
5454+}
5555+5656+/** Takes a string like `tangled.sh/core` or `https://tangled.sh/@tangled.sh/core` and returns a {@link RepoRef} */
5757+export function StringRepoRef(repo: string): RepoRef {
5858+ // pick out repo info
5959+ const repoSplat = repo.split("//");
6060+ let repoOwner: string;
6161+ let repoName: string;
6262+ if (repoSplat.length == 1) {
6363+ const repoHalves = repo.split("/");
6464+ if (repoHalves.length == 1) throw new Error("invalid repo string");
6565+ repoOwner = repoHalves[0];
6666+ repoName = repoHalves[1];
6767+ } else {
6868+ if (repoSplat[0] == "https:") {
6969+ const repoUrlSplat = new URL(repo).pathname.split("/");
7070+ // [ "", "@tangled.sh", "core" ]
7171+ if (repoUrlSplat.length < 2) throw new Error("invalid repo url");
7272+ repoOwner = repoUrlSplat[1];
7373+ repoName = repoUrlSplat[2];
7474+ } else {
7575+ throw new Error("unknown repo uri scheme");
7676+ }
7777+ }
7878+ if (repoOwner.startsWith("@")) repoOwner = repoOwner.substring(1);
7979+ return {owner: repoOwner, name: repoName};
8080+}
-7
src/windows/issuesearch.ts
···11-// rewrite of ../issuesearch.ts to be a self-contained class that can be shoved into a window easily
22-33-import { Column } from "../domlink.ts";
44-55-export class IssueSearch extends Column {
66-77-}