fix: bug in displaying reply-to

This commit is contained in:
Shusui MOYATANI
2023-05-20 13:22:05 +09:00
parent dfe6bbadb1
commit 7a9632bc48
6 changed files with 103 additions and 30 deletions

View File

@@ -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>

View File

@@ -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} />}

View File

@@ -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,
};
};

View File

@@ -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 };

View File

@@ -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
View 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;