diff --git a/app/assets/javascripts/player_listeners.js b/app/assets/javascripts/player_listeners.js index 1db0483b13..fb66f3fceb 100644 --- a/app/assets/javascripts/player_listeners.js +++ b/app/assets/javascripts/player_listeners.js @@ -20,6 +20,7 @@ let addToPlaylistListenersAdded = false; let firstLoad = true; let streamId = ''; let isMobile = false; +let isPlaying = false; /** * Bind action buttons on the page with player events and re-populate details @@ -69,17 +70,21 @@ function addActionButtonListeners(player, mediaObjectId, sectionIds) { } /* Add player event listeners to update UI components on the page */ - // Listen to 'timeupdate' event to udate add to playlist form when using while media is playing or manually seeking - player.player.on('timeupdate', () => { + // Listen to 'seeked' event to udate add to playlist form + player.player.on('seeked', () => { if (getActiveItem() != undefined) { activeTrack = getActiveItem(false); if (activeTrack != undefined) { streamId = activeTrack.streamId; } - disableEnableCurrentTrack(activeTrack, player.player.currentTime(), true, currentSectionLabel); + disableEnableCurrentTrack(activeTrack, player.player.currentTime(), isPlaying, currentSectionLabel); } }); + player.player.on('play', () => { isPlaying = true; }); + + player.player.on('pause', () => { isPlaying = false; }); + /* Disable action buttons tied to player related information on player's 'loadstart' event which functions parallel to the player's src changes. So, that the user doesn't interact with them get corrupted data @@ -273,7 +278,7 @@ function addToPlaylistListeners(sectionIds, mediaObjectId) { disableEnableCurrentTrack( activeTrack, currentTime, - false, + isPlaying, $('#playlist_item_title').val() || currentSectionLabel // Preserve user edits for the title when available ); $('#current-section-name').text(currentSectionLabel); diff --git a/app/assets/javascripts/ramp_utils.js b/app/assets/javascripts/ramp_utils.js index 30fbdefcc8..ea82abd688 100644 --- a/app/assets/javascripts/ramp_utils.js +++ b/app/assets/javascripts/ramp_utils.js @@ -111,7 +111,7 @@ function getTimelineScopes() { if (parent.length === 0) { let begin = 0; let end = activeItem.times.end; - scopes[0].times = { begin: 0, end: end } + scopes[0].times = { begin: 0, end: end }; } while (parent.length > 0) { let next = parent.closest('ul').closest('li'); @@ -209,10 +209,10 @@ function collapseMoreDetails() { * disable the option otherwise enable it. * @param {Object} activeTrack JSON object for the active timespans * @param {Number} currentTime player's playhead position - * @param {Boolean} isSeeked flag to indicate player 'seeked' event happened/not + * @param {Boolean} isPlaying flag to inidicate media is playing or not * @param {String} sectionTitle name of the current section */ -function disableEnableCurrentTrack(activeTrack, currentTime, isSeeked, sectionTitle) { +function disableEnableCurrentTrack(activeTrack, currentTime, isPlaying, sectionTitle) { // Return when add to playlist form is not visible let playlistForm = $('#add_to_playlist')[0]; if (!playlistForm) { @@ -222,7 +222,8 @@ function disableEnableCurrentTrack(activeTrack, currentTime, isSeeked, sectionTi if (activeTrack != undefined) { streamId = activeTrack.streamId; let { label, times, sectionLabel } = activeTrack; - let starttime = currentTime || times.begin; + // Update starttime when media is not playing + let starttime = isPlaying ? times.begin : currentTime || times.begin; $('#playlist_item_start').val(createTimestamp(starttime, true)); $('#playlist_item_end').val(createTimestamp(times.end, true)); title = `${sectionLabel} - ${label}`; @@ -245,9 +246,7 @@ function disableEnableCurrentTrack(activeTrack, currentTime, isSeeked, sectionTi $('#playlistitem_scope_track').prop('checked', false); } } - // Only change the title when user actively seeked to a different timestamp, - // persisting the user changes to the field unless the active track is changed - if (isSeeked && sectionTitle != undefined) { + if (sectionTitle != undefined) { $('#playlist_item_title').val(title); } } @@ -345,4 +344,10 @@ function resetAddToPlaylistForm() { function closeAlert() { $('#add_to_playlist_alert').slideUp(); $('#add_to_playlist_form_group').slideDown(); + // Set default selection in options list when alert is closed + if ($('#playlistitem_scope_track')[0].disabled) { + $('#playlistitem_scope_section').prop('checked', true); + } else { + $('#playlistitem_scope_track').prop('checked', true); + } } diff --git a/app/assets/stylesheets/avalon/_footer.scss b/app/assets/stylesheets/avalon/_footer.scss index 5313f9a3f9..413099019c 100644 --- a/app/assets/stylesheets/avalon/_footer.scss +++ b/app/assets/stylesheets/avalon/_footer.scss @@ -21,7 +21,7 @@ width: 100%; min-height: 2.5rem; color: #999999; - z-index: -1000; + z-index: 0; } footer { diff --git a/app/assets/stylesheets/avalon/_playlists.scss b/app/assets/stylesheets/avalon/_playlists.scss index f0d6e5468b..485fb18c1b 100644 --- a/app/assets/stylesheets/avalon/_playlists.scss +++ b/app/assets/stylesheets/avalon/_playlists.scss @@ -176,6 +176,7 @@ #Playlists_paginate { float: right; + z-index: 10; } /* Playlist copy modal */ diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 948342619a..3371bbb4db 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -89,18 +89,18 @@ class CatalogController < ApplicationController config.add_facet_field 'unit_ssim', label: 'Unit', limit: 5 config.add_facet_field 'language_ssim', label: 'Language', limit: 5 # Hide these facets if not a Collection Manager - config.add_facet_field 'workflow_published_sim', label: 'Published', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow" - config.add_facet_field 'avalon_uploader_ssi', label: 'Created by', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow" - config.add_facet_field 'read_access_group_ssim', label: 'Item access', if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", query: { + config.add_facet_field 'workflow_published_sim', label: 'Published', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow" + config.add_facet_field 'avalon_uploader_ssi', label: 'Created by', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow" + config.add_facet_field 'read_access_group_ssim', label: 'Item access', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", query: { public: { label: "Public", fq: "has_model_ssim:MediaObject AND read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_PUBLIC}" }, restricted: { label: "Authenticated", fq: "has_model_ssim:MediaObject AND read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_AUTHENTICATED}" }, private: { label: "Private", fq: "has_model_ssim:MediaObject AND NOT read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_PUBLIC} AND NOT read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_AUTHENTICATED}" } } - config.add_facet_field 'read_access_virtual_group_ssim', label: 'External Group', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", helper_method: :vgroup_display - config.add_facet_field 'date_digitized_ssim', label: 'Date Digitized', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow"#, partial: 'blacklight/hierarchy/facet_hierarchy' - config.add_facet_field 'date_ingested_ssim', label: 'Date Ingested', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow" - config.add_facet_field 'has_captions_bsi', label: 'Has Captions', if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript - config.add_facet_field 'has_transcripts_bsi', label: 'Has Transcripts', if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript + config.add_facet_field 'read_access_virtual_group_ssim', label: 'External Group', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :vgroup_display + config.add_facet_field 'date_digitized_ssim', label: 'Date Digitized', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow"#, partial: 'blacklight/hierarchy/facet_hierarchy' + config.add_facet_field 'date_ingested_ssim', label: 'Date Ingested', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow" + config.add_facet_field 'has_captions_bsi', label: 'Has Captions', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript + config.add_facet_field 'has_transcripts_bsi', label: 'Has Transcripts', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript # Have BL send all facet field names to Solr, which has been the default # previously. Simply remove these lines if you'd rather use Solr request diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ad555e556c..2f3e8e3c7f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -61,8 +61,8 @@ def lti_share_url_for(obj, _opts = {}) def image_for(document) master_file_id = document["section_id_ssim"].try :first - video_count = document["avalon_resource_type_ssim"].count{|m| m.start_with?('moving image') } rescue 0 - audio_count = document["avalon_resource_type_ssim"].count{|m| m.start_with?('sound recording') } rescue 0 + video_count = document["avalon_resource_type_ssim"].count{|m| m.downcase.start_with?('moving image') } rescue 0 + audio_count = document["avalon_resource_type_ssim"].count{|m| m.downcase.start_with?('sound recording') } rescue 0 if master_file_id if video_count > 0 diff --git a/app/helpers/blacklight/local_blacklight_helper.rb b/app/helpers/blacklight/local_blacklight_helper.rb index ecc2e18ca6..c11b38074e 100644 --- a/app/helpers/blacklight/local_blacklight_helper.rb +++ b/app/helpers/blacklight/local_blacklight_helper.rb @@ -26,7 +26,7 @@ def facet_group_names end def url_for_document doc, options = {} - SpeedyAF::Base.find(doc[:id]) + SpeedyAF::Base.for(doc.to_h.with_indifferent_access) end def contributor_index_display args diff --git a/app/javascript/components/PlaylistRamp.jsx b/app/javascript/components/PlaylistRamp.jsx index 2efd8af22b..fb8e5927e9 100644 --- a/app/javascript/components/PlaylistRamp.jsx +++ b/app/javascript/components/PlaylistRamp.jsx @@ -223,7 +223,7 @@ const Ramp = ({ - + {comment && (

{comment_label}

@@ -251,7 +251,7 @@ const Ramp = ({ {playlist_item_ids?.length > 0 && ( -

Playlist Items

+

Playlist Items

)} diff --git a/app/models/master_file.rb b/app/models/master_file.rb index 6f911d0e79..789471d64c 100644 --- a/app/models/master_file.rb +++ b/app/models/master_file.rb @@ -597,7 +597,7 @@ def create_frame_source_hls_temp_file(offset) # Fixes https://github.com/avalonmediasystem/avalon/issues/3474 target_location = File.basename(details[:location]).split('?')[0] target = File.join(Dir.tmpdir, target_location) - File.open(target,'wb') { |f| URI.open(details[:location]) { |io| f.write(io.read) } } + File.open(target,'wb') { |f| URI.open(details[:location], "Referer" => Rails.application.routes.url_helpers.root_url) { |io| f.write(io.read) } } return target, details[:offset] end diff --git a/app/models/media_object.rb b/app/models/media_object.rb index e2030cd738..c1eb6aa305 100644 --- a/app/models/media_object.rb +++ b/app/models/media_object.rb @@ -177,7 +177,7 @@ def section_ids return @section_ids if @section_ids # Do migration - self.section_ids = self.ordered_master_file_ids if self.section_list.nil? + self.section_ids = self.ordered_master_file_ids.compact if self.section_list.nil? return [] if self.section_list.nil? @section_ids = JSON.parse(self.section_list) diff --git a/app/models/mods_behaviors.rb b/app/models/mods_behaviors.rb index 9d0d0d6edf..4ba32746ba 100644 --- a/app/models/mods_behaviors.rb +++ b/app/models/mods_behaviors.rb @@ -82,7 +82,7 @@ def to_solr(solr_doc = Hash.new, opts = {}) solr_doc['other_identifier_sim'] = gather_terms(self.find_by_terms(:other_identifier)) solr_doc['bibliographic_id_ssi'] = self.bibliographic_id.first solr_doc['bibliographic_id_source_ssi'] = self.bibliographic_id.source.first - solr_doc['statement_of_responsibility_ssi'] = gather_terms(self.find_by_terms(:statement_of_responsibility)) + solr_doc['statement_of_responsibility_ssi'] = gather_terms(self.find_by_terms(:statement_of_responsibility)).first solr_doc['record_identifier_ssim'] = gather_terms(self.find_by_terms(:record_identifier)) # Extract 4-digit year for creation date facet in Hydra and pub_date facet in Blacklight diff --git a/app/models/search_builder.rb b/app/models/search_builder.rb index 014ba8bf84..6b02d50579 100644 --- a/app/models/search_builder.rb +++ b/app/models/search_builder.rb @@ -51,6 +51,7 @@ def search_section_transcripts(solr_parameters) return unless solr_parameters[:q].present? && SupplementalFile.with_tag('transcript').any? && !(blacklight_params[:controller] == 'bookmarks') terms = solr_parameters[:q].split + return if terms.any? { |term| term.match?(/[\{\}]/) } term_subquery = terms.map { |term| "transcript_tsim:#{RSolr.solr_escape(term)}" }.join(" OR ") solr_parameters[:defType] = "lucene" solr_parameters[:q] = "({!edismax v=\"#{RSolr.solr_escape(solr_parameters[:q])}\"}) {!join to=id from=isPartOf_ssim}{!join to=id from=isPartOf_ssim}#{term_subquery}" diff --git a/config/application.rb b/config/application.rb index 1ecb941c0e..475af9a286 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,4 +1,5 @@ require_relative 'boot' +require_relative '../lib/tempfile_factory' require 'rails/all' require 'resolv-replace' @@ -56,6 +57,8 @@ class Application < Rails::Application end end + config.middleware.insert_before 0, TempfileFactory + config.active_storage.service = (Settings&.active_storage&.service.presence || "local").to_sym end end diff --git a/config/settings.yml b/config/settings.yml index ec358e8a0c..5b5b039022 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -123,3 +123,8 @@ derivative: allow_download: true # Maximum size for uploaded files in bytes (default is disabled) #max_upload_size: 2147483648 # Use :none or comment out to disable limit +# Rack Multipart creates temporary files when processing multipart form data with a large payload. +# If the default system /tmp directory is storage constrained, you can define an alternative here. +# Leave commented out to use the system default. +# tempfile: +# location: '/tmp' \ No newline at end of file diff --git a/lib/tasks/avalon_migrations.rake b/lib/tasks/avalon_migrations.rake index 3b5bd49cb6..ef68420c79 100644 --- a/lib/tasks/avalon_migrations.rake +++ b/lib/tasks/avalon_migrations.rake @@ -97,7 +97,7 @@ namespace :avalon do task media_object_section_list: :environment do error_ids = [] mo_count = MediaObject.count - ids_to_migrate = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", rows: mo_count).pluck("id") + ids_to_migrate = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", fl: [:id], rows: mo_count).pluck("id") puts "Migrating #{ids_to_migrate.size} out of #{mo_count} Media Objects." ids_to_migrate.each do |id| MediaObject.find(id).save!(validate: false) @@ -105,7 +105,7 @@ namespace :avalon do error_ids += [id] puts "Error migrating #{id}: #{error.message}" end - remaining_ids_count = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", rows: mo_count).size + remaining_ids_count = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", fl: [:id], rows: mo_count).size if error_ids.size > 0 puts "Migration finished running but #{error_ids.size} Media Objects failed to migrate. Try running the migration again." elsif remaining_ids_count > 0 diff --git a/lib/tempfile_factory.rb b/lib/tempfile_factory.rb new file mode 100644 index 0000000000..f1efde5caa --- /dev/null +++ b/lib/tempfile_factory.rb @@ -0,0 +1,30 @@ +# Middleware to override the default Rack::Multipart tempfile factory: +# https://www.rubydoc.info/gems/rack/Rack/Multipart/Parser#TEMPFILE_FACTORY-constant +class TempfileFactory + def initialize(app) + @app = app + return unless Settings.tempfile.present? + if Settings.tempfile&.location.present? && File.directory?(Settings.tempfile&.location) && File.writable?(Settings.tempfile&.location) + @tempfile_location = Settings.tempfile.location + else + logger.warn("[Rack::Multipart] [Tempfile] #{Settings.tempfile.location} is not a diretory or not writable. Falling back to #{Dir.tmpdir}.") + end + end + + def call(env) + if @tempfile_location + env["rack.multipart.tempfile_factory"] = lambda { |filename, content_type| + extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129] + Tempfile.new(["RackMultipart", extension], @tempfile_location) + } + end + + @app.call(env) + end + + private + + def logger + @logger ||= Logger.new(File.join(Rails.root, 'log', "#{Rails.env}.log")) + end +end diff --git a/package.json b/package.json index 2dd7ca271c..4f5aa1441a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/preset-react": "^7.0.0", "@babel/runtime": "7", - "@samvera/ramp": "https://github.com/samvera-labs/ramp.git#e7d98e06ceceebe7e8ba59af29d1350a3a43acee", + "@samvera/ramp": "^3.2.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "buffer": "^6.0.3", diff --git a/spec/controllers/catalog_controller_spec.rb b/spec/controllers/catalog_controller_spec.rb index 296d584b24..4ad1437206 100644 --- a/spec/controllers/catalog_controller_spec.rb +++ b/spec/controllers/catalog_controller_spec.rb @@ -308,6 +308,22 @@ expect(assigns(:response).documents.count).to eq 2 expect(assigns(:response).documents.map(&:id)).to match_array [private_media_object.id, public_media_object.id] end + context "when there are transcripts present" do + let!(:transcript) { FactoryBot.create(:supplemental_file, :with_transcript_file, :with_transcript_tag, parent_id: private_media_object.id) } + + before do + private_media_object.supplemental_files = [transcript] + private_media_object.save + end + + it "should not error when search includes curly brackets" do + request.headers['Avalon-Api-Key'] = 'secret_token' + get 'index', params: { q: '{8}', format: 'atom' } + expect(response).to be_successful + expect(response).to render_template('catalog/index') + expect(response.content_type).to eq "application/atom+xml; charset=utf-8" + end + end end context "without api key" do it "should not show results" do diff --git a/spec/lib/tempfile_factory_spec.rb b/spec/lib/tempfile_factory_spec.rb new file mode 100644 index 0000000000..2360592055 --- /dev/null +++ b/spec/lib/tempfile_factory_spec.rb @@ -0,0 +1,79 @@ +# Copyright 2011-2024, The Trustees of Indiana University and Northwestern +# University. Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# --- END LICENSE_HEADER BLOCK --- + +require 'rails_helper' + +describe TempfileFactory do + class MockApp + def call(env) + [200, {}, env] + end + end + + let(:file) { fixture_file_upload("videoshort.mp4", "video/mp4") } + let(:env) { Rack::MockRequest.env_for('/', method: :post, params: file) } + let(:app) { MockApp.new } + subject { TempfileFactory.new(app) } + + around do |example| + @old_config = Settings.dig(:tempfile, :location) + if tempfile_config.present? + Settings.tempfile ||= Config::Options.new + Settings.tempfile.location = tempfile_config + else + Settings.tempfile = nil + end + example.run + if @old_config.present? + Settings.tempfile ||= Config::Options.new + Settings.tempfile.location = @old_config + else + Settings.tempfile = nil + end + end + + context "when an alternate directory is defined" do + let(:tempfile_config) { Rails.root.join('spec', 'fixtures').to_s } + + it "sets `env['rack.multipart.tempfile_factory']`" do + status, headers, response = subject.call(env) + expect(response).to include 'rack.multipart.tempfile_factory' + expect(response['rack.multipart.tempfile_factory'].call("videoshort.mp4", "video/mp4").path).to start_with(tempfile_config) + end + end + + context "when an alternate directory is NOT defined" do + let(:tempfile_config) { nil } + + it "does not set `env['rack.multipart.tempfile_factory']`" do + status, headers, response = subject.call(env) + expect(response).to_not include 'rack.multipart.tempfile_factory' + end + end + + context "when there is a problem with the defined alternate directory" do + let(:tempfile_config) { 'does not exist' } + let(:logger) { double() } + + before do + allow_any_instance_of(TempfileFactory).to receive(:logger).and_return(logger) + end + + it "does not set `env['rack.multipart.tempfile_factory']`" do + expect(logger).to receive(:warn).with(match("Falling back")) + status, headers, response = subject.call(env) + expect(response).to_not include 'rack.multipart.tempfile_factory' + end + end +end diff --git a/yarn.lock b/yarn.lock index 92ed2d308c..c90b3fa9c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1477,9 +1477,10 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@samvera/ramp@https://github.com/samvera-labs/ramp.git#e7d98e06ceceebe7e8ba59af29d1350a3a43acee": - version "3.1.2" - resolved "https://github.com/samvera-labs/ramp.git#e7d98e06ceceebe7e8ba59af29d1350a3a43acee" +"@samvera/ramp@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@samvera/ramp/-/ramp-3.2.0.tgz#c281a6a2970e1bf6def1e2cabc5822ca22409261" + integrity sha512-lJUvPQETvYHWKW8GnO4XmP3D/JUZNNXELm9bDlOEv0PaQIs28FkSozbh/gQlNWh6DCBWUtV6qnG0ZlkNIH+qwQ== dependencies: "@rollup/plugin-json" "^6.0.1" "@silvermine/videojs-quality-selector" "^1.3.1" @@ -5397,9 +5398,9 @@ postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.3.11: - version "8.4.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" - integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== + version "8.4.40" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" + integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -6563,9 +6564,9 @@ underscore@1.13.1: integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== underscore@^1.13.1: - version "1.13.6" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" - integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + version "1.13.7" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.7.tgz#970e33963af9a7dda228f17ebe8399e5fbe63a10" + integrity sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g== unfetch@^4.2.0: version "4.2.0"