From 2d109f2f42426da119d82ca33b00a6082808aae9 Mon Sep 17 00:00:00 2001 From: Shusui MOYATANI Date: Sun, 2 Jul 2023 19:14:12 +0900 Subject: [PATCH] feat: support video --- .../event/textNote/ImageDisplay.tsx | 2 +- .../event/textNote/TextNoteContentDisplay.tsx | 18 ++++---- .../event/textNote/VideoDisplay.tsx | 41 +++++++++++++++++++ src/components/modal/Config.tsx | 8 ++-- src/core/useConfig.ts | 4 +- src/locales/en.ts | 2 +- src/locales/ja.ts | 2 +- src/nostr/event/channel.ts | 8 +--- src/utils/{imageUrl.test.ts => url.test.ts} | 2 +- src/utils/{imageUrl.ts => url.ts} | 9 ++++ 10 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 src/components/event/textNote/VideoDisplay.tsx rename src/utils/{imageUrl.test.ts => url.test.ts} (89%) rename src/utils/{imageUrl.ts => url.ts} (82%) diff --git a/src/components/event/textNote/ImageDisplay.tsx b/src/components/event/textNote/ImageDisplay.tsx index f064a3d..116d940 100644 --- a/src/components/event/textNote/ImageDisplay.tsx +++ b/src/components/event/textNote/ImageDisplay.tsx @@ -1,7 +1,7 @@ import { Component, createSignal, Show } from 'solid-js'; import SafeLink from '@/components/utils/SafeLink'; -import { fixUrl } from '@/utils/imageUrl'; +import { fixUrl } from '@/utils/url'; type ImageDisplayProps = { url: string; diff --git a/src/components/event/textNote/TextNoteContentDisplay.tsx b/src/components/event/textNote/TextNoteContentDisplay.tsx index 4e80e5a..3a3fdb9 100644 --- a/src/components/event/textNote/TextNoteContentDisplay.tsx +++ b/src/components/event/textNote/TextNoteContentDisplay.tsx @@ -8,6 +8,7 @@ import EventDisplayById from '@/components/event/EventDisplayById'; import ImageDisplay from '@/components/event/textNote/ImageDisplay'; import MentionedEventDisplay from '@/components/event/textNote/MentionedEventDisplay'; import MentionedUserDisplay from '@/components/event/textNote/MentionedUserDisplay'; +import VideoDisplay from '@/components/event/textNote/VideoDisplay'; import EventLink from '@/components/EventLink'; import SafeLink from '@/components/utils/SafeLink'; import { createSearchColumn } from '@/core/column'; @@ -15,7 +16,7 @@ import useConfig from '@/core/useConfig'; import { useRequestCommand } from '@/hooks/useCommandBus'; import { textNote } from '@/nostr/event'; import { type ParsedTextNoteNode } from '@/nostr/parseTextNote'; -import { isImageUrl } from '@/utils/imageUrl'; +import { isImageUrl, isVideoUrl } from '@/utils/url'; export type TextNoteContentDisplayProps = { event: NostrEvent; @@ -41,15 +42,14 @@ const TextNoteContentDisplay = (props: TextNoteContentDisplayProps) => { return {item.content}; } if (item.type === 'URL') { + const initialHidden = () => + !config().showMedia || event().contentWarning().contentWarning || !props.embedding; + if (isImageUrl(item.content)) { - return ( - - ); + return ; + } + if (isVideoUrl(item.content)) { + return ; } return ; } diff --git a/src/components/event/textNote/VideoDisplay.tsx b/src/components/event/textNote/VideoDisplay.tsx new file mode 100644 index 0000000..0457019 --- /dev/null +++ b/src/components/event/textNote/VideoDisplay.tsx @@ -0,0 +1,41 @@ +import { Component, createSignal, Show } from 'solid-js'; + +import SafeLink from '@/components/utils/SafeLink'; + +type VideoDisplayProps = { + url: string; + initialHidden: boolean; +}; + +const VideoDisplay: Component = (props) => { + let videoRef: HTMLVideoElement | undefined; + const [hidden, setHidden] = createSignal(props.initialHidden); + + return ( + setHidden(false)} + > + 動画を表示する + + } + > + + {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + + + + ); +}; + +export default VideoDisplay; diff --git a/src/components/modal/Config.tsx b/src/components/modal/Config.tsx index 3b92c93..1bfcc4e 100644 --- a/src/components/modal/Config.tsx +++ b/src/components/modal/Config.tsx @@ -451,10 +451,10 @@ const OtherConfig = () => { })); }; - const toggleShowImage = () => { + const toggleShowMedia = () => { setConfig((current) => ({ ...current, - showImage: !(current.showImage ?? true), + showMedia: !(current.showMedia ?? true), })); }; @@ -477,8 +477,8 @@ const OtherConfig = () => { />
-
{i18n()('config.display.showImagesByDefault')}
- toggleShowImage()} /> +
{i18n()('config.display.showMediaByDefault')}
+ toggleShowMedia()} />
{i18n()('config.display.hideNumbers')}
diff --git a/src/core/useConfig.ts b/src/core/useConfig.ts index 2c11d0e..4d6388f 100644 --- a/src/core/useConfig.ts +++ b/src/core/useConfig.ts @@ -32,7 +32,7 @@ export type Config = { keepOpenPostForm: boolean; useEmojiReaction: boolean; showEmojiReaction: boolean; - showImage: boolean; + showMedia: boolean; // TODO 'always' | 'only-followings' | 'never' hideCount: boolean; mutedPubkeys: string[]; mutedKeywords: string[]; @@ -80,7 +80,7 @@ const InitialConfig = (): Config => ({ keepOpenPostForm: false, useEmojiReaction: false, showEmojiReaction: false, - showImage: true, + showMedia: true, hideCount: false, mutedPubkeys: [], mutedKeywords: [], diff --git a/src/locales/en.ts b/src/locales/en.ts index 409a6bd..949247e 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -98,7 +98,7 @@ export default { showEmojiReaction: 'Show emoji reactions on posts', others: 'Others', keepOpenPostForm: 'Remain the input field open after posting', - showImagesByDefault: 'Load images by default', + showMediaByDefault: 'Load media by default', hideNumbers: 'Hide the numbers of reactions, reposts and followers', }, customEmoji: { diff --git a/src/locales/ja.ts b/src/locales/ja.ts index e67e615..a777da0 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -96,7 +96,7 @@ export default { showEmojiReaction: '投稿にリアクションされた絵文字を表示する', others: 'その他', keepOpenPostForm: '投稿後も投稿欄を開いたままにする', - showImagesByDefault: 'デフォルトで画像を読み込む', + showMediaByDefault: 'デフォルトで画像や動画を読み込む', hideNumbers: 'いいねやリポスト、フォロワーなどの数を隠す', }, customEmoji: { diff --git a/src/nostr/event/channel.ts b/src/nostr/event/channel.ts index 1d297d7..84f3fc3 100644 --- a/src/nostr/event/channel.ts +++ b/src/nostr/event/channel.ts @@ -1,15 +1,9 @@ import { z } from 'zod'; -import { isImageUrl } from '@/utils/imageUrl'; - 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(), + picture: z.string().url().optional(), }); export type ChannelMeta = z.infer; diff --git a/src/utils/imageUrl.test.ts b/src/utils/url.test.ts similarity index 89% rename from src/utils/imageUrl.test.ts rename to src/utils/url.test.ts index 61249a1..47d639d 100644 --- a/src/utils/imageUrl.test.ts +++ b/src/utils/url.test.ts @@ -2,7 +2,7 @@ import assert from 'assert'; import { describe, it } from 'vitest'; -import { fixUrl } from '@/utils/imageUrl'; +import { fixUrl } from '@/utils/url'; describe('fixUrl', () => { it('should return an image url for a given imgur.com URL with additional path', () => { diff --git a/src/utils/imageUrl.ts b/src/utils/url.ts similarity index 82% rename from src/utils/imageUrl.ts rename to src/utils/url.ts index 4e0b2ee..94e0acc 100644 --- a/src/utils/imageUrl.ts +++ b/src/utils/url.ts @@ -7,6 +7,15 @@ export const isImageUrl = (urlString: string): boolean => { } }; +export const isVideoUrl = (urlString: string): boolean => { + try { + const url = new URL(urlString); + return /\.(mpg|mpeg|mp4|avi|mov|webm|ogv)$/i.test(url.pathname); + } catch { + return false; + } +}; + export const fixUrl = (urlString: string): string => { try { const url = new URL(urlString);