From e48c6ff79bf210a606aaba43565785e2b28733c2 Mon Sep 17 00:00:00 2001 From: Yohei Yasukawa Date: Sat, 25 Apr 2026 18:09:21 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=9D=E3=83=83=E3=83=89=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=82=B9=E3=83=88=E5=90=84=E3=82=A8=E3=83=94=E3=82=BD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AB=E3=82=B5=E3=83=A0=E3=83=8D=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E4=BB=A3=E3=82=8F=E3=82=8A=E3=81=AB=20YouTube=20?= =?UTF-8?q?=E5=9F=8B=E3=82=81=E8=BE=BC=E3=81=BF=E3=83=97=E3=83=AC=E3=82=A4?= =?UTF-8?q?=E3=83=A4=E3=83=BC=E3=82=92=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `_youtube_embed.html.erb` パーシャルを追加(16:9 レスポンシブ、プレイリスト付き、rel=0) - `convert_shownote` でサムネイル画像を YouTube embed に置換 - `Podcast::YOUTUBE_PLAYLIST_ID` 定数を追加 - `.episode-cover` の max-width 制約を削除してフル幅表示に対応 - テストフィクスチャに YouTube URL を追加 --- app/controllers/podcasts_controller.rb | 5 ++- app/models/podcast.rb | 3 +- app/views/podcasts/_youtube_embed.html.erb | 5 +++ app/views/podcasts/show.html.erb | 11 ++----- spec/features/podcasts_spec.rb | 36 +++++++++++----------- 5 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 app/views/podcasts/_youtube_embed.html.erb diff --git a/app/controllers/podcasts_controller.rb b/app/controllers/podcasts_controller.rb index 9476d90f..2c537911 100644 --- a/app/controllers/podcasts_controller.rb +++ b/app/controllers/podcasts_controller.rb @@ -27,6 +27,7 @@ def show @url = request.url @title = @episode.title.split('-').last.strip @date = @episode.published_date.strftime("%Y年%-m月%-d日(#{Podcast::WDAY2JAPANESE[@episode.published_date.wday]})") + @youtube_id = @episode.content.match(Podcast::REGEX_YOUTUBE_ID)[1] @content = Kramdown::Document.new( self.convert_shownote(@episode.content), input: 'GFM').to_html @@ -45,8 +46,10 @@ def convert_shownote(content) content.gsub!(/(#+) Shownote/) { shownote } return content unless content.match?(Podcast::REGEX_YOUTUBE_ID) + youtube_id = content.match(Podcast::REGEX_YOUTUBE_ID)[1] + embed = render_to_string(partial: 'podcasts/youtube_embed', locals: { youtube_id: youtube_id }) + content.gsub!(/]+>\s*]+Cover Photo[^>]*>\s*<\/a>/m, embed) return content unless content.match?(Podcast::REGEX_TIMESTAMP) - youtube_id = @episode.content.match(Podcast::REGEX_YOUTUBE_ID)[1] content.gsub!(Podcast::REGEX_TIMESTAMP) do original_t = $1 parts = original_t.split(':') diff --git a/app/models/podcast.rb b/app/models/podcast.rb index 6c2f0c2c..70279b46 100644 --- a/app/models/podcast.rb +++ b/app/models/podcast.rb @@ -1,7 +1,8 @@ class Podcast < ApplicationRecord self.table_name = 'podcasts' DIR_PATH = 'public/podcasts' - WDAY2JAPANESE = %w(日 月 火 水 木 金 土) + WDAY2JAPANESE = %w(日 月 火 水 木 金 土) + YOUTUBE_PLAYLIST_ID = 'PL94GDfaSQTmJxxnapafkApHYgQUJ6ABUU' # Match timestamps at the beginning of lines (YouTube format) REGEX_TIMESTAMP = /^((\d{1,2}:)?\d{1,2}:\d{2})/ REGEX_YOUTUBE_ID = /watch\?v=((\w)*)/ diff --git a/app/views/podcasts/_youtube_embed.html.erb b/app/views/podcasts/_youtube_embed.html.erb new file mode 100644 index 00000000..b673ef5d --- /dev/null +++ b/app/views/podcasts/_youtube_embed.html.erb @@ -0,0 +1,5 @@ +
+ +
diff --git a/app/views/podcasts/show.html.erb b/app/views/podcasts/show.html.erb index 25cb00f9..b06d3a6b 100644 --- a/app/views/podcasts/show.html.erb +++ b/app/views/podcasts/show.html.erb @@ -13,13 +13,8 @@ h1#title { margin-top: 30px; margin-bottom: 20px; text-align: center; } .episode h2 { margin-top: 100px; } - .episode-cover { - margin: 30px auto; - @media screen and (min-width: 640px) { - max-width: 70%; - } - } - .episode-cover img { margin-bottom: 20px;} + .episode-cover { margin: 30px auto; } + .episode-cover img { margin-bottom: 20px; } .episode ul { padding-left: 0px; list-style: none; @@ -45,7 +40,7 @@
- +
<%= render 'shared/social_buttons' %>
diff --git a/spec/features/podcasts_spec.rb b/spec/features/podcasts_spec.rb index e9c9ba1b..387bf857 100644 --- a/spec/features/podcasts_spec.rb +++ b/spec/features/podcasts_spec.rb @@ -11,7 +11,7 @@ @podcast = create(:podcast) allow(@podcast).to receive(:exist?) { true } allow(@podcast).to receive(:exist?).with(offset: -1) { false } - allow(@podcast).to receive(:content) { "title\n収録日: 2019/05/10\n..." } + allow(@podcast).to receive(:content) { "title\n収録日: 2019/05/10\nhttps://www.youtube.com/watch?v=test123\n..." } allow(Podcast).to receive(:find_by).with(id: @podcast.id.to_s) { @podcast } visit "/podcasts/#{@podcast.id}" @@ -26,7 +26,7 @@ scenario 'Load doc file with absolute path' do @podcast = create(:podcast) allow(@podcast).to receive(:exist?) { true } - allow(@podcast).to receive(:content) { "title\n収録日: 2019/05/10\n..." } + allow(@podcast).to receive(:content) { "title\n収録日: 2019/05/10\nhttps://www.youtube.com/watch?v=test123\n..." } allow(Podcast).to receive(:find_by).with(id: @podcast.id.to_s) { @podcast } visit "/podcasts/#{@podcast.id}" @@ -40,15 +40,15 @@ scenario 'Show note timestamps are converted to YouTube links' do @podcast = create(:podcast) allow(@podcast).to receive(:exist?) { true } - allow(@podcast).to receive(:content) { + allow(@podcast).to receive(:content) { <<~CONTENT タイトル 収録日: 2019/05/10 - + YouTubeリンク: https://www.youtube.com/watch?v=Dd9IYiF0R6E - + ## Shownote - + 00:00:00 米国系 IT 企業から CoderDojo へ、233台のノートPC寄贈 00:25:01 AI と遊んでみる回の動画 https://youtu.be/BYpa1CcYtss?t=1425 00:59:14 CASE Shinjuku 利用者と CoderDojo の繋がり @@ -59,13 +59,13 @@ visit "/podcasts/#{@podcast.id}" expect(page).to have_http_status(:success) - + # タイムスタンプがYouTubeリンクに変換されているか確認 expect(page).to have_link '00:00:00', href: 'https://youtu.be/Dd9IYiF0R6E?t=00h00m00s' expect(page).to have_link '00:25:01', href: 'https://youtu.be/Dd9IYiF0R6E?t=00h25m01s' expect(page).to have_link '00:59:14', href: 'https://youtu.be/Dd9IYiF0R6E?t=00h59m14s' expect(page).to have_link '01:00:57', href: 'https://youtu.be/Dd9IYiF0R6E?t=01h00m57s' - + # 既存のURL付きタイムスタンプはそのまま表示されること expect(page).to have_content 'AI と遊んでみる回の動画 https://youtu.be/BYpa1CcYtss?t=1425' expect(page).to have_content 'CASE Shinjuku の英語アクセスページ https://case-shinjuku.com/english' @@ -74,15 +74,15 @@ scenario 'Show note timestamps with mm:ss format are converted to YouTube links' do @podcast = create(:podcast) allow(@podcast).to receive(:exist?) { true } - allow(@podcast).to receive(:content) { + allow(@podcast).to receive(:content) { <<~CONTENT タイトル 収録日: 2019/05/10 - + YouTubeリンク: https://www.youtube.com/watch?v=test123 - + ## Shownote - + 00:30 オープニング 05:45 メインテーマ 59:59 エンディング @@ -92,7 +92,7 @@ visit "/podcasts/#{@podcast.id}" expect(page).to have_http_status(:success) - + # mm:ss形式のタイムスタンプもYouTubeリンクに変換されているか確認 expect(page).to have_link '00:30', href: 'https://youtu.be/test123?t=00m30s' expect(page).to have_link '05:45', href: 'https://youtu.be/test123?t=05m45s' @@ -102,15 +102,15 @@ scenario 'Show note timestamps with m:ss format (single digit minutes) are converted to YouTube links' do @podcast = create(:podcast) allow(@podcast).to receive(:exist?) { true } - allow(@podcast).to receive(:content) { + allow(@podcast).to receive(:content) { <<~CONTENT タイトル 収録日: 2019/05/10 - + YouTubeリンク: https://www.youtube.com/watch?v=episode21 - + ## Shownote - + 0:00 ゲスト自己紹介 0:54 TFabWorks 無償レンタルプログラム 2:10 2019年の年末から動き出した @@ -121,7 +121,7 @@ visit "/podcasts/#{@podcast.id}" expect(page).to have_http_status(:success) - + # m:ss形式(一桁の分)のタイムスタンプもYouTubeリンクに変換されているか確認 expect(page).to have_link '0:00', href: 'https://youtu.be/episode21?t=0m00s' expect(page).to have_link '0:54', href: 'https://youtu.be/episode21?t=0m54s'