Thread progess
This commit is contained in:
parent
e617d6d528
commit
c454f5c7aa
@ -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
21
src/element/Thread.js
Normal 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()}/>)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -58,4 +58,9 @@ input[type="text"] {
|
||||
|
||||
.f-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
line-height: 1.3em;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user