From 819131d47162d6056a40a937510d2db1462ffc13 Mon Sep 17 00:00:00 2001 From: mv141575 Date: Mon, 4 May 2026 13:24:28 +0200 Subject: [PATCH 01/11] Initial implementation of embeding Zenodo videos Co-authored-by: Copilot --- app/assets/javascripts/materials.js | 15 ++++++++++++ app/helpers/materials_helper.rb | 10 +++++++- app/views/materials/show.html.erb | 1 + lib/renderers/zenodo.rb | 36 +++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/materials.js create mode 100644 lib/renderers/zenodo.rb diff --git a/app/assets/javascripts/materials.js b/app/assets/javascripts/materials.js new file mode 100644 index 000000000..039f1a214 --- /dev/null +++ b/app/assets/javascripts/materials.js @@ -0,0 +1,15 @@ +function make_zenodo_video(video_element, files_url) { + const videoExtensions = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.m4v', '.mkv']; + + fetch(files_url) + .then(response => response.json()) + .then(data => { + const video_file = data.entries.find(file => videoExtensions.some(ext => file.key.endsWith(ext))); + if (video_file) { + const video_url = video_file.links.content; + video_element.src = video_url; + video_element.style.display = 'block'; + } + }) + .catch(error => console.error('Error fetching Zenodo files:', error)); +} \ No newline at end of file diff --git a/app/helpers/materials_helper.rb b/app/helpers/materials_helper.rb index 0fc4d362e..04e8ffe38 100644 --- a/app/helpers/materials_helper.rb +++ b/app/helpers/materials_helper.rb @@ -124,7 +124,15 @@ def display_attribute_no_label(resource, attribute, markdown: false, &block) # r end def embed_youtube(material) - renderer = Renderers::Youtube.new(material) + embed_video(Renderers::Youtube, material) + end + + def embed_zenodo(material) + embed_video(Renderers::Zenodo, material) + end + + def embed_video(renderer_class, material) + renderer = renderer_class.new(material) return unless renderer.can_render? content_tag(:div, class: 'embedded-content') do diff --git a/app/views/materials/show.html.erb b/app/views/materials/show.html.erb index e180b97b6..81131bcc6 100644 --- a/app/views/materials/show.html.erb +++ b/app/views/materials/show.html.erb @@ -47,6 +47,7 @@ <%= embed_youtube(@material) %> + <%= embed_zenodo(@material) %>
diff --git a/lib/renderers/zenodo.rb b/lib/renderers/zenodo.rb new file mode 100644 index 000000000..065876deb --- /dev/null +++ b/lib/renderers/zenodo.rb @@ -0,0 +1,36 @@ +module Renderers + class Zenodo + VALID_HOSTS = %w[zenodo.org doi.org].freeze + VALID_SCHEMES = %w[http https].freeze + TEMPLATE = %() + + def initialize(resource) + @resource = resource + end + + def can_render? + @resource.url && construct_files_url(@resource.url) + end + + def render_content + files_url = construct_files_url(@resource.url) + (TEMPLATE % { files_url: files_url }).html_safe + end + + def extract_record_id(url) + parsed_url = URI.parse(url) + return unless VALID_SCHEMES.include?(parsed_url.scheme) + + match = parsed_url.host == 'zenodo.org' && url.match(/records\/(\d+)/) || + parsed_url.host == 'doi.org' && url.match(/10\.5281\/zenodo\.(\d+)/) + match[1] if match + rescue + nil + end + + def construct_files_url(url) + record_id = extract_record_id(url) + "https://zenodo.org/api/records/#{record_id}/files" if record_id + end + end +end \ No newline at end of file From 538148f34b462f840833af66068463e93ff390c8 Mon Sep 17 00:00:00 2001 From: mv141575 Date: Mon, 4 May 2026 17:01:30 +0200 Subject: [PATCH 02/11] Zenodo video preview style improvements Co-authored-by: Copilot --- app/assets/javascripts/materials.js | 6 +++++- app/assets/stylesheets/application.scss | 3 ++- lib/renderers/zenodo.rb | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/materials.js b/app/assets/javascripts/materials.js index 039f1a214..258bea4e0 100644 --- a/app/assets/javascripts/materials.js +++ b/app/assets/javascripts/materials.js @@ -1,10 +1,14 @@ function make_zenodo_video(video_element, files_url) { const videoExtensions = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.m4v', '.mkv']; + const audioExtensions = ['.mp3', '.ogg', '.wav', '.aac', '.flac', '.opus']; fetch(files_url) .then(response => response.json()) .then(data => { - const video_file = data.entries.find(file => videoExtensions.some(ext => file.key.endsWith(ext))); + let video_file = data.entries.find(file => videoExtensions.some(ext => file.key.toLowerCase().endsWith(ext))); + if (!video_file) { // fallback to audio + video_file = data.entries.find(file => audioExtensions.some(ext => file.key.toLowerCase().endsWith(ext))); + } if (video_file) { const video_url = video_file.links.content; video_element.src = video_url; diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 51dca1c07..af8996f3e 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -886,10 +886,11 @@ input[type=checkbox].field-lock + label:before, .embedded-content { margin: 2em auto; + display: flex; + justify-content: center; iframe { display: block; - margin: auto; } } diff --git a/lib/renderers/zenodo.rb b/lib/renderers/zenodo.rb index 065876deb..0cc7433d2 100644 --- a/lib/renderers/zenodo.rb +++ b/lib/renderers/zenodo.rb @@ -2,7 +2,7 @@ module Renderers class Zenodo VALID_HOSTS = %w[zenodo.org doi.org].freeze VALID_SCHEMES = %w[http https].freeze - TEMPLATE = %() + TEMPLATE = %() def initialize(resource) @resource = resource From 5d415ff6418ebb1861b74b489bc9599781ec0aa7 Mon Sep 17 00:00:00 2001 From: mv141575 Date: Mon, 4 May 2026 17:18:06 +0200 Subject: [PATCH 03/11] Test zenodo video embedding Co-authored-by: Copilot --- test/controllers/materials_controller_test.rb | 8 ++++++++ test/fixtures/materials.yml | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/test/controllers/materials_controller_test.rb b/test/controllers/materials_controller_test.rb index adf68ac00..e388d9b9d 100644 --- a/test/controllers/materials_controller_test.rb +++ b/test/controllers/materials_controller_test.rb @@ -1353,6 +1353,14 @@ class MaterialsControllerTest < ActionController::TestCase assert_select 'div.embedded-content iframe[src=?]', 'https://www.youtube.com/embed/1T_2xMTQCv4' end + test 'can render embedded zenodo video' do + sign_in users(:regular_user) + get :show, params: { id: materials(:zenodo_video_material) } + assert_response :success + + assert_select 'div.embedded-content video#zenodo-video' + end + test 'no embedded content section if not available' do sign_in users(:regular_user) get :show, params: { id: materials(:good_material) } diff --git a/test/fixtures/materials.yml b/test/fixtures/materials.yml index 10eaa65a5..5c9ba9057 100644 --- a/test/fixtures/materials.yml +++ b/test/fixtures/materials.yml @@ -172,6 +172,20 @@ youtube_video_material: - Video learning_objectives: In this video, I will show you how to create and run data quality checks in REDCap. +zenodo_video_material: + user: regular_user + title: 'How to Soften Hard Water?' + url: https://zenodo.org/records/17402249 + description: In this video, I will show you how to soften hard water. + licence: CC-BY-NC-4.0 + contact: contact@elixir-uk.org + keywords: + - water + - " hard water" + resource_type: + - Video + learning_objectives: In this video, I will show you how to soften hard water. + plant_space_material: user: regular_user title: Plant material From 8ea2f80fc20c37ce80fcc7fd035aeed112af4314 Mon Sep 17 00:00:00 2001 From: mv141575 Date: Mon, 4 May 2026 19:24:58 +0200 Subject: [PATCH 04/11] Test zenodo video preview --- config/sunspot.example.yml | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config/sunspot.example.yml b/config/sunspot.example.yml index 18961511b..857a550fa 100644 --- a/config/sunspot.example.yml +++ b/config/sunspot.example.yml @@ -16,5 +16,5 @@ test: solr: hostname: localhost port: 8983 - log_level: WARNING + log_level: INFO path: /solr/tess_test \ No newline at end of file diff --git a/package.json b/package.json index 3ce294f94..a00e2ddf7 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,6 @@ "markdown-it": "^12.3.2", "moment": "^2.30.1", "select2": "^4.0.8" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } From d1c97b65313015d30472d7662110457306a611b8 Mon Sep 17 00:00:00 2001 From: mv141575 Date: Mon, 4 May 2026 22:07:07 +0200 Subject: [PATCH 05/11] Read preview_file parameter when previewing zenodo video Co-authored-by: Copilot --- app/assets/javascripts/materials.js | 7 +++++-- lib/renderers/zenodo.rb | 24 +++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/materials.js b/app/assets/javascripts/materials.js index 258bea4e0..f1f7f1db0 100644 --- a/app/assets/javascripts/materials.js +++ b/app/assets/javascripts/materials.js @@ -1,11 +1,14 @@ -function make_zenodo_video(video_element, files_url) { +function make_zenodo_video(video_element, files_url, preferred_key) { const videoExtensions = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.m4v', '.mkv']; const audioExtensions = ['.mp3', '.ogg', '.wav', '.aac', '.flac', '.opus']; fetch(files_url) .then(response => response.json()) .then(data => { - let video_file = data.entries.find(file => videoExtensions.some(ext => file.key.toLowerCase().endsWith(ext))); + let video_file = data.entries.find(file => file.key == preferred_key); + if (!video_file) { + video_file = data.entries.find(file => videoExtensions.some(ext => file.key.toLowerCase().endsWith(ext))); + } if (!video_file) { // fallback to audio video_file = data.entries.find(file => audioExtensions.some(ext => file.key.toLowerCase().endsWith(ext))); } diff --git a/lib/renderers/zenodo.rb b/lib/renderers/zenodo.rb index 0cc7433d2..37bac8740 100644 --- a/lib/renderers/zenodo.rb +++ b/lib/renderers/zenodo.rb @@ -2,35 +2,33 @@ module Renderers class Zenodo VALID_HOSTS = %w[zenodo.org doi.org].freeze VALID_SCHEMES = %w[http https].freeze - TEMPLATE = %() + TEMPLATE = %() def initialize(resource) @resource = resource end def can_render? - @resource.url && construct_files_url(@resource.url) + url && render_content.present? end - def render_content - files_url = construct_files_url(@resource.url) - (TEMPLATE % { files_url: files_url }).html_safe + def url + @resource.url end - def extract_record_id(url) - parsed_url = URI.parse(url) + def render_content + parsed_url = Addressable::URI.parse(url) return unless VALID_SCHEMES.include?(parsed_url.scheme) match = parsed_url.host == 'zenodo.org' && url.match(/records\/(\d+)/) || parsed_url.host == 'doi.org' && url.match(/10\.5281\/zenodo\.(\d+)/) - match[1] if match + return unless match + + files_url = "https://zenodo.org/api/records/#{match[1]}/files" + key = parsed_url.query_values.to_h['preview_file'] + (TEMPLATE % { files_url: files_url, key: key.to_json }).html_safe rescue nil end - - def construct_files_url(url) - record_id = extract_record_id(url) - "https://zenodo.org/api/records/#{record_id}/files" if record_id - end end end \ No newline at end of file From 4ee1d87c13def808963d1d844c218054f873a234 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Tue, 5 May 2026 10:14:50 +0200 Subject: [PATCH 06/11] Revert package.json changes --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index a00e2ddf7..3ce294f94 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,5 @@ "markdown-it": "^12.3.2", "moment": "^2.30.1", "select2": "^4.0.8" - }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + } } From c2db1198ef870db46185d5733631a6a5370f58fb Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Tue, 5 May 2026 10:27:33 +0200 Subject: [PATCH 07/11] Revert sunspot.example.yml change --- config/sunspot.example.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sunspot.example.yml b/config/sunspot.example.yml index 857a550fa..18961511b 100644 --- a/config/sunspot.example.yml +++ b/config/sunspot.example.yml @@ -16,5 +16,5 @@ test: solr: hostname: localhost port: 8983 - log_level: INFO + log_level: WARNING path: /solr/tess_test \ No newline at end of file From 80887edf28572ee725c4c72daa8c5f2acb5e5954 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Tue, 5 May 2026 11:12:02 +0200 Subject: [PATCH 08/11] Refactor zenodo.rb for slight performance improvement --- lib/renderers/zenodo.rb | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/renderers/zenodo.rb b/lib/renderers/zenodo.rb index 37bac8740..ea87e10c8 100644 --- a/lib/renderers/zenodo.rb +++ b/lib/renderers/zenodo.rb @@ -5,30 +5,26 @@ class Zenodo TEMPLATE = %() def initialize(resource) - @resource = resource + @url = resource.url + @parsed_url = Addressable::URI.parse(@url) rescue nil end def can_render? - url && render_content.present? - end - - def url - @resource.url + @url && @parsed_url && zenodo_id && VALID_SCHEMES.include?(@parsed_url.scheme) end def render_content - parsed_url = Addressable::URI.parse(url) - return unless VALID_SCHEMES.include?(parsed_url.scheme) + files_url = "https://zenodo.org/api/records/#{zenodo_id}/files" + key = @parsed_url.query_values.to_h['preview_file'] + (TEMPLATE % { files_url: files_url, key: key.to_json }).html_safe + end - match = parsed_url.host == 'zenodo.org' && url.match(/records\/(\d+)/) || - parsed_url.host == 'doi.org' && url.match(/10\.5281\/zenodo\.(\d+)/) - return unless match + private - files_url = "https://zenodo.org/api/records/#{match[1]}/files" - key = parsed_url.query_values.to_h['preview_file'] - (TEMPLATE % { files_url: files_url, key: key.to_json }).html_safe - rescue - nil + def zenodo_id + match = @parsed_url.host == 'zenodo.org' && @url.match(/records\/(\d+)/) || + @parsed_url.host == 'doi.org' && @url.match(/10\.5281\/zenodo\.(\d+)/) + match[1] if match end end end \ No newline at end of file From 7e90a2a5601d3a05856eac115092bda3d381007c Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Tue, 5 May 2026 11:16:38 +0200 Subject: [PATCH 09/11] Remove unused VALID_HOSTS variable in Zenodo renderer --- lib/renderers/zenodo.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/renderers/zenodo.rb b/lib/renderers/zenodo.rb index ea87e10c8..bd51c2cdd 100644 --- a/lib/renderers/zenodo.rb +++ b/lib/renderers/zenodo.rb @@ -1,12 +1,15 @@ module Renderers class Zenodo - VALID_HOSTS = %w[zenodo.org doi.org].freeze VALID_SCHEMES = %w[http https].freeze - TEMPLATE = %() + TEMPLATE = %() def initialize(resource) @url = resource.url - @parsed_url = Addressable::URI.parse(@url) rescue nil + @parsed_url = begin + Addressable::URI.parse(@url) + rescue StandardError + nil + end end def can_render? @@ -16,15 +19,15 @@ def can_render? def render_content files_url = "https://zenodo.org/api/records/#{zenodo_id}/files" key = @parsed_url.query_values.to_h['preview_file'] - (TEMPLATE % { files_url: files_url, key: key.to_json }).html_safe + format(TEMPLATE, files_url:, key: key.to_json).html_safe end private def zenodo_id - match = @parsed_url.host == 'zenodo.org' && @url.match(/records\/(\d+)/) || - @parsed_url.host == 'doi.org' && @url.match(/10\.5281\/zenodo\.(\d+)/) + match = @parsed_url.host == 'zenodo.org' && @url.match(%r{records/(\d+)}) || + @parsed_url.host == 'doi.org' && @url.match(%r{10\.5281/zenodo\.(\d+)}) match[1] if match end end -end \ No newline at end of file +end From 175e30f1e4eb6d74e8aed7376d57e5c8c3436747 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Tue, 5 May 2026 11:35:32 +0200 Subject: [PATCH 10/11] Refactor Zenodo video embedding --- app/assets/javascripts/materials.js | 5 ++++- test/fixtures/materials.yml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/materials.js b/app/assets/javascripts/materials.js index f1f7f1db0..25385dac9 100644 --- a/app/assets/javascripts/materials.js +++ b/app/assets/javascripts/materials.js @@ -5,7 +5,10 @@ function make_zenodo_video(video_element, files_url, preferred_key) { fetch(files_url) .then(response => response.json()) .then(data => { - let video_file = data.entries.find(file => file.key == preferred_key); + let video_file = null; + if (preferred_key != null) { + video_file = data.entries.find(file => file.key === String(preferred_key)); + } if (!video_file) { video_file = data.entries.find(file => videoExtensions.some(ext => file.key.toLowerCase().endsWith(ext))); } diff --git a/test/fixtures/materials.yml b/test/fixtures/materials.yml index 5c9ba9057..34de6f04d 100644 --- a/test/fixtures/materials.yml +++ b/test/fixtures/materials.yml @@ -181,7 +181,7 @@ zenodo_video_material: contact: contact@elixir-uk.org keywords: - water - - " hard water" + - "hard water" resource_type: - Video learning_objectives: In this video, I will show you how to soften hard water. From a45cd7bbaa31c48f293f753d9e3c2e26b6dc4030 Mon Sep 17 00:00:00 2001 From: Martin Voigt Date: Tue, 5 May 2026 12:24:22 +0200 Subject: [PATCH 11/11] Hide embedding element completely when there is no Zenodo video to embed --- app/assets/javascripts/materials.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/materials.js b/app/assets/javascripts/materials.js index 25385dac9..1ec5544cc 100644 --- a/app/assets/javascripts/materials.js +++ b/app/assets/javascripts/materials.js @@ -1,6 +1,7 @@ function make_zenodo_video(video_element, files_url, preferred_key) { const videoExtensions = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.m4v', '.mkv']; const audioExtensions = ['.mp3', '.ogg', '.wav', '.aac', '.flac', '.opus']; + video_element.parentElement.style.display = 'none'; fetch(files_url) .then(response => response.json()) @@ -19,6 +20,7 @@ function make_zenodo_video(video_element, files_url, preferred_key) { const video_url = video_file.links.content; video_element.src = video_url; video_element.style.display = 'block'; + video_element.parentElement.style.display = 'block'; } }) .catch(error => console.error('Error fetching Zenodo files:', error));