From 7aa0fe214bb7d38a9cd0488dd8b7f372b6116bae Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 2 May 2023 15:46:40 +0100 Subject: [PATCH] feat: create patch event --- package.json | 2 + patch | 288 ++++++++++++++++++++++++++++++++++++++++++++ src/App.tsx | 4 +- src/Diff.ts | 2 +- src/NewPatch.css | 9 +- src/NewPatch.tsx | 116 ++++++++++-------- src/PatchBuilder.ts | 35 ++++++ src/PatchReview.tsx | 53 +------- src/PathView.tsx | 56 +++++++++ src/Util.ts | 4 + yarn.lock | 7 +- 11 files changed, 470 insertions(+), 106 deletions(-) create mode 100644 patch create mode 100644 src/PatchBuilder.ts create mode 100644 src/PathView.tsx 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/patch b/patch new file mode 100644 index 0000000..d545f08 --- /dev/null +++ b/patch @@ -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
++ < Back ++

++ New Patch ++

++

++ Patch Title: ++

++ setSubject(e.target.value)} /> ++

++ Enter Diff: ++

++ ++

++ Git Repo: ++

++ setRepo(e.target.value)} /> ++

++ Relay(s): ++

++ setRelay(e.target.value)} /> ++

++ ++
; ++ } ++ ++ 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
++ ++
+ } + + return
+- < Back +-

+- New Patch +-

+-

+- Patch Title: +-

+- setSubject(e.target.value)} /> +-

+- Enter Diff: +-

+- +-

+- Git Repo: +-

+- setRepo(e.target.value)} /> +-

+- Relay(s): +-

+- setRelay(e.target.value)} /> +-

+- ++ {inputs()} ++ {preview()} +
+ } +\ 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 Missing route data + } + +- function renderChunk(c: Chunk) { +- var oldY = c.oldStart; +- var newY = c.newStart; +- return <> +-
+-
+-
+-
+- {c.content} +-
+-
+- {c.changes.map(v =>
+-
{v.type === "del" || v.type === "normal" ? ++oldY : ""}
+-
{v.type === "add" || v.type === "normal" ? ++newY : ""}
+-
+-
+-
)} +- +- } +- +- function renderFileChanges(f: File) { +- const k = `${f.from}=${f.to}`; +- return
+-
+-
+- {f.from}{f.to && f.to !== f.from && `...${f.to}`} +-
+-
+-
+- +{f.additions ?? 0} +-
+-
+- -{f.deletions ?? 0} +-
+-
+-
+-
+- {f.chunks.map(renderChunk)} +-
+-
+- } +- +- const patch = location.state as ParsedPatch +- return <> +- {patch.diff.map(renderFileChanges)} +- ++ return + } +\ 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(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== diff --git a/src/App.tsx b/src/App.tsx index 13d8e19..9f2c8d0 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], @@ -41,7 +41,7 @@ export function App() { const [tag, setTag] = useState(); 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 ( diff --git a/src/Diff.ts b/src/Diff.ts index 57b1c5f..1dda455 100644 --- a/src/Diff.ts +++ b/src/Diff.ts @@ -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, 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..cee7c3e 100644 --- a/src/NewPatch.tsx +++ b/src/NewPatch.tsx @@ -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
+ < Back +

+ New Patch +

+

+ Patch Title: +

+ setSubject(e.target.value)} /> +

+ Enter Diff: +

+ +

+ Git Repo: +

+ setRepo(e.target.value)} /> +

+ Relay(s): +

+ setRelay(e.target.value)} /> +

+ +
; + } + + 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
+ +
} return
- < Back -

- New Patch -

-

- Patch Title: -

- setSubject(e.target.value)} /> -

- Enter Diff: -

- -

- Git Repo: -

- setRepo(e.target.value)} /> -

- Relay(s): -

- setRelay(e.target.value)} /> -

- + {inputs()} + {preview()}
} \ No newline at end of file diff --git a/src/PatchBuilder.ts b/src/PatchBuilder.ts new file mode 100644 index 0000000..a21178b --- /dev/null +++ b/src/PatchBuilder.ts @@ -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; + signEvent: (event: Event) => Promise; + getRelays: () => Promise>; + nip04: { + encrypt: (pubkey: string, content: string) => Promise; + decrypt: (pubkey: string, content: string) => Promise; + }; + }; + } +} + +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] + ] + }) +} \ 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 Missing route data } - function renderChunk(c: Chunk) { - var oldY = c.oldStart; - var newY = c.newStart; - return <> -
-
-
-
- {c.content} -
-
- {c.changes.map(v =>
-
{v.type === "del" || v.type === "normal" ? ++oldY : ""}
-
{v.type === "add" || v.type === "normal" ? ++newY : ""}
-
-
-
)} - - } - - function renderFileChanges(f: File) { - const k = `${f.from}=${f.to}`; - return
-
-
- {f.from}{f.to && f.to !== f.from && `...${f.to}`} -
-
-
- +{f.additions ?? 0} -
-
- -{f.deletions ?? 0} -
-
-
-
- {f.chunks.map(renderChunk)} -
-
- } - - const patch = location.state as ParsedPatch - return <> - {patch.diff.map(renderFileChanges)} - + return } \ No newline at end of file diff --git a/src/PathView.tsx b/src/PathView.tsx new file mode 100644 index 0000000..33a80d5 --- /dev/null +++ b/src/PathView.tsx @@ -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 <> +
+
+
+
+ {c.content} +
+
+ {c.changes.map(v =>
+
{v.type === "del" || v.type === "normal" ? ++oldY : ""}
+
{v.type === "add" || v.type === "normal" ? ++newY : ""}
+
+
+
)} + + } + + function renderFileChanges(f: File) { + const k = `${f.from}=${f.to}`; + return
+
+
+ {f.from}{f.to && f.to !== f.from && `...${f.to}`} +
+
+
+ +{f.additions ?? 0} +
+
+ -{f.deletions ?? 0} +
+
+
+
+ {f.chunks.map(renderChunk)} +
+
+ } + + return <> + {patch.diff.map(renderFileChanges)} + +} \ 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(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==