Thread progess

This commit is contained in:
Kieran 2022-12-20 12:08:41 +00:00
parent e617d6d528
commit c454f5c7aa
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
6 changed files with 98 additions and 12 deletions

View File

@ -3,13 +3,19 @@ import Event from "../nostr/Event";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import moment from "moment";
import { useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { isFulfilled } from "@reduxjs/toolkit";
const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/;
const FileExtensionRegex = /\.([\w]+)$/;
const MentionRegex = /(#\[\d+\])/g;
export default function Note(props) {
const navigate = useNavigate();
const data = props.data;
const [sig, setSig] = useState(false);
const user = useSelector(s => s.users?.users[data?.pubkey]);
const users = useSelector(s => s.users?.users);
const user = users[data?.pubkey];
const ev = Event.FromObject(data);
useEffect(() => {
@ -29,8 +35,10 @@ export default function Note(props) {
}
function goToEvent(e, id) {
e.stopPropagation();
navigate(`/e/${id}`);
if (!window.location.pathname.startsWith("/e/")) {
e.stopPropagation();
navigate(`/e/${id}`);
}
}
function replyTag() {
@ -40,13 +48,61 @@ export default function Note(props) {
}
let replyId = thread?.ReplyTo?.Event;
let mentions = thread?.PubKeys?.map(a => [a, users[a]])?.map(a => a[1]?.name ?? a[0].substring(0, 8));
return (
<div className="reply" onClick={(e) => goToEvent(e, replyId)}>
{replyId?.substring(0, 8)}
{mentions?.join(", ") ?? replyId?.substring(0, 8)}
</div>
)
}
function transformBody() {
let body = ev.Content;
let pTags = ev.Tags.filter(a => a.Key === "p");
let urlBody = body.split(UrlRegex);
return urlBody.map(a => {
if (a.startsWith("http")) {
let url = new URL(a);
let ext = url.pathname.match(FileExtensionRegex);
if (ext) {
switch (ext[1]) {
case "gif":
case "jpg":
case "jpeg":
case "png":
case "bmp":
case "webp": {
return <img src={url} />;
}
case "mp4":
case "mkv":
case "avi":
case "m4v": {
return <video src={url} controls />
}
}
}
} else {
let mentions = a.split(MentionRegex).map((match) => {
if (match.startsWith("#")) {
let pref = pTags[match.match(/\[(\d+)\]/)[1]];
if (pref) {
let pUser = users[pref.PubKey]?.name ?? pref.PubKey.substring(0, 8);
return <Link to={`/p/${pref.PubKey}`}>#{pUser}</Link>;
} else {
return <pre>BROKEN REF: {match[0]}</pre>;
}
} else {
return match;
}
});
return mentions;
}
});
}
if (!ev.IsContent()) {
return (
<>
@ -70,7 +126,7 @@ export default function Note(props) {
</div>
</div>
<div className="body" onClick={(e) => goToEvent(e, ev.Id)}>
{ev.Content}
{transformBody()}
</div>
</div>
)

21
src/element/Thread.js Normal file
View File

@ -0,0 +1,21 @@
import Event from "../nostr/Event";
import Note from "./Note";
export default function Thread(props) {
/** @type {Array<Event>} */
const notes = props.notes?.map(a => Event.FromObject(a));
// root note has no thread info
const root = notes.find(a => a.GetThread() === null);
if(root === undefined) {
return null;
}
const repliesToRoot = notes?.filter(a => a.GetThread()?.ReplyTo?.Event === root.Id);
return (
<>
<Note data={root?.ToObject()}/>
{repliesToRoot?.map(a => <Note key={a.Id} data={a.ToObject()}/>)}
</>
);
}

View File

@ -58,4 +58,9 @@ input[type="text"] {
.f-grow {
flex-grow: 1;
}
a {
color: inherit;
line-height: 1.3em;
}

View File

@ -2,10 +2,16 @@ import Event from "./Event";
export default class Thread {
constructor() {
/** @type {Tag} */
this.Root = null;
/** @type {Tag} */
this.ReplyTo = null;
/** @type {Array<Tag>} */
this.Mentions = [];
/** @type {Event} */
this.Reply = null;
/** @type {Array<String>} */
this.PubKeys = [];
}
/**
@ -34,6 +40,7 @@ export default class Thread {
ret.Root = root;
ret.ReplyTo = reply;
}
ret.PubKeys = ev.Tags.filter(a => a.Key === "p").map(a => a.PubKey);
return ret;
}

View File

@ -1,6 +1,7 @@
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import Note from "../element/Note";
import Thread from "../element/Thread";
import useThreadFeed from "./feed/ThreadFeed";
export default function EventPage() {
@ -11,9 +12,7 @@ export default function EventPage() {
if(notes) {
return (
<>
{notes?.map(n => <Note key={n.id} data={n}/>)}
</>
<Thread notes={notes}/>
)
}
return (

View File

@ -55,10 +55,8 @@ export default function useThreadFeed(id) {
sub.OnEvent = (e) => {
dispatch(addNote(e));
};
sub.OnEnd = (c) => {
c.RemoveSubscription(sub.Id);
};
system.AddSubscription(sub);
return () => system.RemoveSubscription(sub.Id);
}
}, [system, notes]);