mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 22:44:26 +01:00
update
This commit is contained in:
@@ -6,24 +6,10 @@ type ConfigProps = {
|
||||
};
|
||||
|
||||
const RelayConfig = () => {
|
||||
const { config, setConfig } = useConfig();
|
||||
const { config, setConfig, addRelay, removeRelay } = useConfig();
|
||||
|
||||
const [relayUrlInput, setRelayUrlInput] = createSignal<string>('');
|
||||
|
||||
const addRelay = (relayUrl: string) => {
|
||||
setConfig((current) => ({
|
||||
...current,
|
||||
relayUrls: [...current.relayUrls, relayUrl],
|
||||
}));
|
||||
};
|
||||
|
||||
const removeRelay = (relayUrl: string) => {
|
||||
setConfig((current) => ({
|
||||
...current,
|
||||
relayUrls: current.relayUrls.filter((e) => e !== relayUrl),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleClickAddRelay: JSX.EventHandler<HTMLFormElement, Event> = (ev) => {
|
||||
ev.preventDefault();
|
||||
const relayUrl = ev.currentTarget?.relayUrl?.value as string | undefined;
|
||||
|
||||
@@ -1,25 +1,75 @@
|
||||
import { createSignal, createMemo, onMount, type Component, type JSX } from 'solid-js';
|
||||
import { createSignal, createMemo, onMount, For, type Component, type JSX, Show } from 'solid-js';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import PaperAirplane from 'heroicons/24/solid/paper-airplane.svg';
|
||||
import XMark from 'heroicons/24/outline/x-mark.svg';
|
||||
|
||||
import UserNameDisplay from '@/components/UserDisplayName';
|
||||
|
||||
import eventWrapper from '@/core/event';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useCommands from '@/nostr/useCommands';
|
||||
import usePubkey from '@/nostr/usePubkey';
|
||||
|
||||
type NotePostFormProps = {
|
||||
ref?: HTMLTextAreaElement | undefined;
|
||||
onPost: (textNote: { content: string }) => void;
|
||||
replyTo?: NostrEvent;
|
||||
mode?: 'normal' | 'reply';
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
const [text, setText] = createSignal<string>('');
|
||||
const placeholder = (mode: NotePostFormProps['mode']) => {
|
||||
switch (mode) {
|
||||
case 'normal':
|
||||
return 'いまどうしてる?';
|
||||
case 'reply':
|
||||
return '返信を投稿';
|
||||
default:
|
||||
return 'いまどうしてる?';
|
||||
}
|
||||
};
|
||||
|
||||
const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
let textAreaRef: HTMLTextAreaElement | undefined;
|
||||
|
||||
const { config } = useConfig();
|
||||
const getPubkey = usePubkey();
|
||||
const commands = useCommands();
|
||||
|
||||
const [text, setText] = createSignal<string>('');
|
||||
const clearText = () => setText('');
|
||||
|
||||
const replyTo = () => props.replyTo && eventWrapper(props.replyTo);
|
||||
const mode = () => props.mode ?? 'normal';
|
||||
const notifyPubkeys = createMemo(() => replyTo()?.mentionedPubkeys() ?? []);
|
||||
|
||||
const handleChangeText: JSX.EventHandler<HTMLTextAreaElement, Event> = (ev) => {
|
||||
setText(ev.currentTarget.value);
|
||||
};
|
||||
|
||||
// TODO 投稿完了したかどうかの検知をしたい
|
||||
const submit = () => {
|
||||
props.onPost({ content: text() });
|
||||
clearText();
|
||||
const pubkey = getPubkey();
|
||||
if (pubkey == null) {
|
||||
console.error('pubkey is not available');
|
||||
return;
|
||||
}
|
||||
commands
|
||||
.publishTextNote({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey,
|
||||
content: text(),
|
||||
notifyPubkeys: notifyPubkeys(),
|
||||
rootEventId: replyTo()?.rootEvent()?.id ?? replyTo()?.id,
|
||||
replyEventId: replyTo()?.id,
|
||||
})
|
||||
.then(() => {
|
||||
console.log('succeeded to post');
|
||||
clearText();
|
||||
props?.onClose();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('error', err);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit: JSX.EventHandler<HTMLFormElement, Event> = (ev) => {
|
||||
@@ -37,23 +87,55 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
|
||||
const submitDisabled = createMemo(() => text().trim().length === 0);
|
||||
|
||||
onMount(() => {
|
||||
setTimeout(() => {
|
||||
textAreaRef?.focus();
|
||||
}, 50);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="p-1">
|
||||
<Show when={notifyPubkeys().length > 0}>
|
||||
<div>
|
||||
<For each={notifyPubkeys()}>
|
||||
{(pubkey) => (
|
||||
<>
|
||||
<UserNameDisplay pubkey={pubkey} />{' '}
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
に返信
|
||||
</div>
|
||||
</Show>
|
||||
<form class="flex flex-col gap-1" onSubmit={handleSubmit}>
|
||||
<textarea
|
||||
ref={props.ref}
|
||||
ref={textAreaRef}
|
||||
name="text"
|
||||
class="rounded border-none"
|
||||
rows={4}
|
||||
placeholder="いまどうしてる?"
|
||||
placeholder={placeholder(mode())}
|
||||
onInput={handleChangeText}
|
||||
onKeyDown={handleKeyDown}
|
||||
value={text()}
|
||||
/>
|
||||
<div class="grid justify-end">
|
||||
<div class="flex items-end justify-end">
|
||||
<Show when={mode() === 'reply'}>
|
||||
<div class="flex-1">
|
||||
<button class="h-5 w-5 text-stone-500" onClick={() => props.onClose()}>
|
||||
<XMark />
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
<button
|
||||
class="h-8 w-8 rounded bg-primary p-2 font-bold text-white"
|
||||
classList={{ 'bg-primary-disabled': submitDisabled(), 'bg-primary': !submitDisabled() }}
|
||||
class="rounded bg-primary p-2 font-bold text-white"
|
||||
classList={{
|
||||
'bg-primary-disabled': submitDisabled(),
|
||||
'bg-primary': !submitDisabled(),
|
||||
'h-8': mode() === 'normal',
|
||||
'w-8': mode() === 'normal',
|
||||
'h-7': mode() === 'reply',
|
||||
'w-7': mode() === 'reply',
|
||||
}}
|
||||
type="submit"
|
||||
disabled={submitDisabled()}
|
||||
>
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { createSignal, createMemo, type Component, type JSX } from 'solid-js';
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import PaperAirplane from 'heroicons/24/solid/paper-airplane.svg';
|
||||
|
||||
type ReplyPostFormProps = {
|
||||
replyTo: NostrEvent;
|
||||
onPost: (textNote: { content: string }) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const ReplyPostForm: Component<ReplyPostFormProps> = (props: ReplyPostFormProps) => {
|
||||
const [text, setText] = createSignal<string>('');
|
||||
|
||||
const clearText = () => setText('');
|
||||
|
||||
const handleChangeText: JSX.EventHandler<HTMLTextAreaElement, Event> = (ev) => {
|
||||
setText(ev.currentTarget.value);
|
||||
};
|
||||
|
||||
// TODO 投稿完了したかどうかの検知をしたい
|
||||
const submit = () => {
|
||||
props.onPost({ content: text() });
|
||||
clearText();
|
||||
};
|
||||
|
||||
const handleSubmit: JSX.EventHandler<HTMLFormElement, Event> = (ev) => {
|
||||
ev.preventDefault();
|
||||
submit();
|
||||
};
|
||||
|
||||
const handleKeyDown: JSX.EventHandler<HTMLTextAreaElement, KeyboardEvent> = (ev) => {
|
||||
if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey)) {
|
||||
submit();
|
||||
}
|
||||
};
|
||||
|
||||
const submitDisabled = createMemo(() => text().trim().length === 0);
|
||||
|
||||
return (
|
||||
<div class="p-1">
|
||||
<div>
|
||||
{'Replying to '}
|
||||
{props.replyTo.pubkey}
|
||||
</div>
|
||||
<form class="grid w-full gap-1" onSubmit={handleSubmit}>
|
||||
<textarea
|
||||
name="text"
|
||||
class="rounded border-none"
|
||||
rows={4}
|
||||
placeholder="返信を投稿"
|
||||
onInput={handleChangeText}
|
||||
onKeyDown={handleKeyDown}
|
||||
value={text()}
|
||||
/>
|
||||
<div class="flex justify-between">
|
||||
{/* TODO あとでちゃんとアイコンにする */}
|
||||
<button onClick={() => props.onClose()}>X</button>
|
||||
<button
|
||||
class="h-7 w-7 rounded bg-primary p-2 font-bold text-white"
|
||||
classList={{ 'bg-primary-disabled': submitDisabled(), 'bg-primary': !submitDisabled() }}
|
||||
type="submit"
|
||||
disabled={submitDisabled()}
|
||||
>
|
||||
<PaperAirplane />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplyPostForm;
|
||||
@@ -6,56 +6,18 @@ import Cog6Tooth from 'heroicons/24/outline/cog-6-tooth.svg';
|
||||
import NotePostForm from '@/components/NotePostForm';
|
||||
import Config from '@/components/Config';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useCommands from '@/nostr/useCommands';
|
||||
import usePubkey from '@/nostr/usePubkey';
|
||||
import { useHandleCommand } from '@/hooks/useCommandBus';
|
||||
|
||||
const SideBar: Component = () => {
|
||||
let formTextAreaRef: HTMLTextAreaElement | undefined;
|
||||
const { config } = useConfig();
|
||||
const getPubkey = usePubkey();
|
||||
const commands = useCommands();
|
||||
|
||||
const [formOpened, setFormOpened] = createSignal(false);
|
||||
const [configOpened, setConfigOpened] = createSignal(false);
|
||||
|
||||
const openForm = () => {
|
||||
setFormOpened(true);
|
||||
setTimeout(() => {
|
||||
formTextAreaRef?.focus?.();
|
||||
}, 100);
|
||||
};
|
||||
const closeForm = () => {
|
||||
setFormOpened(false);
|
||||
formTextAreaRef?.blur?.();
|
||||
};
|
||||
|
||||
const handlePost = ({ content }: { content: string }) => {
|
||||
const pubkey = getPubkey();
|
||||
if (pubkey == null) {
|
||||
console.error('pubkey is not available');
|
||||
return;
|
||||
}
|
||||
commands
|
||||
.publishTextNote({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey,
|
||||
content,
|
||||
})
|
||||
.then(() => {
|
||||
console.log('succeeded to post');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('error', err);
|
||||
});
|
||||
};
|
||||
const openForm = () => setFormOpened(true);
|
||||
const closeForm = () => setFormOpened(false);
|
||||
|
||||
useHandleCommand(() => ({
|
||||
commandType: 'openPostForm',
|
||||
handler: (cmd) => {
|
||||
openForm();
|
||||
},
|
||||
handler: (cmd) => openForm(),
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -87,7 +49,7 @@ const SideBar: Component = () => {
|
||||
</div>
|
||||
</div>
|
||||
<Show when={formOpened()}>
|
||||
<NotePostForm ref={formTextAreaRef} onPost={handlePost} onClose={closeForm} />
|
||||
<NotePostForm onClose={closeForm} />
|
||||
</Show>
|
||||
<Show when={configOpened()}>
|
||||
<Config onClose={() => setConfigOpened(false)} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, Switch, Match } from 'solid-js';
|
||||
import { npubEncode } from 'nostr-tools/nip19';
|
||||
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useProfile from '@/nostr/useProfile';
|
||||
@@ -15,7 +16,7 @@ const UserNameDisplay: Component<UserNameDisplayProps> = (props) => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<Switch fallback={`@${props.pubkey}`}>
|
||||
<Switch fallback={npubEncode(props.pubkey)}>
|
||||
<Match when={(profile()?.display_name?.length ?? 0) > 0}>{profile()?.display_name}</Match>
|
||||
<Match when={(profile()?.name?.length ?? 0) > 0}>@{profile()?.name}</Match>
|
||||
</Switch>
|
||||
|
||||
@@ -9,6 +9,7 @@ import UserDisplayName from '@/components/UserDisplayName';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import useProfile from '@/nostr/useProfile';
|
||||
import useEvent from '@/nostr/useEvent';
|
||||
import { npubEncode } from 'nostr-tools/nip19';
|
||||
|
||||
type ReactionProps = {
|
||||
event: NostrEvent;
|
||||
@@ -63,10 +64,11 @@ const Reaction: Component<ReactionProps> = (props) => {
|
||||
</div>
|
||||
<div class="notification-event py-1">
|
||||
<Show
|
||||
when={reactedEvent() != null}
|
||||
when={reactedEvent()}
|
||||
fallback={<div class="truncate">loading {eventId()}</div>}
|
||||
keyed
|
||||
>
|
||||
<TextNoteDisplay event={reactedEvent()} />
|
||||
{(event) => <TextNoteDisplay event={event} />}
|
||||
</Show>
|
||||
</div>
|
||||
</ColumnItem>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { MentionedUser } from '@/core/parseTextNote';
|
||||
import { Show } from 'solid-js';
|
||||
import { npubEncode } from 'nostr-tools/nip19';
|
||||
|
||||
import useProfile from '@/nostr/useProfile';
|
||||
import useConfig from '@/nostr/useConfig';
|
||||
import { Show } from 'solid-js';
|
||||
|
||||
export type GeneralUserMentionDisplayProps = {
|
||||
pubkey: string;
|
||||
@@ -15,7 +16,7 @@ const GeneralUserMentionDisplay = (props: GeneralUserMentionDisplayProps) => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<Show when={(profile()?.name?.length ?? 0) > 0} fallback={`@${props.pubkey}`}>
|
||||
<Show when={(profile()?.name?.length ?? 0) > 0} fallback={`@${npubEncode(props.pubkey)}`}>
|
||||
@{profile()?.name ?? props.pubkey}
|
||||
</Show>
|
||||
);
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import { Component, createSignal, Show } from 'solid-js';
|
||||
import { ContentWarning } from '@/core/event';
|
||||
import { Component, createEffect, createSignal, Show } from 'solid-js';
|
||||
import { fixUrl } from '@/utils/imageUrl';
|
||||
|
||||
type ImageDisplayProps = {
|
||||
url: string;
|
||||
contentWarning: ContentWarning;
|
||||
};
|
||||
|
||||
const fixUrl = (url: URL): string => {
|
||||
const result = new URL(url);
|
||||
if (url.host === 'i.imgur.com') {
|
||||
const match = url.pathname.match(/^\/([a-zA-Z0-9]+)\.(jpg|jpeg|png|gif)/);
|
||||
if (match != null) {
|
||||
const imageId = match[1];
|
||||
result.pathname = `${imageId}l.webp`;
|
||||
}
|
||||
} else if (url.host === 'i.gyazo.com') {
|
||||
result.host = 'thumb.gyazo.com';
|
||||
result.pathname = `/thumb/640_w${url.pathname}`;
|
||||
}
|
||||
return result.toString();
|
||||
initialHidden: boolean;
|
||||
};
|
||||
|
||||
const ImageDisplay: Component<ImageDisplayProps> = (props) => {
|
||||
const [hidden, setHidden] = createSignal(props.contentWarning.contentWarning);
|
||||
const url = () => new URL(props.url);
|
||||
const [hidden, setHidden] = createSignal(props.initialHidden);
|
||||
|
||||
return (
|
||||
<Show
|
||||
@@ -40,7 +24,7 @@ const ImageDisplay: Component<ImageDisplayProps> = (props) => {
|
||||
<a class="my-2 block" href={props.url} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
class="inline-block max-h-64 max-w-full rounded object-contain shadow hover:shadow-md"
|
||||
src={fixUrl(url())}
|
||||
src={fixUrl(new URL(props.url)).toString()}
|
||||
alt={props.url}
|
||||
/>
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { For, Switch, Match } from 'solid-js';
|
||||
import { For } from 'solid-js';
|
||||
import parseTextNote, { type ParsedTextNoteNode } from '@/core/parseTextNote';
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
import PlainTextDisplay from '@/components/textNote/PlainTextDisplay';
|
||||
@@ -6,6 +6,7 @@ import MentionedUserDisplay from '@/components/textNote/MentionedUserDisplay';
|
||||
import MentionedEventDisplay from '@/components/textNote/MentionedEventDisplay';
|
||||
import ImageDisplay from '@/components/textNote/ImageDisplay';
|
||||
import eventWrapper from '@/core/event';
|
||||
import { isImageUrl } from '@/utils/imageUrl';
|
||||
import EventLink from '../EventLink';
|
||||
import TextNoteDisplayById from './TextNoteDisplayById';
|
||||
|
||||
@@ -45,8 +46,13 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
||||
return <span class="text-blue-500 underline">{item.content}</span>;
|
||||
}
|
||||
if (item.type === 'URL') {
|
||||
if (item.content.match(/\.(jpeg|jpg|png|gif|webp)$/i) && props.embedding) {
|
||||
return <ImageDisplay url={item.content} contentWarning={event().contentWarning()} />;
|
||||
if (isImageUrl(new URL(item.content))) {
|
||||
return (
|
||||
<ImageDisplay
|
||||
url={item.content}
|
||||
initialHidden={event().contentWarning().contentWarning || !props.embedding}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Show, For, createSignal, createMemo, type JSX, type Component } from 'solid-js';
|
||||
import {
|
||||
Show,
|
||||
For,
|
||||
createSignal,
|
||||
createMemo,
|
||||
type JSX,
|
||||
type Component,
|
||||
Match,
|
||||
Switch,
|
||||
} from 'solid-js';
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import HeartOutlined from 'heroicons/24/outline/heart.svg';
|
||||
@@ -9,8 +18,9 @@ import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
|
||||
|
||||
import ColumnItem from '@/components/ColumnItem';
|
||||
import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay';
|
||||
import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay';
|
||||
import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay';
|
||||
import ReplyPostForm from '@/components/ReplyPostForm';
|
||||
import NotePostForm from '@/components/NotePostForm';
|
||||
|
||||
import eventWrapper from '@/core/event';
|
||||
|
||||
@@ -24,7 +34,9 @@ import useDeprecatedReposts from '@/nostr/useDeprecatedReposts';
|
||||
import useFormatDate from '@/hooks/useFormatDate';
|
||||
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import ContentWarningDisplay from './ContentWarningDisplay';
|
||||
import { npubEncode } from 'nostr-tools/nip19';
|
||||
import UserNameDisplay from '../UserDisplayName';
|
||||
import TextNoteDisplayById from './TextNoteDisplayById';
|
||||
|
||||
export type TextNoteDisplayProps = {
|
||||
event: NostrEvent;
|
||||
@@ -32,15 +44,15 @@ export type TextNoteDisplayProps = {
|
||||
actions?: boolean;
|
||||
};
|
||||
|
||||
const ContentWarning = (props) => {};
|
||||
|
||||
const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
const { config } = useConfig();
|
||||
const formatDate = useFormatDate();
|
||||
const commands = useCommands();
|
||||
const pubkey = usePubkey();
|
||||
|
||||
const [showReplyForm, setShowReplyForm] = createSignal(false);
|
||||
const [showContentWarning, setShowContentWarning] = createSignal(false);
|
||||
const [postingRepost, setPostingRepost] = createSignal(false);
|
||||
const [postingReaction, setPostingReaction] = createSignal(false);
|
||||
|
||||
const event = createMemo(() => eventWrapper(props.event));
|
||||
|
||||
@@ -54,41 +66,41 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
|
||||
const { reactions, isReactedBy, invalidateReactions } = useReactions(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
eventId: props.event.id,
|
||||
eventId: props.event.id as string, // TODO いつかなおす
|
||||
}));
|
||||
|
||||
const { reposts, isRepostedBy, invalidateDeprecatedReposts } = useDeprecatedReposts(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
eventId: props.event.id,
|
||||
eventId: props.event.id as string, // TODO いつかなおす
|
||||
}));
|
||||
|
||||
const isReactedByMe = createMemo(() => isReactedBy(pubkey()));
|
||||
const isRepostedByMe = createMemo(() => isRepostedBy(pubkey()));
|
||||
|
||||
const createdAt = () => formatDate(event().createdAtAsDate());
|
||||
|
||||
const handleReplyPost = ({ content }: { content: string }) => {
|
||||
commands
|
||||
.publishTextNote({
|
||||
relayUrls: config().relayUrls,
|
||||
pubkey: pubkey(),
|
||||
content,
|
||||
notifyPubkeys: [event().pubkey, ...event().mentionedPubkeys()],
|
||||
rootEventId: event().rootEvent()?.id ?? props.event.id,
|
||||
replyEventId: props.event.id,
|
||||
})
|
||||
.then(() => {
|
||||
setShowReplyForm(false);
|
||||
});
|
||||
const showReplyEvent = (): string | undefined => {
|
||||
const replyingToEvent = event().replyingToEvent();
|
||||
if (
|
||||
embedding() &&
|
||||
replyingToEvent != null &&
|
||||
!event().containsEventMentionIndex(replyingToEvent.index)
|
||||
) {
|
||||
return replyingToEvent.id;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const createdAt = () => formatDate(event().createdAtAsDate());
|
||||
|
||||
const handleRepost: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (ev) => {
|
||||
if (isRepostedByMe()) {
|
||||
// TODO remove reaction
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
if (postingRepost()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPostingRepost(true);
|
||||
ensureNonNull([pubkey(), props.event.id] as const)(([pubkeyNonNull, eventIdNonNull]) => {
|
||||
commands
|
||||
.publishDeprecatedRepost({
|
||||
@@ -98,7 +110,8 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
notifyPubkey: props.event.pubkey,
|
||||
})
|
||||
.then(() => invalidateDeprecatedReposts())
|
||||
.catch((err) => console.error('failed to repost: ', err));
|
||||
.catch((err) => console.error('failed to repost: ', err))
|
||||
.finally(() => setPostingRepost(false));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -107,8 +120,11 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
// TODO remove reaction
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
if (postingReaction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPostingReaction(true);
|
||||
ensureNonNull([pubkey(), props.event.id] as const)(([pubkeyNonNull, eventIdNonNull]) => {
|
||||
commands
|
||||
.publishReaction({
|
||||
@@ -119,7 +135,8 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
notifyPubkey: props.event.pubkey,
|
||||
})
|
||||
.then(() => invalidateReactions())
|
||||
.catch((err) => console.error('failed to publish reaction: ', err));
|
||||
.catch((err) => console.error('failed to publish reaction: ', err))
|
||||
.finally(() => setPostingReaction(false));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -145,23 +162,31 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
<div class="author-name truncate pr-1 font-bold">{author()?.display_name}</div>
|
||||
</Show>
|
||||
<div class="author-username truncate text-zinc-600">
|
||||
<Show when={author()?.name} fallback={props.event.pubkey}>
|
||||
<Show when={author()?.name != null} fallback={`@${npubEncode(props.event.pubkey)}`}>
|
||||
@{author()?.name}
|
||||
</Show>
|
||||
{/* TODO <Match when={author()?.nip05 != null}>@{author()?.nip05}</Match> */}
|
||||
</div>
|
||||
</div>
|
||||
<div class="created-at shrink-0">{createdAt()}</div>
|
||||
</div>
|
||||
<Show when={event().mentionedPubkeys().length > 0}>
|
||||
<Show when={showReplyEvent()} keyed>
|
||||
{(id) => (
|
||||
<div class="rounded border p-1">
|
||||
<TextNoteDisplayById eventId={id} actions={false} embedding={false} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={event().mentionedPubkeysWithoutAuthor().length > 0}>
|
||||
<div class="text-xs">
|
||||
{'Replying to '}
|
||||
<For each={event().mentionedPubkeys()}>
|
||||
<For each={event().mentionedPubkeysWithoutAuthor()}>
|
||||
{(replyToPubkey: string) => (
|
||||
<span class="pr-1 text-blue-500 underline">
|
||||
<GeneralUserMentionDisplay pubkey={replyToPubkey} />
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
{'への返信'}
|
||||
</div>
|
||||
</Show>
|
||||
<ContentWarningDisplay contentWarning={event().contentWarning()}>
|
||||
@@ -184,7 +209,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
'text-green-400': isRepostedByMe(),
|
||||
}}
|
||||
>
|
||||
<button class="h-4 w-4" onClick={handleRepost}>
|
||||
<button class="h-4 w-4" onClick={handleRepost} disabled={postingRepost()}>
|
||||
<ArrowPathRoundedSquare />
|
||||
</button>
|
||||
<Show when={reposts().length > 0}>
|
||||
@@ -198,7 +223,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
'text-rose-400': isReactedByMe(),
|
||||
}}
|
||||
>
|
||||
<button class="h-4 w-4" onClick={handleReaction}>
|
||||
<button class="h-4 w-4" onClick={handleReaction} disabled={postingReaction()}>
|
||||
<Show when={isReactedByMe()} fallback={<HeartOutlined />}>
|
||||
<HeartSolid />
|
||||
</Show>
|
||||
@@ -215,11 +240,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
<Show when={showReplyForm()}>
|
||||
<ReplyPostForm
|
||||
replyTo={props.event}
|
||||
onPost={handleReplyPost}
|
||||
onClose={() => setShowReplyForm(false)}
|
||||
/>
|
||||
<NotePostForm mode="reply" replyTo={props.event} onClose={() => setShowReplyForm(false)} />
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user