mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 22:44:26 +01:00
fix: bug in displaying reply-to
This commit is contained in:
@@ -12,11 +12,13 @@ import EmojiPicker from '@/components/EmojiPicker';
|
||||
import UserNameDisplay from '@/components/UserDisplayName';
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { useHandleCommand } from '@/hooks/useCommandBus';
|
||||
import usePersistStatus from '@/hooks/usePersistStatus';
|
||||
import eventWrapper from '@/nostr/event';
|
||||
import parseTextNote, { ParsedTextNote } from '@/nostr/parseTextNote';
|
||||
import useCommands, { PublishTextNoteParams } from '@/nostr/useCommands';
|
||||
import usePubkey from '@/nostr/usePubkey';
|
||||
import { uploadNostrBuild, uploadFiles } from '@/utils/imageUpload';
|
||||
import { uploadNostrBuild, uploadFiles, uploaders } from '@/utils/imageUpload';
|
||||
import openLink from '@/utils/openLink';
|
||||
|
||||
type NotePostFormProps = {
|
||||
replyTo?: NostrEvent;
|
||||
@@ -106,6 +108,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
};
|
||||
|
||||
const { config, getEmoji } = useConfig();
|
||||
const { persistStatus, didAgreeToToS, agreeToToS } = usePersistStatus();
|
||||
const getPubkey = usePubkey();
|
||||
const commands = useCommands();
|
||||
|
||||
@@ -150,23 +153,24 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
},
|
||||
});
|
||||
|
||||
const mentionedPubkeys = createMemo(() => replyTo()?.mentionedPubkeysWithoutAuthor() ?? []);
|
||||
const mentionedPubkeysWithoutMe = createMemo(() => {
|
||||
const p = getPubkey();
|
||||
return (
|
||||
replyTo()
|
||||
?.mentionedPubkeys()
|
||||
?.filter((pubkey) => pubkey !== p) ?? []
|
||||
);
|
||||
});
|
||||
|
||||
const mentionedPubkeysWithoutMe = createMemo(() =>
|
||||
mentionedPubkeys().filter((pubkey) => pubkey !== getPubkey()),
|
||||
);
|
||||
|
||||
const notifyPubkeys = (pubkey: string, pubkeyReferences: string[]): string[] => {
|
||||
if (props.replyTo == null) return pubkeyReferences;
|
||||
const notifyPubkeys = createMemo(() => {
|
||||
if (props.replyTo == null) return [];
|
||||
return uniq([
|
||||
// 返信先を先頭に
|
||||
props.replyTo.pubkey,
|
||||
// その他の返信先
|
||||
...mentionedPubkeysWithoutMe(),
|
||||
// 本文中の公開鍵(npub)
|
||||
...pubkeyReferences,
|
||||
]);
|
||||
};
|
||||
});
|
||||
|
||||
const buildEmojiTags = (emojis: string[]): string[][] => {
|
||||
const emojiTags: string[][] = [];
|
||||
@@ -210,7 +214,10 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
if (replyTo() != null) {
|
||||
textNote = {
|
||||
...textNote,
|
||||
notifyPubkeys: notifyPubkeys(pubkey, pubkeyReferences),
|
||||
notifyPubkeys: uniq([
|
||||
...notifyPubkeys(),
|
||||
...pubkeyReferences, // 本文中の公開鍵(npub)
|
||||
]),
|
||||
rootEventId: replyTo()?.rootEvent()?.id ?? replyTo()?.id,
|
||||
replyEventId: replyTo()?.id,
|
||||
};
|
||||
@@ -225,6 +232,22 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
close();
|
||||
};
|
||||
|
||||
const ensureUploaderAgreement = (): boolean => {
|
||||
if (didAgreeToToS('nostrBuild')) return true;
|
||||
|
||||
window.alert(
|
||||
'画像アップローダーの利用規約をお読みください。\n(新しいタブで利用規約を開きます)',
|
||||
);
|
||||
openLink(uploaders.nostrBuild.tos);
|
||||
const didAgree = window.confirm('同意する場合はOKをクリックしてください。');
|
||||
|
||||
if (didAgree) {
|
||||
agreeToToS('nostrBuild');
|
||||
}
|
||||
|
||||
return didAgree;
|
||||
};
|
||||
|
||||
const handleInput: JSX.EventHandler<HTMLTextAreaElement, InputEvent> = (ev) => {
|
||||
setText(ev.currentTarget.value);
|
||||
resizeTextArea();
|
||||
@@ -246,6 +269,9 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
|
||||
const handleChangeFile: JSX.EventHandler<HTMLInputElement, Event> = (ev) => {
|
||||
ev.preventDefault();
|
||||
if (uploadFilesMutation.isLoading) return;
|
||||
if (!ensureUploaderAgreement()) return;
|
||||
|
||||
const files = [...(ev.currentTarget.files ?? [])];
|
||||
uploadFilesMutation.mutate(files);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@@ -255,12 +281,14 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
const handleDrop: JSX.EventHandler<HTMLTextAreaElement, DragEvent> = (ev) => {
|
||||
ev.preventDefault();
|
||||
if (uploadFilesMutation.isLoading) return;
|
||||
if (!ensureUploaderAgreement()) return;
|
||||
const files = [...(ev?.dataTransfer?.files ?? [])];
|
||||
uploadFilesMutation.mutate(files);
|
||||
};
|
||||
|
||||
const handlePaste: JSX.EventHandler<HTMLTextAreaElement, ClipboardEvent> = (ev) => {
|
||||
if (uploadFilesMutation.isLoading) return;
|
||||
|
||||
const items = [...(ev?.clipboardData?.items ?? [])];
|
||||
|
||||
const files: File[] = [];
|
||||
@@ -273,6 +301,8 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
}
|
||||
});
|
||||
if (files.length === 0) return;
|
||||
if (!ensureUploaderAgreement()) return;
|
||||
|
||||
uploadFilesMutation.mutate(files);
|
||||
};
|
||||
|
||||
@@ -296,12 +326,13 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div class="p-1">
|
||||
<Show when={mentionedPubkeys().length > 0}>
|
||||
<Show when={props.replyTo != null}>
|
||||
<div>
|
||||
<For each={mentionedPubkeys()}>
|
||||
{(pubkey) => (
|
||||
<For each={notifyPubkeys()}>
|
||||
{(pubkey, index) => (
|
||||
<>
|
||||
<UserNameDisplay pubkey={pubkey} />{' '}
|
||||
<UserNameDisplay pubkey={pubkey} />
|
||||
<Show when={index() !== notifyPubkeys().length - 1}> と </Show>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -28,7 +28,14 @@ const EventDisplayById: Component<EventDisplayByIdProps> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch fallback="投稿が見つかりません">
|
||||
<Switch
|
||||
fallback={
|
||||
<span>
|
||||
投稿が見つかりません
|
||||
{props.eventId}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Match when={hidden()}>{null}</Match>
|
||||
<Match when={fetchedEvent()} keyed>
|
||||
{(event) => <EventDisplay event={event} {...restProps} />}
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import { Accessor } from 'solid-js';
|
||||
|
||||
import {
|
||||
createSignalWithStorage,
|
||||
createStoreWithStorage,
|
||||
createStorageWithSerializer,
|
||||
} from '@/hooks/createSignalWithStorage';
|
||||
import { UploaderIds } from '@/utils/imageUpload';
|
||||
|
||||
type PersistStatus = {
|
||||
loggedIn: boolean;
|
||||
agreeWithNostrBuild: boolean;
|
||||
agreements: Record<UploaderIds, boolean>;
|
||||
};
|
||||
|
||||
type UsePersistStatus = {
|
||||
persistStatus: Accessor<PersistStatus>;
|
||||
loggedIn: () => void;
|
||||
agreeToToS: (uploaderId: UploaderIds) => void;
|
||||
didAgreeToToS: (uploaderId: UploaderIds) => boolean;
|
||||
};
|
||||
|
||||
const InitialPersistStatus = {
|
||||
const InitialPersistStatus: PersistStatus = {
|
||||
loggedIn: false,
|
||||
agreeWithNostrBuild: false,
|
||||
agreements: {
|
||||
nostrBuild: false,
|
||||
},
|
||||
};
|
||||
|
||||
const serializer = (persistStatus: PersistStatus): string => JSON.stringify(persistStatus);
|
||||
@@ -26,7 +31,7 @@ const deserializer = (json: string): PersistStatus => JSON.parse(json) as Persis
|
||||
|
||||
const storage = createStorageWithSerializer(() => window.localStorage, serializer, deserializer);
|
||||
|
||||
const [persistStatus, setPersistStatus] = createSignalWithStorage(
|
||||
const [persistStatus, setPersistStatus] = createStoreWithStorage(
|
||||
'RabbitPersistStatus',
|
||||
InitialPersistStatus,
|
||||
storage,
|
||||
@@ -37,12 +42,20 @@ const usePersistStatus = (): UsePersistStatus => {
|
||||
setPersistStatus((current) => ({ ...current, loggedIn: true }));
|
||||
};
|
||||
|
||||
const agreeToToS = (key: UploaderIds) => {
|
||||
setPersistStatus('agreements', (current) => ({ ...current, [key]: true }));
|
||||
};
|
||||
|
||||
const didAgreeToToS = (key: UploaderIds): boolean => persistStatus.agreements[key] ?? false;
|
||||
|
||||
return {
|
||||
persistStatus: () => ({
|
||||
...InitialPersistStatus,
|
||||
...persistStatus(),
|
||||
...persistStatus,
|
||||
}),
|
||||
loggedIn,
|
||||
agreeToToS,
|
||||
didAgreeToToS,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -153,9 +153,6 @@ const eventWrapper = (event: NostrEvent) => {
|
||||
mentionedPubkeys(): string[] {
|
||||
return uniq(this.pTags().map(([, pubkey]) => pubkey));
|
||||
},
|
||||
mentionedPubkeysWithoutAuthor(): string[] {
|
||||
return this.mentionedPubkeys().filter((pubkey) => pubkey !== event.pubkey);
|
||||
},
|
||||
contentWarning(): ContentWarning {
|
||||
const tag = event.tags.find(([tagName]) => tagName === 'content-warning');
|
||||
if (tag == null) return { contentWarning: false };
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
export type UploadResult = {
|
||||
imageUrl: string;
|
||||
};
|
||||
|
||||
export type Uploader = {
|
||||
name: string;
|
||||
tos: string;
|
||||
upload: (file: File) => Promise<UploadResult>;
|
||||
};
|
||||
|
||||
const toHexString = (buff: ArrayBuffer): string => {
|
||||
const arr = new Array(buff.byteLength);
|
||||
const view = new Int8Array(buff);
|
||||
@@ -9,10 +19,6 @@ const toHexString = (buff: ArrayBuffer): string => {
|
||||
return arr.join();
|
||||
};
|
||||
|
||||
export type UploadResult = {
|
||||
imageUrl: string;
|
||||
};
|
||||
|
||||
export const uploadNostrBuild = async (blob: Blob): Promise<UploadResult> => {
|
||||
const form = new FormData();
|
||||
form.set('fileToUpload', blob);
|
||||
@@ -56,6 +62,16 @@ export const uploadVoidCat = async (blob: Blob): Promise<any> => {
|
||||
return res.json();
|
||||
};
|
||||
|
||||
export const uploaders = {
|
||||
nostrBuild: {
|
||||
name: 'nostr.build',
|
||||
tos: 'https://nostr.build/tos/',
|
||||
upload: uploadNostrBuild,
|
||||
} satisfies Uploader,
|
||||
} as const;
|
||||
|
||||
export type UploaderIds = keyof typeof uploaders;
|
||||
|
||||
export const uploadFiles =
|
||||
<T>(uploadFn: (file: Blob) => Promise<T>) =>
|
||||
(files: File[]): Promise<PromiseSettledResult<Awaited<T>>[]> => {
|
||||
|
||||
9
src/utils/openLink.ts
Normal file
9
src/utils/openLink.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
const openLink = (url: string) => {
|
||||
const a = document.createElement('a');
|
||||
a.href = new URL(url).toString();
|
||||
a.target = '_blank';
|
||||
a.rel = 'noreferrer noopener';
|
||||
a.click();
|
||||
};
|
||||
|
||||
export default openLink;
|
||||
Reference in New Issue
Block a user