mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
parent
689b7bd019
commit
1ab5e65191
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user