2023-06-30 14:05:57 +00:00
|
|
|
/** @jsx h */
|
2023-09-06 16:29:27 +00:00
|
|
|
import { Component, ComponentChildren, createRef, h } from "https://esm.sh/preact@10.17.1";
|
2023-06-30 14:05:57 +00:00
|
|
|
import { tw } from "https://esm.sh/twind@0.16.16";
|
|
|
|
import { Editor, EditorEvent, EditorModel } from "./editor.tsx";
|
|
|
|
|
2023-09-10 17:56:37 +00:00
|
|
|
import { AboutIcon, CloseIcon, LeftArrowIcon, ReplyIcon } from "./icons/mod.tsx";
|
2023-06-30 14:05:57 +00:00
|
|
|
import { Avatar } from "./components/avatar.tsx";
|
2023-09-19 19:38:38 +00:00
|
|
|
import { IconButtonClass } from "./components/tw.ts";
|
2023-06-30 14:05:57 +00:00
|
|
|
import { sleep } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
|
2023-09-19 19:38:38 +00:00
|
|
|
import { emitFunc } from "../event-bus.ts";
|
2023-06-30 14:05:57 +00:00
|
|
|
|
2023-07-16 15:04:23 +00:00
|
|
|
import { ChatMessage, groupContinuousMessages, sortMessage, urlIsImage } from "./message.ts";
|
2023-08-28 17:58:05 +00:00
|
|
|
import { PublicKey } from "../lib/nostr-ts/key.ts";
|
|
|
|
import { NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
|
2023-07-16 07:19:20 +00:00
|
|
|
import {
|
2023-09-12 14:51:27 +00:00
|
|
|
DirectedMessage_Event,
|
2023-09-16 20:04:48 +00:00
|
|
|
Parsed_Event,
|
2023-07-16 07:19:20 +00:00
|
|
|
PinContact,
|
|
|
|
Profile_Nostr_Event,
|
2023-09-12 14:51:27 +00:00
|
|
|
Text_Note_Event,
|
2023-07-16 07:19:20 +00:00
|
|
|
UnpinContact,
|
|
|
|
} from "../nostr.ts";
|
2023-08-28 20:34:41 +00:00
|
|
|
import { ProfileData, ProfilesSyncer } from "../features/profile.ts";
|
2023-06-30 14:05:57 +00:00
|
|
|
import { MessageThread } from "./dm.tsx";
|
|
|
|
import { UserDetail } from "./user-detail.tsx";
|
|
|
|
import { MessageThreadPanel } from "./message-thread-panel.tsx";
|
2023-07-11 09:49:58 +00:00
|
|
|
import { Database_Contextual_View } from "../database.ts";
|
2023-09-19 19:38:38 +00:00
|
|
|
import { LinkColor, PrimaryTextColor } from "./style/colors.ts";
|
2023-08-28 20:34:41 +00:00
|
|
|
import { getUserInfoFromPublicKey, UserInfo } from "./contact-list.ts";
|
2023-07-05 09:32:02 +00:00
|
|
|
import { EventSyncer } from "./event_syncer.ts";
|
2023-08-29 14:29:48 +00:00
|
|
|
import { ButtonGroup } from "./components/button-group.tsx";
|
2023-09-16 20:04:48 +00:00
|
|
|
import { ProfileCard } from "./profile-card.tsx";
|
|
|
|
import { NoteCard } from "./note-card.tsx";
|
2023-06-30 14:05:57 +00:00
|
|
|
|
2023-08-24 13:41:50 +00:00
|
|
|
export type RightPanelModel = {
|
|
|
|
show: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type DirectMessagePanelUpdate =
|
|
|
|
| {
|
|
|
|
type: "ToggleRightPanel";
|
|
|
|
show: boolean;
|
|
|
|
}
|
|
|
|
| ViewThread
|
2023-09-10 17:56:37 +00:00
|
|
|
| ViewUserDetail
|
2023-09-16 20:04:48 +00:00
|
|
|
| ViewNoteThread
|
2023-09-10 17:56:37 +00:00
|
|
|
| {
|
|
|
|
type: "ViewEventDetail";
|
2023-09-12 14:51:27 +00:00
|
|
|
event: Text_Note_Event | DirectedMessage_Event;
|
2023-09-10 17:56:37 +00:00
|
|
|
};
|
2023-08-24 13:41:50 +00:00
|
|
|
|
2023-09-16 20:04:48 +00:00
|
|
|
export type ViewNoteThread = {
|
|
|
|
type: "ViewNoteThread";
|
|
|
|
event: Parsed_Event;
|
|
|
|
};
|
|
|
|
|
2023-08-24 13:41:50 +00:00
|
|
|
export type ViewThread = {
|
|
|
|
type: "ViewThread";
|
|
|
|
root: NostrEvent;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type ViewUserDetail = {
|
|
|
|
type: "ViewUserDetail";
|
|
|
|
pubkey: PublicKey;
|
|
|
|
};
|
|
|
|
|
2023-06-30 14:05:57 +00:00
|
|
|
interface DirectMessagePanelProps {
|
|
|
|
myPublicKey: PublicKey;
|
|
|
|
editorModel: EditorModel;
|
|
|
|
|
|
|
|
messages: MessageThread[];
|
|
|
|
focusedContent: {
|
|
|
|
type: "MessageThread";
|
|
|
|
data: MessageThread;
|
|
|
|
editor: EditorModel;
|
|
|
|
} | {
|
|
|
|
type: "ProfileData";
|
|
|
|
data?: ProfileData;
|
|
|
|
pubkey: PublicKey;
|
|
|
|
} | undefined;
|
|
|
|
|
|
|
|
rightPanelModel: RightPanelModel;
|
|
|
|
|
2023-07-11 09:49:58 +00:00
|
|
|
db: Database_Contextual_View;
|
2023-09-13 18:27:08 +00:00
|
|
|
emit: emitFunc<
|
2023-06-30 14:05:57 +00:00
|
|
|
EditorEvent | DirectMessagePanelUpdate | PinContact | UnpinContact
|
|
|
|
>;
|
2023-07-05 05:50:34 +00:00
|
|
|
profilesSyncer: ProfilesSyncer;
|
2023-07-05 09:32:02 +00:00
|
|
|
eventSyncer: EventSyncer;
|
2023-07-16 15:04:23 +00:00
|
|
|
allUserInfo: Map<string, UserInfo>;
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
|
|
|
|
2023-09-11 18:09:38 +00:00
|
|
|
// export function MessagePanel(props: DirectMessagePanelProps) {
|
|
|
|
export class MessagePanel extends Component<DirectMessagePanelProps> {
|
|
|
|
render() {
|
|
|
|
const props = this.props;
|
|
|
|
const t = Date.now();
|
|
|
|
let placeholder = "Post your thoughts";
|
|
|
|
if (props.editorModel.target.kind == NostrKind.DIRECT_MESSAGE) {
|
|
|
|
placeholder = `Message @${
|
|
|
|
props.editorModel.target.receiver.name || props.editorModel.target.receiver.pubkey.bech32()
|
|
|
|
}`;
|
|
|
|
}
|
2023-06-30 14:05:57 +00:00
|
|
|
|
2023-09-11 18:09:38 +00:00
|
|
|
let rightPanel;
|
|
|
|
if (props.rightPanelModel.show) {
|
|
|
|
let rightPanelChildren: h.JSX.Element | undefined;
|
|
|
|
if (props.focusedContent) {
|
|
|
|
if (props.focusedContent.type == "MessageThread") {
|
|
|
|
rightPanelChildren = (
|
|
|
|
<MessageThreadPanel
|
2023-09-13 18:27:08 +00:00
|
|
|
emit={props.emit}
|
2023-09-11 18:09:38 +00:00
|
|
|
messages={[props.focusedContent.data.root, ...props.focusedContent.data.replies]}
|
|
|
|
myPublicKey={props.myPublicKey}
|
|
|
|
db={props.db}
|
|
|
|
editorModel={props.focusedContent.editor}
|
|
|
|
profilesSyncer={props.profilesSyncer}
|
|
|
|
eventSyncer={props.eventSyncer}
|
|
|
|
allUserInfo={props.allUserInfo}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
} else if (props.focusedContent.type == "ProfileData") {
|
|
|
|
rightPanelChildren = (
|
|
|
|
<UserDetail
|
|
|
|
targetUserProfile={{
|
|
|
|
name: props.focusedContent?.data?.name,
|
|
|
|
picture: props.focusedContent?.data?.picture,
|
|
|
|
about: props.focusedContent?.data?.about,
|
|
|
|
website: props.focusedContent?.data?.website,
|
|
|
|
}}
|
|
|
|
pubkey={props.focusedContent.pubkey}
|
2023-09-13 18:27:08 +00:00
|
|
|
emit={props.emit}
|
2023-09-11 18:09:38 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
2023-09-11 18:09:38 +00:00
|
|
|
rightPanel = (
|
|
|
|
<RightPanel
|
2023-09-13 18:27:08 +00:00
|
|
|
emit={props.emit}
|
2023-09-11 18:09:38 +00:00
|
|
|
rightPanelModel={props.rightPanelModel}
|
|
|
|
>
|
|
|
|
{rightPanelChildren}
|
|
|
|
</RightPanel>
|
|
|
|
);
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
2023-09-11 18:09:38 +00:00
|
|
|
let vnode = (
|
2023-09-16 20:04:48 +00:00
|
|
|
<div class={tw`flex h-full w-full relative bg-[#36393F]`}>
|
2023-09-11 18:09:38 +00:00
|
|
|
<div class={tw`flex flex-col h-full flex-1 overflow-hidden`}>
|
|
|
|
<div class={tw`flex-1`}></div>
|
|
|
|
{
|
|
|
|
<MessageList
|
|
|
|
myPublicKey={props.myPublicKey}
|
|
|
|
threads={props.messages}
|
2023-09-13 18:27:08 +00:00
|
|
|
emit={props.emit}
|
2023-09-11 18:09:38 +00:00
|
|
|
db={props.db}
|
|
|
|
profilesSyncer={props.profilesSyncer}
|
|
|
|
eventSyncer={props.eventSyncer}
|
|
|
|
allUserInfo={props.allUserInfo}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
{
|
|
|
|
<Editor
|
|
|
|
model={props.editorModel}
|
|
|
|
placeholder={placeholder}
|
|
|
|
maxHeight="30vh"
|
2023-09-13 18:27:08 +00:00
|
|
|
emit={props.emit}
|
2023-09-11 18:09:38 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
{!props.rightPanelModel.show
|
|
|
|
? (
|
|
|
|
<button
|
|
|
|
class={tw`absolute z-10 w-6 h-6 transition-transform duration-100 ease-in-out right-4 top-4${
|
|
|
|
props.rightPanelModel.show ? " rotate-180" : ""
|
|
|
|
} ${IconButtonClass}`}
|
|
|
|
onClick={() => {
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit({
|
2023-09-11 18:09:38 +00:00
|
|
|
type: "ToggleRightPanel",
|
|
|
|
show: !props.rightPanelModel.show,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<LeftArrowIcon
|
|
|
|
class={tw`w-4 h-4`}
|
|
|
|
style={{
|
|
|
|
fill: "#F3F4EA",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</button>
|
|
|
|
)
|
|
|
|
: undefined}
|
|
|
|
{rightPanel}
|
|
|
|
</div>
|
2023-06-30 14:05:57 +00:00
|
|
|
);
|
2023-09-11 18:09:38 +00:00
|
|
|
console.log("DirectMessagePanel:end", Date.now() - t);
|
|
|
|
return vnode;
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
interface MessageListProps {
|
|
|
|
myPublicKey: PublicKey;
|
|
|
|
threads: MessageThread[];
|
2023-07-11 09:49:58 +00:00
|
|
|
db: Database_Contextual_View;
|
2023-09-13 18:27:08 +00:00
|
|
|
emit: emitFunc<DirectMessagePanelUpdate>;
|
2023-07-05 05:50:34 +00:00
|
|
|
profilesSyncer: ProfilesSyncer;
|
2023-07-05 09:32:02 +00:00
|
|
|
eventSyncer: EventSyncer;
|
2023-07-16 15:04:23 +00:00
|
|
|
allUserInfo: Map<string, UserInfo>;
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
interface MessageListState {
|
|
|
|
currentRenderCount: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ItemsOfPerPage = 100;
|
|
|
|
export class MessageList extends Component<MessageListProps, MessageListState> {
|
|
|
|
constructor(public props: MessageListProps) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
messagesULElement = createRef<HTMLUListElement>();
|
|
|
|
state = {
|
|
|
|
currentRenderCount: ItemsOfPerPage,
|
|
|
|
};
|
|
|
|
jitter = new JitterPrevention(100);
|
|
|
|
|
|
|
|
componentWillReceiveProps() {
|
|
|
|
this.setState({
|
|
|
|
currentRenderCount: ItemsOfPerPage,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onScroll = async (e: h.JSX.TargetedUIEvent<HTMLUListElement>) => {
|
|
|
|
if (
|
|
|
|
e.currentTarget.scrollHeight - e.currentTarget.offsetHeight +
|
|
|
|
e.currentTarget.scrollTop < 1000
|
|
|
|
) {
|
|
|
|
const ok = await this.jitter.shouldExecute();
|
|
|
|
if (!ok || this.state.currentRenderCount >= this.props.threads.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.setState({
|
|
|
|
currentRenderCount: Math.min(
|
|
|
|
this.state.currentRenderCount + ItemsOfPerPage,
|
|
|
|
this.props.threads.length,
|
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
sortAndSliceMessage = () => {
|
|
|
|
return sortMessage(this.props.threads)
|
|
|
|
.slice(
|
|
|
|
0,
|
|
|
|
this.state.currentRenderCount,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const t = Date.now();
|
|
|
|
const groups = groupContinuousMessages(this.sortAndSliceMessage(), (pre, cur) => {
|
|
|
|
const sameAuthor = pre.root.event.pubkey == cur.root.event.pubkey;
|
|
|
|
const _66sec =
|
|
|
|
Math.abs(cur.root.created_at.getTime() - pre.root.created_at.getTime()) < 1000 * 60;
|
|
|
|
return sameAuthor && _66sec;
|
|
|
|
});
|
|
|
|
console.log("MessageList:groupContinuousMessages", Date.now() - t);
|
|
|
|
const messageBoxGroups = [];
|
2023-07-16 15:04:23 +00:00
|
|
|
let i = 0;
|
2023-06-30 14:05:57 +00:00
|
|
|
for (const threads of groups) {
|
|
|
|
messageBoxGroups.push(
|
|
|
|
MessageBoxGroup({
|
|
|
|
messageGroup: threads.map((thread) => {
|
2023-07-16 15:04:23 +00:00
|
|
|
i++;
|
2023-06-30 14:05:57 +00:00
|
|
|
return {
|
|
|
|
msg: thread.root,
|
|
|
|
replyCount: thread.replies.length,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
myPublicKey: this.props.myPublicKey,
|
2023-09-13 18:27:08 +00:00
|
|
|
emit: this.props.emit,
|
2023-07-04 06:37:14 +00:00
|
|
|
db: this.props.db,
|
2023-07-05 05:50:34 +00:00
|
|
|
profilesSyncer: this.props.profilesSyncer,
|
2023-07-05 09:32:02 +00:00
|
|
|
eventSyncer: this.props.eventSyncer,
|
2023-07-16 15:04:23 +00:00
|
|
|
allUserInfo: this.props.allUserInfo,
|
2023-06-30 14:05:57 +00:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
2023-07-16 15:04:23 +00:00
|
|
|
console.log(`MessageList:elements ${i}`, Date.now() - t);
|
2023-06-30 14:05:57 +00:00
|
|
|
|
|
|
|
const vNode = (
|
|
|
|
<div
|
|
|
|
class={tw`w-full overflow-hidden`}
|
|
|
|
style={{
|
|
|
|
transform: "perspective(none)",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
onClick={() => {
|
|
|
|
if (this.messagesULElement.current) {
|
|
|
|
this.messagesULElement.current.scrollTo({
|
|
|
|
top: this.messagesULElement.current.scrollHeight,
|
|
|
|
left: 0,
|
|
|
|
behavior: "smooth",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
class={tw`${IconButtonClass} fixed z-10 bottom-8 right-4 h-10 w-10 rotate-[-90deg] bg-[#42464D] hover:bg-[#2F3136]`}
|
|
|
|
>
|
|
|
|
<LeftArrowIcon
|
|
|
|
class={tw`w-6 h-6`}
|
|
|
|
style={{
|
|
|
|
fill: "#F3F4EA",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</button>
|
|
|
|
<ul
|
2023-07-03 13:42:11 +00:00
|
|
|
class={tw`w-full h-full overflow-y-auto overflow-x-hidden py-8 px-2 flex flex-col-reverse`}
|
2023-06-30 14:05:57 +00:00
|
|
|
ref={this.messagesULElement}
|
|
|
|
onScroll={this.onScroll}
|
|
|
|
>
|
|
|
|
{messageBoxGroups}
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
console.log("MessageList:end", Date.now() - t);
|
|
|
|
return vNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function MessageBoxGroup(props: {
|
|
|
|
messageGroup: {
|
|
|
|
msg: ChatMessage;
|
|
|
|
replyCount: number;
|
|
|
|
}[];
|
|
|
|
myPublicKey: PublicKey;
|
2023-07-11 09:49:58 +00:00
|
|
|
db: Database_Contextual_View;
|
2023-07-16 15:04:23 +00:00
|
|
|
allUserInfo: Map<string, UserInfo>;
|
2023-09-13 18:27:08 +00:00
|
|
|
emit: emitFunc<DirectMessagePanelUpdate | ViewUserDetail>;
|
2023-07-05 05:50:34 +00:00
|
|
|
profilesSyncer: ProfilesSyncer;
|
2023-07-05 09:32:02 +00:00
|
|
|
eventSyncer: EventSyncer;
|
2023-06-30 14:05:57 +00:00
|
|
|
}) {
|
|
|
|
// const t = Date.now();
|
|
|
|
|
2023-08-24 13:41:50 +00:00
|
|
|
const messageGroups = props.messageGroup.reverse();
|
|
|
|
if (messageGroups.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const first_group = messageGroups[0];
|
|
|
|
const rows = [];
|
|
|
|
rows.push(
|
|
|
|
<li
|
|
|
|
class={tw`px-4 hover:bg-[#32353B] w-full max-w-full flex items-start pr-8 group relative`}
|
|
|
|
>
|
2023-09-13 18:27:08 +00:00
|
|
|
{MessageActions(first_group.msg.event, props.emit)}
|
2023-08-24 13:41:50 +00:00
|
|
|
<Avatar
|
|
|
|
class={tw`h-8 w-8 mt-[0.45rem] mr-2`}
|
|
|
|
picture={getUserInfoFromPublicKey(first_group.msg.event.publicKey, props.allUserInfo)
|
|
|
|
?.profile?.profile.picture}
|
|
|
|
onClick={() => {
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit({
|
2023-08-24 13:41:50 +00:00
|
|
|
type: "ViewUserDetail",
|
|
|
|
pubkey: first_group.msg.event.publicKey,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div
|
|
|
|
class={tw`flex-1`}
|
|
|
|
style={{
|
|
|
|
maxWidth: "calc(100% - 2.75rem)",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{NameAndTime(
|
|
|
|
first_group.msg.event.publicKey,
|
|
|
|
getUserInfoFromPublicKey(first_group.msg.event.publicKey, props.allUserInfo)
|
|
|
|
?.profile?.profile,
|
|
|
|
props.myPublicKey,
|
|
|
|
first_group.msg.created_at,
|
|
|
|
)}
|
|
|
|
<pre
|
|
|
|
class={tw`text-[#DCDDDE] whitespace-pre-wrap break-words font-roboto`}
|
|
|
|
>
|
2023-07-16 15:04:23 +00:00
|
|
|
{ParseMessageContent(
|
2023-08-24 13:41:50 +00:00
|
|
|
first_group.msg,
|
2023-07-16 15:04:23 +00:00
|
|
|
props.allUserInfo,
|
|
|
|
props.profilesSyncer,
|
|
|
|
props.eventSyncer,
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit,
|
2023-07-16 15:04:23 +00:00
|
|
|
)}
|
2023-08-24 13:41:50 +00:00
|
|
|
</pre>
|
|
|
|
{first_group.replyCount > 0
|
|
|
|
? (
|
|
|
|
<div class={tw`flex items-center mb-[0.45rem] ml-[1rem]`}>
|
|
|
|
<span
|
|
|
|
class={tw`text-[#A6A8AA] font-bold hover:underline cursor-pointer text-[0.8rem]`}
|
|
|
|
onClick={() => {
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit({
|
2023-08-24 13:41:50 +00:00
|
|
|
type: "ViewThread",
|
|
|
|
root: first_group.msg.event,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{first_group.replyCount} replies
|
|
|
|
</span>
|
2023-06-30 14:05:57 +00:00
|
|
|
</div>
|
2023-08-24 13:41:50 +00:00
|
|
|
)
|
|
|
|
: undefined}
|
|
|
|
</div>
|
|
|
|
</li>,
|
|
|
|
);
|
|
|
|
|
|
|
|
for (let i = 1; i < messageGroups.length; i++) {
|
|
|
|
const msg = messageGroups[i];
|
|
|
|
rows.push(
|
|
|
|
<li
|
|
|
|
class={tw`px-4 hover:bg-[#32353B] w-full max-w-full flex items-start pr-8 group relative`}
|
|
|
|
>
|
2023-09-13 18:27:08 +00:00
|
|
|
{MessageActions(msg.msg.event, props.emit)}
|
2023-08-24 13:41:50 +00:00
|
|
|
{Time(msg.msg.created_at)}
|
|
|
|
<div
|
|
|
|
class={tw`flex-1`}
|
|
|
|
style={{
|
|
|
|
maxWidth: "calc(100% - 2.75rem)",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<pre
|
|
|
|
class={tw`text-[#DCDDDE] whitespace-pre-wrap break-words font-roboto`}
|
|
|
|
>
|
|
|
|
{ParseMessageContent(
|
|
|
|
msg.msg,
|
|
|
|
props.allUserInfo,
|
|
|
|
props.profilesSyncer,
|
|
|
|
props.eventSyncer,
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit,
|
2023-08-24 13:41:50 +00:00
|
|
|
)}
|
|
|
|
</pre>
|
|
|
|
{msg.replyCount > 0
|
|
|
|
? (
|
|
|
|
<div class={tw`flex items-center mb-[0.45rem] ml-[1rem]`}>
|
|
|
|
<span
|
|
|
|
class={tw`text-[#A6A8AA] font-bold hover:underline cursor-pointer text-[0.8rem]`}
|
|
|
|
onClick={() => {
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit({
|
2023-08-24 13:41:50 +00:00
|
|
|
type: "ViewThread",
|
|
|
|
root: msg.msg.event,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{msg.replyCount} replies
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
: undefined}
|
|
|
|
</div>
|
|
|
|
</li>,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const vnode = (
|
|
|
|
<ul class={tw`py-2`}>
|
|
|
|
{rows}
|
2023-06-30 14:05:57 +00:00
|
|
|
</ul>
|
|
|
|
);
|
|
|
|
|
|
|
|
// console.log("MessageBoxGroup", Date.now() - t);
|
|
|
|
return vnode;
|
|
|
|
}
|
|
|
|
|
2023-09-10 17:56:37 +00:00
|
|
|
function MessageActions(
|
2023-09-12 14:51:27 +00:00
|
|
|
event: Text_Note_Event | DirectedMessage_Event,
|
2023-09-10 17:56:37 +00:00
|
|
|
emit: emitFunc<ViewThread | DirectMessagePanelUpdate>,
|
2023-08-29 14:29:48 +00:00
|
|
|
) {
|
|
|
|
return (
|
|
|
|
<ButtonGroup
|
|
|
|
class={tw`hidden group-hover:flex absolute top-[-0.75rem] right-[3rem]`}
|
|
|
|
style={{
|
|
|
|
boxShadow: "2px 2px 5px 0 black",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
class={tw`w-6 h-6 flex items-center justify-center`}
|
|
|
|
onClick={() => {
|
2023-09-10 17:56:37 +00:00
|
|
|
emit({
|
2023-08-29 14:29:48 +00:00
|
|
|
type: "ViewThread",
|
|
|
|
root: event,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<ReplyIcon
|
|
|
|
class={tw`w-4 h-4 scale-150`}
|
|
|
|
style={{
|
|
|
|
fill: PrimaryTextColor,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</button>
|
2023-09-10 17:56:37 +00:00
|
|
|
|
|
|
|
<button
|
|
|
|
class={tw`w-6 h-6 flex items-center justify-center`}
|
|
|
|
onClick={async () => {
|
|
|
|
emit({
|
|
|
|
type: "ViewEventDetail",
|
|
|
|
event: event,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<AboutIcon
|
|
|
|
class={tw`w-4 h-4 scale-150`}
|
|
|
|
style={{
|
|
|
|
fill: PrimaryTextColor,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</button>
|
2023-08-29 14:29:48 +00:00
|
|
|
</ButtonGroup>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-24 13:41:50 +00:00
|
|
|
export function Time(created_at: Date) {
|
2023-06-30 14:05:57 +00:00
|
|
|
return (
|
|
|
|
<div class={tw`w-8 mr-2`}>
|
|
|
|
<span
|
|
|
|
class={tw`text-[#A3A6AA] text-[0.8rem] hidden group-hover:inline-block`}
|
|
|
|
>
|
2023-08-24 13:41:50 +00:00
|
|
|
{created_at.toTimeString().slice(0, 5)}
|
2023-06-30 14:05:57 +00:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-24 13:41:50 +00:00
|
|
|
export function NameAndTime(
|
|
|
|
author: PublicKey,
|
|
|
|
author_profile: ProfileData | undefined,
|
|
|
|
myPublicKey: PublicKey,
|
|
|
|
created_at: Date,
|
|
|
|
) {
|
2023-09-12 15:25:50 +00:00
|
|
|
let show = author.bech32();
|
|
|
|
if (author.hex == myPublicKey.hex) {
|
|
|
|
show = "Me";
|
|
|
|
} else if (author_profile?.name) {
|
|
|
|
show = author_profile.name;
|
|
|
|
}
|
2023-08-24 13:41:50 +00:00
|
|
|
|
2023-09-12 15:25:50 +00:00
|
|
|
return (
|
|
|
|
<p class={tw`overflow-hidden flex`}>
|
|
|
|
<p class={tw`text-[#FFFFFF] text-[0.9rem] truncate`}>
|
|
|
|
{show}
|
2023-06-30 14:05:57 +00:00
|
|
|
</p>
|
2023-09-12 15:25:50 +00:00
|
|
|
<p class={tw`text-[#A3A6AA] ml-4 text-[0.8rem] whitespace-nowrap`}>
|
|
|
|
{created_at.toLocaleString()}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
);
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 05:50:34 +00:00
|
|
|
export function ParseMessageContent(
|
|
|
|
message: ChatMessage,
|
2023-07-16 15:04:23 +00:00
|
|
|
allUserInfo: Map<string, UserInfo>,
|
2023-07-05 05:50:34 +00:00
|
|
|
profilesSyncer: ProfilesSyncer,
|
2023-07-05 09:32:02 +00:00
|
|
|
eventSyncer: EventSyncer,
|
2023-09-16 20:04:48 +00:00
|
|
|
emit: emitFunc<ViewUserDetail | ViewThread | ViewNoteThread>,
|
2023-07-05 05:50:34 +00:00
|
|
|
) {
|
2023-07-04 07:15:21 +00:00
|
|
|
if (message.type == "image") {
|
|
|
|
return <img src={message.content} />;
|
|
|
|
}
|
2023-07-05 09:32:02 +00:00
|
|
|
|
2023-07-09 10:22:09 +00:00
|
|
|
const vnode = [];
|
|
|
|
let start = 0;
|
2023-07-16 15:04:23 +00:00
|
|
|
for (const item of message.event.parsedContentItems) {
|
2023-07-09 10:22:09 +00:00
|
|
|
vnode.push(message.content.slice(start, item.start));
|
2023-07-16 15:04:23 +00:00
|
|
|
const itemStr = message.content.slice(item.start, item.end + 1);
|
2023-07-04 07:02:43 +00:00
|
|
|
switch (item.type) {
|
|
|
|
case "url":
|
2023-07-16 15:04:23 +00:00
|
|
|
{
|
|
|
|
if (urlIsImage(itemStr)) {
|
|
|
|
vnode.push(<img src={itemStr} />);
|
|
|
|
} else {
|
|
|
|
vnode.push(
|
|
|
|
<a target="_blank" class={tw`hover:underline text-[${LinkColor}]`} href={itemStr}>
|
|
|
|
{itemStr}
|
|
|
|
</a>,
|
|
|
|
);
|
|
|
|
}
|
2023-07-04 07:02:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "npub":
|
2023-07-16 15:04:23 +00:00
|
|
|
{
|
2023-09-16 20:04:48 +00:00
|
|
|
const userInfo = allUserInfo.get(item.pubkey.hex);
|
2023-07-16 15:04:23 +00:00
|
|
|
if (userInfo) {
|
|
|
|
const profile = userInfo.profile;
|
|
|
|
if (profile) {
|
|
|
|
vnode.push(
|
2023-09-16 20:04:48 +00:00
|
|
|
<ProfileCard
|
|
|
|
profileData={profile.profile}
|
|
|
|
publicKey={item.pubkey}
|
|
|
|
emit={emit}
|
|
|
|
/>,
|
2023-07-16 15:04:23 +00:00
|
|
|
);
|
2023-08-24 17:32:52 +00:00
|
|
|
break;
|
2023-07-16 15:04:23 +00:00
|
|
|
} else {
|
2023-09-16 20:04:48 +00:00
|
|
|
profilesSyncer.add(item.pubkey.hex);
|
2023-07-16 15:04:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-09-16 20:04:48 +00:00
|
|
|
profilesSyncer.add(item.pubkey.hex);
|
2023-07-16 15:04:23 +00:00
|
|
|
}
|
2023-08-24 17:32:52 +00:00
|
|
|
vnode.push(
|
2023-09-16 20:04:48 +00:00
|
|
|
<ProfileCard publicKey={item.pubkey} emit={emit} />,
|
2023-08-24 17:32:52 +00:00
|
|
|
);
|
2023-07-04 07:02:43 +00:00
|
|
|
}
|
|
|
|
break;
|
2023-07-05 09:32:02 +00:00
|
|
|
case "note":
|
2023-07-16 15:04:23 +00:00
|
|
|
{
|
|
|
|
const event = eventSyncer.syncEvent(item.noteID);
|
|
|
|
if (event instanceof Promise) {
|
2023-09-16 20:04:48 +00:00
|
|
|
vnode.push(itemStr);
|
2023-07-16 15:04:23 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-09-16 20:04:48 +00:00
|
|
|
if (event.kind == NostrKind.DIRECT_MESSAGE) {
|
|
|
|
allUserInfo.get(event.pubkey)?.events.find((e) => event.id == e.id);
|
|
|
|
vnode.push(Card(event, emit, allUserInfo));
|
|
|
|
} else if (event.kind == NostrKind.TEXT_NOTE || event.kind == NostrKind.META_DATA) {
|
|
|
|
vnode.push(Card(event, emit, allUserInfo));
|
|
|
|
}
|
2023-07-05 09:32:02 +00:00
|
|
|
}
|
|
|
|
break;
|
2023-07-04 07:02:43 +00:00
|
|
|
case "tag":
|
|
|
|
// todo
|
|
|
|
break;
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
2023-07-09 10:22:09 +00:00
|
|
|
|
|
|
|
start = item.end + 1;
|
2023-06-30 14:05:57 +00:00
|
|
|
}
|
2023-07-09 10:22:09 +00:00
|
|
|
vnode.push(message.content.slice(start));
|
2023-07-04 07:02:43 +00:00
|
|
|
|
2023-06-30 14:05:57 +00:00
|
|
|
return vnode;
|
|
|
|
}
|
|
|
|
|
2023-09-16 20:04:48 +00:00
|
|
|
function Card(
|
|
|
|
event: Profile_Nostr_Event | Text_Note_Event | DirectedMessage_Event,
|
|
|
|
emit: emitFunc<ViewThread | ViewUserDetail | ViewNoteThread>,
|
2023-07-16 15:04:23 +00:00
|
|
|
allUserInfo: Map<string, UserInfo>,
|
2023-07-16 07:19:20 +00:00
|
|
|
) {
|
|
|
|
switch (event.kind) {
|
|
|
|
case NostrKind.META_DATA:
|
2023-09-16 20:04:48 +00:00
|
|
|
return <ProfileCard emit={emit} publicKey={event.publicKey} profileData={event.profile} />;
|
2023-07-16 07:19:20 +00:00
|
|
|
case NostrKind.TEXT_NOTE:
|
|
|
|
case NostrKind.DIRECT_MESSAGE:
|
2023-09-16 20:04:48 +00:00
|
|
|
const profile = allUserInfo.get(event.pubkey)?.profile?.profile;
|
|
|
|
return <NoteCard emit={emit} event={event} profileData={profile} />;
|
|
|
|
// default:
|
|
|
|
// return (
|
|
|
|
// <div class={tw`px-4 my-1 py-2 border-2 border-[${PrimaryTextColor}4D] rounded-lg py-1 flex`}>
|
|
|
|
// {event.content}
|
|
|
|
// </div>
|
|
|
|
// );
|
2023-07-16 07:19:20 +00:00
|
|
|
}
|
2023-07-05 09:32:02 +00:00
|
|
|
}
|
|
|
|
|
2023-06-30 14:05:57 +00:00
|
|
|
type RightPanelProps = {
|
2023-09-13 18:27:08 +00:00
|
|
|
emit: emitFunc<DirectMessagePanelUpdate>;
|
2023-06-30 14:05:57 +00:00
|
|
|
rightPanelModel: RightPanelModel;
|
|
|
|
children: ComponentChildren;
|
|
|
|
};
|
|
|
|
|
|
|
|
function RightPanel(props: RightPanelProps) {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
class={tw`mobile:w-full desktop:w-96 bg-[#2F3136] overflow-hidden overflow-y-auto relative${
|
|
|
|
props.rightPanelModel.show ? " block" : " hidden"
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
class={tw`w-6 min-w-[1.5rem] h-6 ml-4 ${IconButtonClass} hover:bg-[#36393F] absolute right-2 top-3 z-10`}
|
|
|
|
onClick={() => {
|
2023-09-13 18:27:08 +00:00
|
|
|
props.emit({
|
2023-06-30 14:05:57 +00:00
|
|
|
type: "ToggleRightPanel",
|
|
|
|
show: false,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<CloseIcon
|
|
|
|
class={tw`w-4 h-4`}
|
|
|
|
style={{
|
|
|
|
stroke: "rgb(185, 187, 190)",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</button>
|
|
|
|
{props.children}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
class JitterPrevention {
|
|
|
|
constructor(private duration: number) {}
|
|
|
|
cancel: ((value: void) => void) | undefined;
|
|
|
|
async shouldExecute(): Promise<boolean> {
|
|
|
|
if (this.cancel) {
|
|
|
|
this.cancel();
|
|
|
|
this.cancel = undefined;
|
|
|
|
return this.shouldExecute();
|
|
|
|
}
|
|
|
|
const p = new Promise<void>((resolve) => {
|
|
|
|
this.cancel = resolve;
|
|
|
|
});
|
|
|
|
const cancelled = await sleep(this.duration, p);
|
|
|
|
return !cancelled;
|
|
|
|
}
|
|
|
|
}
|