render replies (#432)

Co-authored-by: bob2402 <dv4xd@proton.me>
This commit is contained in:
BlowaterNostr 2024-03-21 14:04:32 +08:00 committed by GitHub
parent 689b7bd019
commit 1ab5e65191
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,4 +1,11 @@
import { Component, createRef, h } from "https://esm.sh/preact@10.17.1"; import {
Component,
ComponentChildren,
createRef,
Fragment,
h,
RefObject,
} from "https://esm.sh/preact@10.17.1";
import { PublicKey } from "../../libs/nostr.ts/key.ts"; import { PublicKey } from "../../libs/nostr.ts/key.ts";
import { RelayRecordGetter } from "../database.ts"; import { RelayRecordGetter } from "../database.ts";
import { emitFunc } from "../event-bus.ts"; import { emitFunc } from "../event-bus.ts";
@ -25,7 +32,7 @@ import { Parsed_Event } from "../nostr.ts";
import { NoteID } from "../../libs/nostr.ts/nip19.ts"; import { NoteID } from "../../libs/nostr.ts/nip19.ts";
import { robohash } from "./relay-detail.tsx"; import { robohash } from "./relay-detail.tsx";
interface MessageListProps { interface Props {
myPublicKey: PublicKey; myPublicKey: PublicKey;
messages: ChatMessage[]; messages: ChatMessage[];
emit: emitFunc<DirectMessagePanelUpdate | SelectConversation | SyncEvent>; emit: emitFunc<DirectMessagePanelUpdate | SelectConversation | SyncEvent>;
@ -42,7 +49,7 @@ interface MessageListState {
const ItemsOfPerPage = 50; const ItemsOfPerPage = 50;
export class MessageList extends Component<MessageListProps, MessageListState> { export class MessageList extends Component<Props, MessageListState> {
readonly messagesULElement = createRef<HTMLUListElement>(); readonly messagesULElement = createRef<HTMLUListElement>();
state = { state = {
@ -51,7 +58,7 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
jitter = new JitterPrevention(100); jitter = new JitterPrevention(100);
async componentDidUpdate(previousProps: Readonly<MessageListProps>) { async componentDidUpdate(previousProps: Readonly<Props>) {
const newest = last(this.props.messages); const newest = last(this.props.messages);
const pre_newest = last(previousProps.messages); const pre_newest = last(previousProps.messages);
if ( if (
@ -74,12 +81,12 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
const sameAuthor = pre.event.pubkey == cur.event.pubkey; const sameAuthor = pre.event.pubkey == cur.event.pubkey;
const _66sec = Math.abs(cur.created_at.getTime() - pre.created_at.getTime()) < const _66sec = Math.abs(cur.created_at.getTime() - pre.created_at.getTime()) <
1000 * 60; 1000 * 60;
return sameAuthor && _66sec; const is_not_reply = cur.event.parsedTags.e.length === 0; // todo: make a isReply(event) function
return sameAuthor && _66sec && is_not_reply;
}); });
const messageBoxGroups = []; const messageBoxGroups = [];
for (const messages of groups) { for (const messages of groups) {
const profileEvent = this.props.getters.profileGetter const profileEvent = this.props.getters.profileGetter.getProfilesByPublicKey(messages[0].author);
.getProfilesByPublicKey(messages[0].author);
messageBoxGroups.push( messageBoxGroups.push(
MessageBoxGroup({ MessageBoxGroup({
messages: messages, messages: messages,
@ -92,35 +99,14 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
} }
return ( return (
<div <div class="w-full flex flex-col overflow-auto">
class={`w-full overflow-hidden ${BackgroundColor_MessagePanel}`} <button class={`${IconButtonClass} shrink-0`} onClick={this.prePage}>
style={{ load earlier messages
transform: "perspective(none)", </button>
}} {MessageListView(this.goToButtom, this.messagesULElement, messageBoxGroups)}
> <button class={`${IconButtonClass} shrink-0`} onClick={this.nextPage}>
<button load more messages
onClick={() => this.goToButtom(true)}
class={`${IconButtonClass} fixed z-10 bottom-8 right-4 h-10 w-10 rotate-[-90deg] bg-[#42464D] hover:bg-[#2F3136]`}
>
<LeftArrowIcon
class={`w-6 h-6`}
style={{
fill: "#F3F4EA",
}}
/>
</button> </button>
<ul
class={`w-full h-full overflow-y-auto overflow-x-hidden py-9 mobile:py-2 px-2 mobile:px-0 flex flex-col`}
ref={this.messagesULElement}
>
<button class={`${IconButtonClass}`} onClick={this.prePage}>
load earlier messages
</button>
{messageBoxGroups}
<button class={`${IconButtonClass}`} onClick={this.nextPage}>
load more messages
</button>
</ul>
</div> </div>
); );
} }
@ -170,7 +156,40 @@ export class MessageList extends Component<MessageListProps, MessageListState> {
}; };
} }
export class MessageList_V0 extends Component<MessageListProps> { function MessageListView(
goToButtom: (smooth: boolean) => void,
messagesULElement: RefObject<HTMLUListElement>,
messageBoxGroups: ComponentChildren,
) {
return (
<div
class={`w-full overflow-hidden ${BackgroundColor_MessagePanel}`}
style={{
transform: "perspective(none)",
}}
>
<button
onClick={() => goToButtom(true)}
class={`${IconButtonClass} fixed z-10 bottom-8 right-4 h-10 w-10 rotate-[-90deg] bg-[#42464D] hover:bg-[#2F3136]`}
>
<LeftArrowIcon
class={`w-6 h-6`}
style={{
fill: "#F3F4EA",
}}
/>
</button>
<ul
class={`w-full h-full overflow-y-auto overflow-x-hidden py-9 mobile:py-2 px-2 mobile:px-0 flex flex-col`}
ref={messagesULElement}
>
{messageBoxGroups}
</ul>
</div>
);
}
export class MessageList_V0 extends Component<Props> {
readonly messagesULElement = createRef<HTMLUListElement>(); readonly messagesULElement = createRef<HTMLUListElement>();
jitter = new JitterPrevention(100); jitter = new JitterPrevention(100);
@ -179,7 +198,7 @@ export class MessageList_V0 extends Component<MessageListProps> {
this.goToButtom(false); this.goToButtom(false);
} }
componentDidUpdate(previousProps: Readonly<MessageListProps>): void { componentDidUpdate(previousProps: Readonly<Props>): void {
// todo: this is not a correct check of if new message is received // todo: this is not a correct check of if new message is received
// a better check is to see if the // a better check is to see if the
// current newest message is newer than previous newest message // current newest message is newer than previous newest message
@ -190,7 +209,6 @@ export class MessageList_V0 extends Component<MessageListProps> {
render() { render() {
const messages_to_render = this.sortAndSliceMessage(); const messages_to_render = this.sortAndSliceMessage();
console.log(messages_to_render);
const groups = groupContinuousMessages(messages_to_render, (pre, cur) => { const groups = groupContinuousMessages(messages_to_render, (pre, cur) => {
const sameAuthor = pre.event.pubkey == cur.event.pubkey; const sameAuthor = pre.event.pubkey == cur.event.pubkey;
const _66sec = Math.abs(cur.created_at.getTime() - pre.created_at.getTime()) < const _66sec = Math.abs(cur.created_at.getTime() - pre.created_at.getTime()) <
@ -212,33 +230,7 @@ export class MessageList_V0 extends Component<MessageListProps> {
); );
} }
return ( return MessageListView(this.goToButtom, this.messagesULElement, messageBoxGroups);
<div
class={`w-full overflow-hidden ${BackgroundColor_MessagePanel}`}
style={{
transform: "perspective(none)",
}}
>
<button
id="go to bottom"
onClick={() => this.goToButtom(true)}
class={`${IconButtonClass} fixed z-10 bottom-8 right-4 h-10 w-10 rotate-[-90deg] bg-[#42464D] hover:bg-[#2F3136]`}
>
<LeftArrowIcon
class={`w-6 h-6`}
style={{
fill: "#F3F4EA",
}}
/>
</button>
<ul
class={`w-full h-full overflow-y-auto overflow-x-hidden py-9 mobile:py-2 px-2 mobile:px-0 flex flex-col`}
ref={this.messagesULElement}
>
{messageBoxGroups}
</ul>
</div>
);
} }
sortAndSliceMessage = () => { sortAndSliceMessage = () => {
@ -292,46 +284,72 @@ function MessageBoxGroup(props: {
}) { }) {
const first_message = props.messages[0]; const first_message = props.messages[0];
const rows = []; const rows = [];
// check if the first message is a reply message
function isReply(event: Parsed_Event) {
if (event.parsedTags.e.length == 0) {
return;
}
const reply_to_event = props.getters.getEventByID(event.parsedTags.e[0]);
if (!reply_to_event) {
return <ReplyTo unknown noteId={NoteID.FromString(event.parsedTags.e[0])} />;
}
let author = reply_to_event.publicKey.bech32();
let picture = robohash(reply_to_event.publicKey.hex);
if (reply_to_event.pubkey) {
const profile = props.getters.profileGetter.getProfilesByPublicKey(reply_to_event.publicKey);
if (profile) {
author = profile.profile.name || profile.profile.display_name ||
reply_to_event?.publicKey.bech32();
picture = profile.profile.picture || robohash(reply_to_event.publicKey.hex);
}
}
return <ReplyTo content={reply_to_event.content} replyName={author} replayPic={picture} />;
}
rows.push( rows.push(
<li <li
class={`px-4 hover:bg-[#32353B] w-full max-w-full flex items-start pr-8 mobile:pr-4 group relative ${ class={`px-4 hover:bg-[#32353B] w-full max-w-full flex flex-col pr-8 mobile:pr-4 group relative ${
isMobile() ? "select-none" : "" isMobile() ? "select-none" : ""
}`} }`}
> >
{MessageActions(first_message, props.emit)} {MessageActions(first_message, props.emit)}
<Avatar {isReply(first_message.event)}
class={`h-8 w-8 mt-[0.45rem] mr-2`} <div class="flex items-start">
picture={props.authorProfile?.picture || <Avatar
robohash(first_message.author.hex)} class={`h-8 w-8 mt-[0.45rem] mr-2`}
onClick={() => { picture={props.authorProfile?.picture ||
props.emit({ robohash(first_message.author.hex)}
type: "ViewUserDetail", onClick={() => {
pubkey: first_message.author, props.emit({
}); type: "ViewUserDetail",
}} pubkey: first_message.author,
/> });
}}
/>
<div <div
class={`flex-1`} class={`flex-1`}
style={{ style={{
maxWidth: "calc(100% - 2.75rem)", maxWidth: "calc(100% - 2.75rem)",
}} }}
>
{NameAndTime(
first_message.author,
props.authorProfile,
props.myPublicKey,
first_message.created_at,
)}
<pre
class={`text-[#DCDDDE] whitespace-pre-wrap break-words font-roboto text-sm`}
> >
{ParseMessageContent( {NameAndTime(
first_message, first_message.author,
props.emit, props.authorProfile,
props.getters, props.myPublicKey,
)} first_message.created_at,
</pre> )}
<pre
class={`text-[#DCDDDE] whitespace-pre-wrap break-words font-roboto text-sm`}
>
{ParseMessageContent(
first_message,
props.emit,
props.getters,
)}
</pre>
</div>
</div> </div>
</li>, </li>,
); );
@ -368,7 +386,6 @@ function MessageBoxGroup(props: {
</ul> </ul>
); );
// console.log("MessageBoxGroup", Date.now() - t);
return vnode; return vnode;
} }
@ -410,3 +427,37 @@ function last<T>(array: Array<T>): T | undefined {
return array[array.length - 1]; return array[array.length - 1];
} }
} }
function ReplyTo(
props: { unknown?: false; content: string; replyName: string; replayPic: string } | {
unknown: true;
noteId: NoteID;
},
) {
return (
<div class="w-full flex flex-row">
<div class="w-10 h-5 shrink-0">
<div class="w-5 h-2.5 border-l-2 border-t-2 rounded-tl translate-y-2.5 translate-x-4 border-[#4F5058]" />
</div>
<div class="flex flex-row w-full justify-start items-center text-[#A3A6AA] gap-2 font-roboto text-sm pr-5">
{props.unknown
? (
<div class="overflow-hidden whitespace-nowrap text-overflow-ellipsis">
{props.noteId.bech32()}
</div>
)
: (
<>
<Avatar class="h-4 w-4 shrink-0" picture={props.replayPic || ""} />
<div class="whitespace-nowrap md:shrink-0 truncate w-30">
@{props.replyName}
</div>
<div class="overflow-hidden whitespace-nowrap truncate text-overflow-ellipsis w-[90%]">
{props.content}
</div>
</>
)}
</div>
</div>
);
}