From 7a9632bc48fc5c08f9ecfc74098c261e3844e181 Mon Sep 17 00:00:00 2001 From: Shusui MOYATANI Date: Sat, 20 May 2023 13:22:05 +0900 Subject: [PATCH] fix: bug in displaying reply-to --- src/components/NotePostForm.tsx | 63 +++++++++++++++++------ src/components/event/EventDisplayById.tsx | 9 +++- src/hooks/usePersistStatus.ts | 25 ++++++--- src/nostr/event.ts | 3 -- src/utils/imageUpload.ts | 24 +++++++-- src/utils/openLink.ts | 9 ++++ 6 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 src/utils/openLink.ts diff --git a/src/components/NotePostForm.tsx b/src/components/NotePostForm.tsx index a1ad23d..ed6987e 100644 --- a/src/components/NotePostForm.tsx +++ b/src/components/NotePostForm.tsx @@ -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 = (props) => { }; const { config, getEmoji } = useConfig(); + const { persistStatus, didAgreeToToS, agreeToToS } = usePersistStatus(); const getPubkey = usePubkey(); const commands = useCommands(); @@ -150,23 +153,24 @@ const NotePostForm: Component = (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 = (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 = (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 = (ev) => { setText(ev.currentTarget.value); resizeTextArea(); @@ -246,6 +269,9 @@ const NotePostForm: Component = (props) => { const handleChangeFile: JSX.EventHandler = (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 = (props) => { const handleDrop: JSX.EventHandler = (ev) => { ev.preventDefault(); if (uploadFilesMutation.isLoading) return; + if (!ensureUploaderAgreement()) return; const files = [...(ev?.dataTransfer?.files ?? [])]; uploadFilesMutation.mutate(files); }; const handlePaste: JSX.EventHandler = (ev) => { if (uploadFilesMutation.isLoading) return; + const items = [...(ev?.clipboardData?.items ?? [])]; const files: File[] = []; @@ -273,6 +301,8 @@ const NotePostForm: Component = (props) => { } }); if (files.length === 0) return; + if (!ensureUploaderAgreement()) return; + uploadFilesMutation.mutate(files); }; @@ -296,12 +326,13 @@ const NotePostForm: Component = (props) => { return (
- 0}> +
- - {(pubkey) => ( + + {(pubkey, index) => ( <> - {' '} + + )} diff --git a/src/components/event/EventDisplayById.tsx b/src/components/event/EventDisplayById.tsx index ff20690..5ef93a2 100644 --- a/src/components/event/EventDisplayById.tsx +++ b/src/components/event/EventDisplayById.tsx @@ -28,7 +28,14 @@ const EventDisplayById: Component = (props) => { }; return ( - + + 投稿が見つかりません + {props.eventId} + + } + > {null} {(event) => } diff --git a/src/hooks/usePersistStatus.ts b/src/hooks/usePersistStatus.ts index ece7413..01b17b1 100644 --- a/src/hooks/usePersistStatus.ts +++ b/src/hooks/usePersistStatus.ts @@ -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; }; type UsePersistStatus = { persistStatus: Accessor; 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, }; }; diff --git a/src/nostr/event.ts b/src/nostr/event.ts index 30026de..128822b 100644 --- a/src/nostr/event.ts +++ b/src/nostr/event.ts @@ -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 }; diff --git a/src/utils/imageUpload.ts b/src/utils/imageUpload.ts index 6decbcf..18611f0 100644 --- a/src/utils/imageUpload.ts +++ b/src/utils/imageUpload.ts @@ -1,3 +1,13 @@ +export type UploadResult = { + imageUrl: string; +}; + +export type Uploader = { + name: string; + tos: string; + upload: (file: File) => Promise; +}; + 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 => { const form = new FormData(); form.set('fileToUpload', blob); @@ -56,6 +62,16 @@ export const uploadVoidCat = async (blob: Blob): Promise => { 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 = (uploadFn: (file: Blob) => Promise) => (files: File[]): Promise>[]> => { diff --git a/src/utils/openLink.ts b/src/utils/openLink.ts new file mode 100644 index 0000000..493d77c --- /dev/null +++ b/src/utils/openLink.ts @@ -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;