From b8c2c221ca0c04016e50b28417eac48930118e22 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Mon, 25 Aug 2025 23:30:41 +0900 Subject: [PATCH] =?UTF-8?q?#17=20YouTube=20=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package-lock.json | 75 ++++++++++++++++++++- frontend/package.json | 1 + frontend/src/components/PostEmbed.tsx | 48 +++++++++++++ frontend/src/pages/posts/PostDetailPage.tsx | 24 +------ 4 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 frontend/src/components/PostEmbed.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 06b8d4c..8096cde 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,6 +25,7 @@ "react-markdown": "^10.1.0", "react-markdown-editor-lite": "^1.3.4", "react-router-dom": "^6.30.0", + "react-youtube": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.3.0" }, @@ -3376,7 +3377,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -4169,6 +4169,12 @@ "uc.micro": "^2.0.0" } }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5275,7 +5281,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5650,6 +5655,17 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -5759,6 +5775,12 @@ "react": "^16.6.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/react-markdown": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -5912,6 +5934,23 @@ } } }, + "node_modules/react-youtube": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz", + "integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "3.1.3", + "prop-types": "15.8.1", + "youtube-player": "5.5.2" + }, + "engines": { + "node": ">= 14.x" + }, + "peerDependencies": { + "react": ">=0.14.1" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6165,6 +6204,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sister": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz", + "integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA==", + "license": "BSD-3-Clause" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -7105,6 +7150,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/youtube-player": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz", + "integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==", + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^2.6.6", + "load-script": "^1.0.0", + "sister": "^3.0.0" + } + }, + "node_modules/youtube-player/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/youtube-player/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 76b5bba..9077da9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "react-markdown": "^10.1.0", "react-markdown-editor-lite": "^1.3.4", "react-router-dom": "^6.30.0", + "react-youtube": "^10.1.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.3.0" }, diff --git a/frontend/src/components/PostEmbed.tsx b/frontend/src/components/PostEmbed.tsx new file mode 100644 index 0000000..85ff567 --- /dev/null +++ b/frontend/src/components/PostEmbed.tsx @@ -0,0 +1,48 @@ +import YoutubeEmbed from 'react-youtube' + +import NicoViewer from '@/components/NicoViewer' +import TwitterEmbed from '@/components/TwitterEmbed' + +import type { FC } from 'react' + +import type { Post } from '@/types' + +type Props = { post: Post } + + +export default (({ post }: Props) => { + const url = new URL (post.url) + + switch (url.hostname.split ('.').slice (-2).join ('.')) + { + case 'nicovideo.jp': + { + const [videoId] = url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/)! + return + } + case 'twitter.com': + case 'x.com': + const [userId] = url.pathname.match (/(?<=\/)[^\/]+?(?=\/|$|\?)/)! + const [statusId] = url.pathname.match (/(?<=\/status\/)\d+?(?=\/|$|\?)/)! + return + case 'youtube.com': + { + const [videoId] = url.pathname.match (/(?<=\/watch\?v\=)[a-zA-Z0-9]+?(?=\/|$|&)/)! + return ( + ) + } + } + + return ( + + {post.url} + ) +}) satisfies FC diff --git a/frontend/src/pages/posts/PostDetailPage.tsx b/frontend/src/pages/posts/PostDetailPage.tsx index 0d2ae31..2955530 100644 --- a/frontend/src/pages/posts/PostDetailPage.tsx +++ b/frontend/src/pages/posts/PostDetailPage.tsx @@ -6,9 +6,8 @@ import { useParams } from 'react-router-dom' import PostList from '@/components/PostList' import TagDetailSidebar from '@/components/TagDetailSidebar' -import NicoViewer from '@/components/NicoViewer' import PostEditForm from '@/components/PostEditForm' -import TwitterEmbed from '@/components/TwitterEmbed' +import PostEmbed from '@/components/PostEmbed' import TabGroup, { Tab } from '@/components/common/TabGroup' import MainArea from '@/components/layout/MainArea' import { Button } from '@/components/ui/button' @@ -22,27 +21,10 @@ import type { FC } from 'react' import type { Post, User } from '@/types' -const PostEmbed: FC<{ post: Post }> = ({ post }: { post: Post }) => { - const url = new URL (post.url) - switch (url.hostname.split ('.').slice (-2).join ('.')) - { - case 'nicovideo.jp': - const [videoId] = url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/)! - return - case 'twitter.com': - case 'x.com': - const [userId] = url.pathname.match (/(?<=\/)[^\/]+?(?=\/|$)/)! - const [statusId] = url.pathname.match (/(?<=\/status\/)\d+?(?=\/|$)/)! - return - default: - return {post.url} - } -} - type Props = { user: User | null } -export default ({ user }: Props) => { +export default (({ user }: Props) => { const { id } = useParams () const [post, setPost] = useState (null) @@ -142,4 +124,4 @@ export default ({ user }: Props) => { ) -} +}) satisfies FC