feat: create patch event

This commit is contained in:
Kieran 2023-05-02 15:46:40 +01:00
parent 39cb400cda
commit 7aa0fe214b
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
11 changed files with 470 additions and 106 deletions

View File

@ -5,6 +5,7 @@
"dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"@noble/secp256k1": "^2.0.0",
"@protobufjs/base64": "^1.1.2",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
@ -15,6 +16,7 @@
"isomorphic-git": "^1.23.0",
"moment": "^2.29.4",
"nostr-relaypool": "^0.6.27",
"nostr-tools": "^1.10.1",
"parse-diff": "^0.11.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",

288
patch Normal file
View File

@ -0,0 +1,288 @@
diff --git a/package.json b/package.json
index 000f4b0..6fff3ef 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"@noble/secp256k1": "^2.0.0",
+ "@protobufjs/base64": "^1.1.2",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
@@ -15,6 +16,7 @@
"isomorphic-git": "^1.23.0",
"moment": "^2.29.4",
"nostr-relaypool": "^0.6.27",
+ "nostr-tools": "^1.10.1",
"parse-diff": "^0.11.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/src/App.tsx b/src/App.tsx
index 13d8e19..05246df 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -16,7 +16,7 @@ const relays = [
export const PatchKind = 19691228;
const Store = new PatchCache("Patches", PatchstrDb.events);
-const Nostr = new RelayPool(relays);
+export const Nostr = new RelayPool(relays);
const sub = Nostr.subscribe([
{
kinds: [PatchKind],
diff --git a/src/NewPatch.css b/src/NewPatch.css
index fb08796..479a2db 100644
--- a/src/NewPatch.css
+++ b/src/NewPatch.css
@@ -1,4 +1,11 @@
.new-patch {
padding: 20px;
- width: 720px;
+ display: flex;
+ flex-direction: row;
+}
+
+.new-patch>div {
+ flex: 1;
+ height: calc(100vh - 40px);
+ overflow: auto;
}
\ No newline at end of file
diff --git a/src/NewPatch.tsx b/src/NewPatch.tsx
index 15b304c..65f505f 100644
--- a/src/NewPatch.tsx
+++ b/src/NewPatch.tsx
@@ -1,9 +1,14 @@
-import { useState } from "react";
+import { useMemo, useState } from "react";
import git from "isomorphic-git";
import http from "isomorphic-git/http/web";
import LightningFS from "@isomorphic-git/lightning-fs";
import "./NewPatch.css";
+import PatchView from "./PathView";
+import { ParsedPatch, parseDiffEvent } from "./Diff";
+import { unixNow } from "./Util";
+import buildPatchEvent from "./PatchBuilder";
+import { Nostr } from "./App";
const fs = new LightningFS("patchstr-fs");
@@ -14,52 +19,61 @@ export default function NewPatch() {
const [relay, setRelay] = useState("");
async function testRepo() {
- const testDir = `/${new Date().getTime() / 1000}_test`;
- await git.init({
- fs,
- dir: testDir
+ const ev = await buildPatchEvent(subject, "", repo, diff);
+ console.debug(ev);
+
+ Nostr.publish(ev, relay.split(/[,; ]/));
+ }
+
+ function inputs() {
+ return <div>
+ <a href="/">&lt; Back</a>
+ <h1>
+ New Patch
+ </h1>
+ <p>
+ Patch Title:
+ </p>
+ <input type="text" placeholder="chore: tweak NIP's" value={subject} onChange={e => setSubject(e.target.value)} />
+ <p>
+ Enter Diff:
+ </p>
+ <textarea cols={80} rows={40} value={diff} onChange={e => setDiff(e.target.value)}></textarea>
+ <p>
+ Git Repo:
+ </p>
+ <input type="text" placeholder="https://github.com/user/repo" value={repo} onChange={e => setRepo(e.target.value)} />
+ <p>
+ Relay(s):
+ </p>
+ <input type="text" placeholder="wss://nostr.mutinywallet.com wss://nos.lol" value={relay} onChange={e => setRelay(e.target.value)} />
+ <br /><br />
+ <button onClick={() => testRepo()}>
+ Submit
+ </button>
+ </div>;
+ }
+
+ const tmpDiff = useMemo(() => {
+ return parseDiffEvent({
+ created_at: unixNow(),
+ content: diff,
+ pubkey: "",
+ sig: "",
+ id: "",
+ tags: [],
+ kind: 0
})
- await git.addRemote({
- fs,
- dir: testDir,
- remote: "upstream",
- url: repo
- });
- const info = await git.fetch({
- fs,
- http,
- remote: "upstream",
- dir: testDir,
- corsProxy: "https://cors.isomorphic-git.org"
- });
- fs.rmdir(testDir, undefined, console.error);
- console.debug(info);
+ }, [diff]);
+
+ function preview() {
+ return <div>
+ <PatchView patch={tmpDiff} />
+ </div>
}
return <div className="new-patch">
- <a href="/">&lt; Back</a>
- <h1>
- New Patch
- </h1>
- <p>
- Patch Title:
- </p>
- <input type="text" placeholder="chore: tweak NIP's" value={subject} onChange={e => setSubject(e.target.value)} />
- <p>
- Enter Diff:
- </p>
- <textarea cols={80} rows={40} value={diff} onChange={e => setDiff(e.target.value)}></textarea>
- <p>
- Git Repo:
- </p>
- <input type="text" placeholder="https://github.com/user/repo" value={repo} onChange={e => setRepo(e.target.value)} />
- <p>
- Relay(s):
- </p>
- <input type="text" placeholder="wss://nostr.mutinywallet.com wss://nos.lol" value={relay} onChange={e => setRelay(e.target.value)} />
- <br /><br />
- <button onClick={() => testRepo()}>
- Submit
- </button>
+ {inputs()}
+ {preview()}
</div>
}
\ No newline at end of file
diff --git a/src/PatchReview.tsx b/src/PatchReview.tsx
index fe2a853..c00d161 100644
--- a/src/PatchReview.tsx
+++ b/src/PatchReview.tsx
@@ -1,10 +1,8 @@
-import { Chunk, File } from "parse-diff";
import { useLocation } from "react-router-dom";
-import hljs from "highlight.js";
-import 'highlight.js/styles/dark.css';
import "./PatchReview.css";
import { ParsedPatch } from "./Diff";
+import PatchView from "./PathView";
export default function PatchReview() {
const location = useLocation();
@@ -13,52 +11,5 @@ export default function PatchReview() {
return <b>Missing route data</b>
}
- function renderChunk(c: Chunk) {
- var oldY = c.oldStart;
- var newY = c.newStart;
- return <>
- <div className="diff chunk">
- <div></div>
- <div></div>
- <div>
- {c.content}
- </div>
- </div>
- {c.changes.map(v => <div className={`diff ${v.type}`}>
- <div>{v.type === "del" || v.type === "normal" ? ++oldY : ""}</div>
- <div>{v.type === "add" || v.type === "normal" ? ++newY : ""}</div>
- <div dangerouslySetInnerHTML={{
- __html: hljs.highlightAuto(v.content, [""]).value
- }}>
- </div>
- </div>)}
- </>
- }
-
- function renderFileChanges(f: File) {
- const k = `${f.from}=${f.to}`;
- return <div className="file" key={k}>
- <div className="header">
- <div>
- {f.from}{f.to && f.to !== f.from && `...${f.to}`}
- </div>
- <div>
- <div className="add">
- +{f.additions ?? 0}
- </div>
- <div className="del">
- -{f.deletions ?? 0}
- </div>
- </div>
- </div>
- <div className="body">
- {f.chunks.map(renderChunk)}
- </div>
- </div>
- }
-
- const patch = location.state as ParsedPatch
- return <>
- {patch.diff.map(renderFileChanges)}
- </>
+ return <PatchView patch={location.state as ParsedPatch} />
}
\ No newline at end of file
diff --git a/src/Util.ts b/src/Util.ts
index 76e421e..3023652 100644
--- a/src/Util.ts
+++ b/src/Util.ts
@@ -36,6 +36,10 @@ export function unixNowMs() {
return new Date().getTime();
}
+export function unixNow() {
+ return Math.floor(unixNowMs() / 1000);
+}
+
export function unwrap<T>(value?: T) {
if (!value) {
throw new Error("Missing value");
diff --git a/yarn.lock b/yarn.lock
index f6f2d62..ce0a062 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1942,6 +1942,11 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
+"@protobufjs/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
+ integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
+
"@remix-run/router@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.0.tgz#45010e1826f4d81a1b2cfaf874f1aac93998cd28"
@@ -6990,7 +6995,7 @@ nostr-relaypool@^0.6.27:
nostr-tools "^1.10.0"
safe-stable-stringify "^2.4.2"
-nostr-tools@^1.10.0:
+nostr-tools@^1.10.0, nostr-tools@^1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.10.1.tgz#b52043b3031f4314478d0a3bfaa8ffb9cc4f98a0"
integrity sha512-zgTYJeuZQ3CDASsmBEcB5i6V6l0IaA6cjnll6OVik3FoZcvbCaL7yP8I40hYnOIi3KlJykV7jEF9fn8h1NzMnA==

View File

@ -16,7 +16,7 @@ const relays = [
export const PatchKind = 19691228;
const Store = new PatchCache("Patches", PatchstrDb.events);
const Nostr = new RelayPool(relays);
export const Nostr = new RelayPool(relays);
const sub = Nostr.subscribe([
{
kinds: [PatchKind],
@ -41,7 +41,7 @@ export function App() {
const [tag, setTag] = useState<string>();
const patches = useMemo(() => {
return store.filter(a => tag === undefined || a.tag === tag);
return [...store.filter(a => tag === undefined || a.tag === tag)].sort(a => -a.created);
}, [tag, store]);
return (

View File

@ -30,7 +30,7 @@ export function parseDiffEvent(ev: Event) {
pubkey: ev.pubkey,
tag,
author: {
name: matches?.[1],
name: matches?.[1] ?? ev.pubkey.slice(0, 12),
email: matches?.[2]
},
subject,

View File

@ -1,4 +1,11 @@
.new-patch {
padding: 20px;
width: 720px;
display: flex;
flex-direction: row;
}
.new-patch>div {
flex: 1;
height: calc(100vh - 40px);
overflow: auto;
}

View File

@ -1,65 +1,81 @@
import { useState } from "react";
import git from "isomorphic-git";
import http from "isomorphic-git/http/web";
import LightningFS from "@isomorphic-git/lightning-fs";
import { useMemo, useState } from "react";
import "./NewPatch.css";
const fs = new LightningFS("patchstr-fs");
import PatchView from "./PathView";
import { parseDiffEvent } from "./Diff";
import { unixNow } from "./Util";
import buildPatchEvent from "./PatchBuilder";
import { Nostr } from "./App";
import { useNavigate } from "react-router-dom";
import { encodeTLV } from "./TLV";
import { NostrPrefix } from "./Nostr";
export default function NewPatch() {
const navigate = useNavigate();
const [subject, setSubject] = useState("");
const [diff, setDiff] = useState("");
const [repo, setRepo] = useState("");
const [relay, setRelay] = useState("");
async function testRepo() {
const testDir = `/${new Date().getTime() / 1000}_test`;
await git.init({
fs,
dir: testDir
async function submitPatch() {
const ev = await buildPatchEvent(subject, "", repo, diff);
console.debug(ev);
Nostr.publish(ev, relay.split(/[,; ]/));
navigate(`/e/${encodeTLV(ev.id, NostrPrefix.Event)}`, {
state: ev
});
}
function inputs() {
return <div>
<a href="/">&lt; Back</a>
<h1>
New Patch
</h1>
<p>
Patch Title:
</p>
<input type="text" placeholder="chore: tweak NIP's" value={subject} onChange={e => setSubject(e.target.value)} />
<p>
Enter Diff:
</p>
<textarea cols={80} rows={40} value={diff} onChange={e => setDiff(e.target.value)}></textarea>
<p>
Git Repo:
</p>
<input type="text" placeholder="https://github.com/user/repo" value={repo} onChange={e => setRepo(e.target.value)} />
<p>
Relay(s):
</p>
<input type="text" placeholder="wss://nostr.mutinywallet.com wss://nos.lol" value={relay} onChange={e => setRelay(e.target.value)} />
<br /><br />
<button onClick={() => submitPatch()}>
Submit
</button>
</div>;
}
const tmpDiff = useMemo(() => {
return parseDiffEvent({
created_at: unixNow(),
content: diff,
pubkey: "",
sig: "",
id: "",
tags: [],
kind: 0
})
await git.addRemote({
fs,
dir: testDir,
remote: "upstream",
url: repo
});
const info = await git.fetch({
fs,
http,
remote: "upstream",
dir: testDir,
corsProxy: "https://cors.isomorphic-git.org"
});
fs.rmdir(testDir, undefined, console.error);
console.debug(info);
}, [diff]);
function preview() {
return <div>
<PatchView patch={tmpDiff} />
</div>
}
return <div className="new-patch">
<a href="/">&lt; Back</a>
<h1>
New Patch
</h1>
<p>
Patch Title:
</p>
<input type="text" placeholder="chore: tweak NIP's" value={subject} onChange={e => setSubject(e.target.value)} />
<p>
Enter Diff:
</p>
<textarea cols={80} rows={40} value={diff} onChange={e => setDiff(e.target.value)}></textarea>
<p>
Git Repo:
</p>
<input type="text" placeholder="https://github.com/user/repo" value={repo} onChange={e => setRepo(e.target.value)} />
<p>
Relay(s):
</p>
<input type="text" placeholder="wss://nostr.mutinywallet.com wss://nos.lol" value={relay} onChange={e => setRelay(e.target.value)} />
<br /><br />
<button onClick={() => testRepo()}>
Submit
</button>
{inputs()}
{preview()}
</div>
}

35
src/PatchBuilder.ts Normal file
View File

@ -0,0 +1,35 @@
import { Event, Kind } from "nostr-tools";
import { unixNow } from "./Util";
import { PatchKind } from "./App";
declare global {
interface Window {
nostr: {
getPublicKey: () => Promise<string>;
signEvent: (event: Event) => Promise<Event>;
getRelays: () => Promise<Record<string, { read: boolean; write: boolean }>>;
nip04: {
encrypt: (pubkey: string, content: string) => Promise<string>;
decrypt: (pubkey: string, content: string) => Promise<string>;
};
};
}
}
export default async function buildPatchEvent(title: string, author: string, repo: string, diff: string) {
const pk = await window.nostr.getPublicKey();
return await window.nostr.signEvent({
id: "",
sig: "",
kind: PatchKind as Kind,
pubkey: pk,
content: diff,
created_at: unixNow(),
tags: [
["t", repo.split("/").pop()!.replace(".git", "")],
["subject", title],
["author", author]
]
})
}

View File

@ -1,10 +1,8 @@
import { Chunk, File } from "parse-diff";
import { useLocation } from "react-router-dom";
import hljs from "highlight.js";
import 'highlight.js/styles/dark.css';
import "./PatchReview.css";
import { ParsedPatch } from "./Diff";
import PatchView from "./PathView";
export default function PatchReview() {
const location = useLocation();
@ -13,52 +11,5 @@ export default function PatchReview() {
return <b>Missing route data</b>
}
function renderChunk(c: Chunk) {
var oldY = c.oldStart;
var newY = c.newStart;
return <>
<div className="diff chunk">
<div></div>
<div></div>
<div>
{c.content}
</div>
</div>
{c.changes.map(v => <div className={`diff ${v.type}`}>
<div>{v.type === "del" || v.type === "normal" ? ++oldY : ""}</div>
<div>{v.type === "add" || v.type === "normal" ? ++newY : ""}</div>
<div dangerouslySetInnerHTML={{
__html: hljs.highlightAuto(v.content, [""]).value
}}>
</div>
</div>)}
</>
}
function renderFileChanges(f: File) {
const k = `${f.from}=${f.to}`;
return <div className="file" key={k}>
<div className="header">
<div>
{f.from}{f.to && f.to !== f.from && `...${f.to}`}
</div>
<div>
<div className="add">
+{f.additions ?? 0}
</div>
<div className="del">
-{f.deletions ?? 0}
</div>
</div>
</div>
<div className="body">
{f.chunks.map(renderChunk)}
</div>
</div>
}
const patch = location.state as ParsedPatch
return <>
{patch.diff.map(renderFileChanges)}
</>
return <PatchView patch={location.state as ParsedPatch} />
}

56
src/PathView.tsx Normal file
View File

@ -0,0 +1,56 @@
import { File, Chunk } from "parse-diff";
import hljs from "highlight.js";
import 'highlight.js/styles/dark.css';
import { ParsedPatch } from "./Diff";
export default function PatchView({ patch }: { patch: ParsedPatch }) {
function renderChunk(c: Chunk) {
var oldY = c.oldStart;
var newY = c.newStart;
return <>
<div className="diff chunk">
<div></div>
<div></div>
<div>
{c.content}
</div>
</div>
{c.changes.map(v => <div className={`diff ${v.type}`}>
<div>{v.type === "del" || v.type === "normal" ? ++oldY : ""}</div>
<div>{v.type === "add" || v.type === "normal" ? ++newY : ""}</div>
<div dangerouslySetInnerHTML={{
__html: hljs.highlightAuto(v.content, [""]).value
}}>
</div>
</div>)}
</>
}
function renderFileChanges(f: File) {
const k = `${f.from}=${f.to}`;
return <div className="file" key={k}>
<div className="header">
<div>
{f.from}{f.to && f.to !== f.from && `...${f.to}`}
</div>
<div>
<div className="add">
+{f.additions ?? 0}
</div>
<div className="del">
-{f.deletions ?? 0}
</div>
</div>
</div>
<div className="body">
{f.chunks.map(renderChunk)}
</div>
</div>
}
return <>
{patch.diff.map(renderFileChanges)}
</>
}

View File

@ -36,6 +36,10 @@ export function unixNowMs() {
return new Date().getTime();
}
export function unixNow() {
return Math.floor(unixNowMs() / 1000);
}
export function unwrap<T>(value?: T) {
if (!value) {
throw new Error("Missing value");

View File

@ -1942,6 +1942,11 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@protobufjs/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
"@remix-run/router@1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.0.tgz#45010e1826f4d81a1b2cfaf874f1aac93998cd28"
@ -6990,7 +6995,7 @@ nostr-relaypool@^0.6.27:
nostr-tools "^1.10.0"
safe-stable-stringify "^2.4.2"
nostr-tools@^1.10.0:
nostr-tools@^1.10.0, nostr-tools@^1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.10.1.tgz#b52043b3031f4314478d0a3bfaa8ffb9cc4f98a0"
integrity sha512-zgTYJeuZQ3CDASsmBEcB5i6V6l0IaA6cjnll6OVik3FoZcvbCaL7yP8I40hYnOIi3KlJykV7jEF9fn8h1NzMnA==