mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 14:04:21 +01:00
fix
This commit is contained in:
54
src/components/event/ChannelInfo.tsx
Normal file
54
src/components/event/ChannelInfo.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Component, Show } from 'solid-js';
|
||||
|
||||
import ChatBubbleLeftRight from 'heroicons/24/outline/chat-bubble-left-right.svg';
|
||||
import { Event as NostrEvent } from 'nostr-tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
import EventLink from '@/components/EventLink';
|
||||
import { isImageUrl } from '@/utils/imageUrl';
|
||||
|
||||
export type ChannelInfoProps = {
|
||||
event: NostrEvent;
|
||||
};
|
||||
|
||||
const ChannelMetaSchema = z.object({
|
||||
name: z.string(),
|
||||
about: z.string().optional(),
|
||||
picture: z
|
||||
.string()
|
||||
.url()
|
||||
.refine((url) => isImageUrl(url), { message: 'not an image url' })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type ChannelMeta = z.infer<typeof ChannelMetaSchema>;
|
||||
|
||||
const parseContent = (content: string): ChannelMeta | null => {
|
||||
try {
|
||||
return ChannelMetaSchema.parse(JSON.parse(content));
|
||||
} catch (err) {
|
||||
console.warn('failed to parse chat channel schema: ', err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const ChannelInfo: Component<ChannelInfoProps> = (props) => {
|
||||
const parsedContent = () => parseContent(props.event.content);
|
||||
|
||||
return (
|
||||
<Show when={parsedContent()} keyed>
|
||||
{(meta) => (
|
||||
<button class="flex flex-col gap-1 px-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="inline-block h-4 w-4 text-purple-400">
|
||||
<ChatBubbleLeftRight />
|
||||
</span>
|
||||
<span>{meta.name}</span>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChannelInfo;
|
||||
@@ -2,6 +2,7 @@ import { Switch, Match, Component } from 'solid-js';
|
||||
|
||||
import { Kind, type Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
import ChannelInfo from '@/components/event/ChannelInfo';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import Repost from '@/components/event/Repost';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
@@ -12,12 +13,16 @@ export type EventDisplayProps = {
|
||||
event: NostrEvent;
|
||||
embedding?: boolean;
|
||||
actions?: boolean;
|
||||
kinds?: Kind[];
|
||||
ensureKinds?: Kind[];
|
||||
};
|
||||
|
||||
const EventDisplay: Component<EventDisplayProps> = (props) => {
|
||||
// noteの場合は kind:1 であることを保証するために利用できる
|
||||
// タイムラインで表示されるべきでないイベントが表示されてしまうのを防ぐ
|
||||
const isAllowedKind = () =>
|
||||
props.kinds == null || props.kinds.length === 0 || props.kinds.includes(props.event.kind);
|
||||
props.ensureKinds == null ||
|
||||
props.ensureKinds.length === 0 ||
|
||||
props.ensureKinds.includes(props.event.kind);
|
||||
|
||||
return (
|
||||
<Switch
|
||||
@@ -28,7 +33,12 @@ const EventDisplay: Component<EventDisplayProps> = (props) => {
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Match when={!isAllowedKind()}>{null}</Match>
|
||||
<Match when={!isAllowedKind()} keyed>
|
||||
<span>
|
||||
予期しないイベント種別({props.event.kind})
|
||||
<EventLink eventId={props.event.id} kind={props.event.kind} />
|
||||
</span>
|
||||
</Match>
|
||||
<Match when={props.event.kind === Kind.Text}>
|
||||
<TextNote event={props.event} embedding={props.actions} actions={props.actions} />
|
||||
</Match>
|
||||
@@ -37,6 +47,11 @@ const EventDisplay: Component<EventDisplayProps> = (props) => {
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
/*
|
||||
<Match when={props.event.kind === Kind.ChannelCreation}>
|
||||
<ChannelInfo event={props.event} />
|
||||
</Match>
|
||||
*/
|
||||
};
|
||||
|
||||
export default EventDisplay;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { For } from 'solid-js';
|
||||
|
||||
import { Kind, Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import EventDisplayById from '@/components/event/EventDisplayById';
|
||||
import ImageDisplay from '@/components/event/textNote/ImageDisplay';
|
||||
@@ -15,8 +17,6 @@ import eventWrapper from '@/nostr/event';
|
||||
import parseTextNote, { resolveTagReference, type ParsedTextNoteNode } from '@/nostr/parseTextNote';
|
||||
import { isImageUrl } from '@/utils/imageUrl';
|
||||
|
||||
import type { Event as NostrEvent } from 'nostr-tools';
|
||||
|
||||
export type TextNoteContentDisplayProps = {
|
||||
event: NostrEvent;
|
||||
embedding: boolean;
|
||||
@@ -57,7 +57,12 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => {
|
||||
if (item.data.type === 'note' && props.embedding) {
|
||||
return (
|
||||
<div class="my-1 rounded border p-1">
|
||||
<EventDisplayById eventId={item.data.data} actions={false} embedding={false} />
|
||||
<EventDisplayById
|
||||
eventId={item.data.data}
|
||||
actions={false}
|
||||
embedding={false}
|
||||
ensureKinds={[Kind.Text]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ const EmojiReactions: Component<EmojiReactionsProps> = (props) => {
|
||||
const pubkey = usePubkey();
|
||||
|
||||
return (
|
||||
<div class="flex gap-2 pt-1">
|
||||
<div class="flex gap-2 py-1">
|
||||
<For each={[...props.reactionsGroupedByContent.entries()]}>
|
||||
{([content, events]) => {
|
||||
const isReactedByMeWithThisContent =
|
||||
@@ -57,9 +57,10 @@ const EmojiReactions: Component<EmojiReactionsProps> = (props) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
class="flex items-center rounded border px-1"
|
||||
class="flex h-6 items-center rounded border px-1"
|
||||
classList={{
|
||||
'text-zinc-400': !isReactedByMeWithThisContent,
|
||||
'hover:bg-zinc-50': !isReactedByMeWithThisContent,
|
||||
'bg-rose-50': isReactedByMeWithThisContent,
|
||||
'border-rose-200': isReactedByMeWithThisContent,
|
||||
'text-rose-400': isReactedByMeWithThisContent,
|
||||
@@ -67,7 +68,7 @@ const EmojiReactions: Component<EmojiReactionsProps> = (props) => {
|
||||
type="button"
|
||||
onClick={() => props.onReaction(content)}
|
||||
>
|
||||
<Show when={content === '+'} fallback={<span class="text-xs">{content}</span>}>
|
||||
<Show when={content === '+'} fallback={<span class="text-base">{content}</span>}>
|
||||
<span class="inline-block h-3 w-3 pt-[1px] text-rose-400">
|
||||
<HeartSolid />
|
||||
</span>
|
||||
@@ -403,9 +404,9 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
onReaction={doReaction}
|
||||
/>
|
||||
</Show>
|
||||
<div class="actions flex w-48 items-center justify-between gap-8 pt-1">
|
||||
<div class="actions flex w-52 items-center justify-between gap-8 pt-1">
|
||||
<button
|
||||
class="h-4 w-4 shrink-0 text-zinc-400"
|
||||
class="h-4 w-4 shrink-0 text-zinc-400 hover:text-zinc-500"
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setShowReplyForm((current) => !current);
|
||||
@@ -417,6 +418,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
class="flex shrink-0 items-center gap-1"
|
||||
classList={{
|
||||
'text-zinc-400': !isRepostedByMe(),
|
||||
'hover:text-green-400': !isRepostedByMe(),
|
||||
'text-green-400': isRepostedByMe() || publishRepostMutation.isLoading,
|
||||
}}
|
||||
>
|
||||
@@ -435,6 +437,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
class="flex shrink-0 items-center gap-1"
|
||||
classList={{
|
||||
'text-zinc-400': !isReactedByMe() || isReactedByMeWithEmoji(),
|
||||
'hover:text-rose-400': !isReactedByMe() || isReactedByMeWithEmoji(),
|
||||
'text-rose-400':
|
||||
(isReactedByMe() && !isReactedByMeWithEmoji()) ||
|
||||
publishReactionMutation.isLoading,
|
||||
@@ -465,6 +468,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
class="flex shrink-0 items-center gap-1"
|
||||
classList={{
|
||||
'text-zinc-400': !isReactedByMe() || !isReactedByMeWithEmoji(),
|
||||
'hover:text-rose-400': !isReactedByMe() || !isReactedByMeWithEmoji(),
|
||||
'text-rose-400':
|
||||
(isReactedByMe() && isReactedByMeWithEmoji()) ||
|
||||
publishReactionMutation.isLoading,
|
||||
@@ -479,7 +483,7 @@ const TextNoteDisplay: Component<TextNoteDisplayProps> = (props) => {
|
||||
</Show>
|
||||
<div>
|
||||
<ContextMenu menu={menu}>
|
||||
<span class="inline-block h-4 w-4 text-zinc-400">
|
||||
<span class="inline-block h-4 w-4 text-zinc-400 hover:text-zinc-500">
|
||||
<EllipsisHorizontal />
|
||||
</span>
|
||||
</ContextMenu>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
createStorageWithSerializer,
|
||||
createStoreWithStorage,
|
||||
} from '@/hooks/createSignalWithStorage';
|
||||
import eventWrapper from '@/nostr/event';
|
||||
|
||||
export type Config = {
|
||||
relayUrls: string[];
|
||||
@@ -149,8 +150,14 @@ const useConfig = (): UseConfig => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const shouldMuteEvent = (event: NostrEvent) =>
|
||||
isPubkeyMuted(event.pubkey) || hasMutedKeyword(event);
|
||||
const shouldMuteEvent = (event: NostrEvent) => {
|
||||
const ev = eventWrapper(event);
|
||||
return (
|
||||
isPubkeyMuted(event.pubkey) ||
|
||||
ev.mentionedPubkeys().some(isPubkeyMuted) ||
|
||||
hasMutedKeyword(event)
|
||||
);
|
||||
};
|
||||
|
||||
const initializeColumns = ({ pubkey }: { pubkey: string }) => {
|
||||
// すでに設定されている場合は終了
|
||||
|
||||
@@ -227,8 +227,6 @@ const { exec } = useBatch<TaskArg, TaskRes>(() => ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldMuteEvent(event)) return;
|
||||
|
||||
if (event.kind === Kind.Reaction) {
|
||||
// Use the last event id
|
||||
const id = eventWrapper(event).lastTaggedEventId();
|
||||
|
||||
@@ -71,9 +71,6 @@ const useSubscription = (propsProvider: () => UseSubscriptionProps | null) => {
|
||||
if (onEvent != null) {
|
||||
onEvent(event as NostrEvent & { id: string });
|
||||
}
|
||||
if (shouldMuteEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (props.clientEventFilter != null && !props.clientEventFilter(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user