diff --git a/readme b/readme index d5f5df0..786f969 100644 --- a/readme +++ b/readme @@ -14,3 +14,7 @@ also i want to say that like this isnt a super serious project like fyi whoever >i dont want to wrestle npm if i can help it. lol lmao i really thought i could get away with a single pure js file + +hey! heads up! im just thinking that as i add things someone might go "wow i want to use that" +i dont know how packaging works (yet). if you can somehow pull my code into your project as it stands, expect it to break. +its not good for your health. just dont. not yet? diff --git a/src/domlink.ts b/src/domlink.ts index b5f13dc..8f124f7 100644 --- a/src/domlink.ts +++ b/src/domlink.ts @@ -1,158 +1,193 @@ let _LCounter = 0; // is there a point to this? idk. might be unnecessary overhead. export class Node { - wraps: HTMLElement; - children: Node[] = []; - id: string = "L" + _LCounter++;; - constructor(wrap: HTMLElement) { - this.wraps = wrap; - this.wraps.id = this.id; - } - // could i make add and with one function? yeah. - // will i do that? no. - // why not? because it feels nice to have them separate. - add(node: Node | string) { - if (node != undefined) { - let xnode: Node; - if (typeof node === "string") { - xnode = new Label(node); - } else { - xnode = node; - } - this.children.push(xnode); - this.wraps.appendChild(xnode.wraps); - } else { - throw new Error("node undefined"); - } - return this; - } - remove(node: Node) { - let idx = this.children.indexOf(node); - if (idx < 0) { - throw new Error("node not a child of this node"); - } - this.children.splice(idx, 1); - this.wraps.removeChild(node.wraps); - } - with(...nodes: (Node | string)[]) { - nodes.forEach(x=>{this.add(x)}); - return this - } - style(fn: (style: CSSStyleDeclaration) => void) { - fn(this.wraps.style); - return this; - } - class(cls: string) { - this.wraps.classList.add(cls); - return this; - } + wraps: HTMLElement; + children: Node[] = []; + id: string = "L" + _LCounter++;; + constructor(wrap: HTMLElement) { + this.wraps = wrap; + this.wraps.id = this.id; + } + // could i make add and with one function? yeah. + // will i do that? no. + // why not? because it feels nice to have them separate. + add(node: Node | string) { + if (node != undefined) { + let xnode: Node; + if (typeof node === "string") { + xnode = new Label(node); + } else { + xnode = node; + } + this.children.push(xnode); + this.wraps.appendChild(xnode.wraps); + } else { + throw new Error("node undefined"); + } + return this; + } + remove(node: Node) { + let idx = this.children.indexOf(node); + if (idx < 0) { + throw new Error("node not a child of this node"); + } + this.children.splice(idx, 1); + this.wraps.removeChild(node.wraps); + } + with(...nodes: (Node | string)[]) { + nodes.forEach(x=>{this.add(x)}); + return this + } + style(fn: (style: CSSStyleDeclaration) => void) { + fn(this.wraps.style); + return this; + } + class(cls: string) { + this.wraps.classList.add(cls); + return this; + } +} +type NodeEquivalent = (Node | string) +export class Link extends Node { + constructor(around: NodeEquivalent | null) { + let a = document.createElement("a"); + a.classList.add("LLink"); + super(a); + if (around != null) { + this.add(around); + } + } + set target(x: string) { + (this.wraps as HTMLAnchorElement).href = x; + } + get target(): string { + return (this.wraps as HTMLAnchorElement).href; + } + to(tgt: string) { + this.target = tgt; + return this; + } } export class Container extends Node { - constructor() { - let div = document.createElement("div"); - div.classList.add("LContainer"); - super(div); - } + constructor() { + let div = document.createElement("div"); + div.classList.add("LContainer"); + super(div); + } } export class Column extends Container { - constructor() { - super(); - this.wraps.classList.add("LColumn"); - } + constructor() { + super(); + this.wraps.classList.add("LColumn"); + } } export class Row extends Container { - constructor() { - super(); - this.wraps.classList.add("LRow"); - } + constructor() { + super(); + this.wraps.classList.add("LRow"); + } } export class Table extends Node { - constructor() { - let table = document.createElement("table"); - table.classList.add("LTable"); - super(table); - } + constructor() { + let table = document.createElement("table"); + table.classList.add("LTable"); + super(table); + } } export class TableRow extends Node { - constructor() { - let tr = document.createElement("tr"); - tr.classList.add("LTableRow"); - super(tr); - } + constructor() { + let tr = document.createElement("tr"); + tr.classList.add("LTableRow"); + super(tr); + } } export class TableCell extends Node { - constructor() { - let td = document.createElement("td"); - td.classList.add("LTableCell"); - super(td); - } + constructor() { + let td = document.createElement("td"); + td.classList.add("LTableCell"); + super(td); + } } export class Text extends Node { - private _text: string = ""; - get text() { - return this._text; - } - set text(x) { - this._text = x; - this.wraps.textContent = x; - } + private _text: string = ""; + get text() { + return this._text; + } + set text(x) { + this._text = x; + this.wraps.textContent = x; + } } export class Label extends Text { - constructor(text: string = "") { - let el = document.createElement("span"); - el.classList.add("LLabel"); - super(el); - this.text = text; - } + constructor(text: string = "") { + let el = document.createElement("span"); + el.classList.add("LLabel"); + super(el); + this.text = text; + } } export class Button extends Text { - constructor(label: string, action: EventListener) { - let btn = document.createElement("button"); - btn.textContent = label; - btn.addEventListener("click", action); - btn.classList.add("LButton"); - super(btn); - this.text = label; - } + constructor(label: string, action: EventListener) { + let btn = document.createElement("button"); + btn.textContent = label; + btn.addEventListener("click", action); + btn.classList.add("LButton"); + super(btn); + this.text = label; + } +} +interface Updatable { + watch: (watcher: (newValue: string)=>void) => void; } -export class Input extends Text { - constructor(type: HTMLInputElement["type"] = "text") { - let el = document.createElement("input"); - el.type = type; - el.classList.add("LInput"); - super(el); - } - set placeholder(x: string) { - (this.wraps as HTMLInputElement).placeholder = x; - } - get value(): string { - return (this.wraps as HTMLInputElement).value; - } - set value(x: string) { - (this.wraps as HTMLInputElement).value = x; - } +export class Input extends Text implements Updatable { + constructor(type: HTMLInputElement["type"] = "text") { + let el = document.createElement("input"); + el.type = type; + el.classList.add("LInput"); + super(el); + } + set placeholder(x: string) { + (this.wraps as HTMLInputElement).placeholder = x; + } + get value(): string { + return (this.wraps as HTMLInputElement).value; + } + set value(x: string) { + (this.wraps as HTMLInputElement).value = x; + this.watching.forEach(y=>y(x)); + } + + + + // Implementation for Updatable + watching: ((newValue: string) => void)[] = []; + watch(watcher: (newValue: string)=>void) { + this.watching.push(watcher); + this.wraps.oninput = (e)=>{watcher(this.value)}; + watcher(this.value); + } } export class Image extends Node { - constructor(src: string) { - let img = document.createElement("img"); - img.src = src; - img.classList.add("LImage"); - super(img); - } + constructor(src: string) { + let img = document.createElement("img"); + img.src = src; + img.classList.add("LImage"); + super(img); + } } export class Modal extends Node { - constructor() { - let dlg: HTMLDialogElement = document.createElement("dialog"); - dlg.classList.add("LModal"); - super(dlg); - } - show() { - Body.add(this); - (this.wraps as HTMLDialogElement).showModal(); - } - hide() { - (this.wraps as HTMLDialogElement).close(); - Body.wraps.removeChild(this.wraps); - } + constructor() { + let dlg: HTMLDialogElement = document.createElement("dialog"); + dlg.classList.add("LModal"); + super(dlg); + } + show() { + Body.add(this); + (this.wraps as HTMLDialogElement).showModal(); + } + hide() { + (this.wraps as HTMLDialogElement).close(); + Body.wraps.removeChild(this.wraps); + } } // user helper shit diff --git a/src/issuesearch.ts b/src/issuesearch.ts index 5a4db98..0514ed8 100644 --- a/src/issuesearch.ts +++ b/src/issuesearch.ts @@ -1,7 +1,7 @@ -import { Body, Button, Column, Input, Label, Row, Table, TableCell, TableRow } from "./domlink.js"; +import { Body, Button, Column, Input, Label, Link, Row, Table, TableCell, TableRow } from "./domlink.js"; type issueRecord = { - body: string; + body: string; createdAt: string; issueId: number; owner: string; @@ -57,34 +57,34 @@ async function xcall(host: string, method: string, params: Record; let repoRecord = ownedRepos.records.find(x=>x.value.name == repoName)!; let encodedRepoUri = encodeURIComponent(repoRecord.uri); - let view_issues_list = new Table().add( - new TableRow().with( - new TableCell().add("Handle"), - new TableCell().add("Issue#"), - new TableCell().add("Title") - ) - ); - Body.add(view_issues_list); + statusText.text = `finding issues...`; let irsp = await (await fetch(`https://constellation.microcosm.blue/links?target=${encodedRepoUri}&collection=sh.tangled.repo.issue&path=.repo`)).json() as constellationResponse; let allIssueRefs: constellationRecordItem[] = irsp.linking_records; let nextCursor = irsp.cursor; while (allIssueRefs.length < irsp.total && nextCursor != null) { - let rsp = await (await fetch(`https://constellation.microcosm.blue/links?target=${encodedRepoUri}&collection=sh.tangled.repo.issue&path=.repo&cursor=${nextCursor}`)).json() as constellationResponse; + statusText.text = `finding issues... (${allIssueRefs.length}/${irsp.total})`; + let rsp = await (await fetch(`https://constellation.microcosm.blue/links?target=${encodedRepoUri}&collection=sh.tangled.repo.issue&path=.repo&cursor=${nextCursor}`, {headers:{Accept:"application/json"}})).json() as constellationResponse; nextCursor = rsp.cursor; allIssueRefs = allIssueRefs.concat(rsp.linking_records); if (nextCursor == null) break; } - let sortedIssues = (await Promise.all(allIssueRefs.map(async (issueRef) => { + allIssues = (await Promise.all(allIssueRefs.map(async (issueRef) => { let rsp = await xcall(SLINGSHOT, "com.atproto.repo.getRecord", { repo: issueRef.did, collection: issueRef.collection, @@ -97,25 +97,36 @@ async function listIssues(repo: string) { } catch (error) { return null; } + statusText.text = `retrieved issue #${issue.issueId} (not in order!)`; let handle = doc.handle; return { handle: handle, issue: issue }; - }))).sort((a,b)=>{ + }))).filter(x=>x as issueItem).sort((a,b)=>{ let x = 0; if (a && b) { x = b.issue.issueId-a.issue.issueId } return x; - }); - sortedIssues.forEach(that =>{ - if (that) { + }) as issueItem[]; + statusText.text = `got ${allIssues.length} issues!`; +} +function renderIssues(issues:issueItem[]) { + let view_issues_list = new Table().add( + new TableRow().with( + new TableCell().add("Handle"), + new TableCell().add("Issue#"), + new TableCell().add("Title") + ) + ); + issues.forEach(issue =>{ + if (issue) { let view_issue_row = new TableRow(); view_issue_row.with( - new TableCell().add(that.handle), - new TableCell().add(that.issue.issueId.toString()), - new TableCell().add(that.issue.title) + new TableCell().add(new Link(issue.handle).to(`https://tangled.sh/@${issue.handle}`)), + new TableCell().add(new Link(issue.issue.issueId.toString()).to(`${currentRepoLink}/issues/${issue.issue.issueId}`)), + new TableCell().add(issue.issue.title) ); view_issues_list.add(view_issue_row); } @@ -130,19 +141,59 @@ let statusText = new Label("waiting"); let runButton = new Button("GO!",async ()=>{ (runButton.wraps as HTMLButtonElement).disabled = true; if (last) { - Body.remove(last); + try { + Body.remove(last); + } catch {} + last = null; } + allIssues = []; + searchRow.style(x=>x.display="none"); try { - last = await listIssues(repoUrl.value); + await getIssues(repoUrl.value); + console.log(allIssues); + last = renderIssues(allIssues); + searchRow.style(x=>x.removeProperty("display")); } catch (e) { last = new Label("Failed"); - Body.add(last); + console.log(e); } + Body.add(last); + searchInput.value = ""; console.log(last); (runButton.wraps as HTMLButtonElement).disabled = false; }); + + + +let searchInput = new Input(); +searchInput.watch((search)=>{ + if (allIssues.length > 0) { + if (last) { + Body.remove(last); + } + search = search.toLowerCase(); + last = renderIssues(allIssues.filter((item)=>{ + return item.issue.issueId == Number(item) || + search.split(" ").every((word)=> ( + item.handle.toLowerCase().includes(word) || + item.issue.title.toLowerCase().includes(word) || + item.issue.body.toLowerCase().includes(word))) + })); + Body.add(last); + } +}); +let searchRow = new Row().with( + "search", + searchInput +); +searchRow.style(x=>x.display="none"); + + + Body.add(new Row().with( "List issues for repo: ", repoUrl, - runButton -)); \ No newline at end of file + runButton, + statusText +)); +Body.add(searchRow); diff --git a/static/issuesearch.html b/static/issuesearch.html index 00a1f8d..12e2ee9 100644 --- a/static/issuesearch.html +++ b/static/issuesearch.html @@ -1,15 +1,15 @@ - - - issuesearch - - - + + + issuesearch + + + - - + +