From 910224c931ec15284720bc678ba99bf97f3e5958 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Mon, 17 Feb 2025 16:44:59 +0200
Subject: [PATCH 01/29] Removed finna-video-element, adjusted styles alot,
created finna-video component
---
themes/finna2/js/finna-image-paginator.js | 5 -
themes/finna2/js/finna-video-element.js | 339 ------------------
themes/finna2/js/finna-video-player.js | 163 +++++++++
themes/finna2/js/finna.js | 3 +-
themes/finna2/scss/finna/video-player.scss | 83 +++--
themes/finna2/scss/global/variables.scss | 3 +
.../DefaultRecord/record-video-player.phtml | 35 +-
.../DefaultRecord/result-online-urls.phtml | 61 +---
.../_ui/components/finna-video.phtml | 36 ++
themes/finna2/templates/search/modals.phtml | 1 -
themes/finna2/theme.config.php | 2 +-
11 files changed, 274 insertions(+), 457 deletions(-)
delete mode 100644 themes/finna2/js/finna-video-element.js
create mode 100644 themes/finna2/js/finna-video-player.js
create mode 100644 themes/finna2/templates/_ui/components/finna-video.phtml
diff --git a/themes/finna2/js/finna-image-paginator.js b/themes/finna2/js/finna-image-paginator.js
index 8c6aad35860..1e09e12f2f7 100644
--- a/themes/finna2/js/finna-image-paginator.js
+++ b/themes/finna2/js/finna-image-paginator.js
@@ -726,9 +726,6 @@ FinnaPaginator.prototype.loadImageInformation = function loadImageInformation()
if (typeof $('.open-link a').attr('href') !== 'undefined') {
_.setDimensions();
}
- _.popup.collapseArea.find('finna-video').on('click', () => {
- _.setCanvasElement('video');
- });
if ($('.imagepopup-holder .feedback-record')[0] || $('.imagepopup-holder .save-record')[0]) {
$('.imagepopup-holder .feedback-record, .imagepopup-holder .save-record').on('click', function onClickActionLink(/*e*/) {
$.fn.finnaPopup.closeOpen();
@@ -883,10 +880,8 @@ FinnaPaginator.prototype.createPopupObject = function createPopupObject(popup) {
_.canvasElements = {
leaflet: popup.find('.leaflet-map-image'),
noZoom: popup.find('.popup-nonzoom'),
- video: popup.find('.popup-video')
};
_.canvasElements.leaflet.attr('id', 'leaflet-map-image');
- _.canvasElements.video.attr('id', 'video-player');
if (_.images.length < 2) {
_.popup.covers.parent().hide();
_.popup.leftBrowseBtn.hide();
diff --git a/themes/finna2/js/finna-video-element.js b/themes/finna2/js/finna-video-element.js
deleted file mode 100644
index e81d7139a07..00000000000
--- a/themes/finna2/js/finna-video-element.js
+++ /dev/null
@@ -1,339 +0,0 @@
-/* global VuFind, finna */
-
-class VideoElement extends HTMLElement {
-
- /**
- * Get the type of the video, iFrame | video
- * @returns {string} Video type
- */
- get type() {
- return (this.getAttribute('type') || '').toLowerCase();
- }
-
- /**
- * Set the type of the video, iFrame | video
- * @param {string} value iFrame | video
- */
- set type(value) {
- this.setAttribute('type', value);
- }
-
- /**
- * Get the parent element to which the video player is being embedded into.
- * @returns {string|undefined} Parent element id or undefined
- */
- get embedParent() {
- return this.getAttribute('embed-parent') || undefined;
- }
-
- /**
- * Set the parent element to which the video player is being embedded into.
- * Omit to display in a new popup.
- * @param {string|undefined} value Parent element id or undefined.
- */
- set embedParent(value) {
- this.setAttribute('embed-parent', value);
- }
-
- /**
- * Get the source of the video.
- * @returns {string} Source of the video
- */
- get source() {
- return this.getAttribute('source') || '';
- }
-
- /**
- * Set the source of the video.
- * @param {string} value The video source
- */
- set source(value) {
- this.setAttribute('source', value);
- }
-
- /**
- * Get the video sources as an object.
- * @returns {object} Object containing video sources
- */
- get videoSources() {
- return this.getAttribute('video-sources') ? JSON.parse(this.getAttribute('video-sources')) : {};
- }
-
- /**
- * Set the video sources as an object.
- * @param {object} value Video sources object
- */
- set videoSources(value) {
- this.setAttribute('video-sources', JSON.stringify(value || {}));
- }
-
- /**
- * Get the poster url to display in viewer.
- * @returns {string} Poster url
- */
- get posterUrl() {
- return this.getAttribute('poster-url') || '';
- }
-
- /**
- * Set the poster url to display in viewer.
- * @param {string} value Poster url
- */
- set posterUrl(value) {
- this.setAttribute('poster-url', value);
- }
-
- /**
- * Get the identity for the popup group.
- * @returns {string} Id of the popup group
- */
- get popupId() {
- return this.getAttribute('popup-id') || '';
- }
-
- /**
- * Set the identity for the popup group.
- * @param {string} value Id of the popup group
- */
- set popupId(value) {
- this.setAttribute('popup-id', value);
- }
-
- /**
- * Get consent service required for the video.
- * @returns {string} Consent service accepted to display this video
- */
- get consentService() {
- return this.getAttribute('consent-service') || '';
- }
-
- /**
- * Get consent service required for the video.
- * @param {string} value Consent service accepted to display this video
- */
- set consentService(value) {
- this.setAttribute('consent-service', value);
- }
-
- /**
- * Get consent categories required for the video.
- * @returns {string} Consent categories
- */
- get consentCategories() {
- return this.getAttribute('consent-categories') || '';
- }
-
- /**
- * Set consent categories required for the video.
- * @param {string} value Consent categories
- */
- set consentCategories(value) {
- this.setAttribute('consent-categories', value);
- }
-
- /**
- * Get index.
- * @returns {string} Index of this video element
- */
- get index() {
- return this.getAttribute('index') || '';
- }
-
- /**
- * Set index.
- * @param {number} value Value
- */
- set index(value) {
- this.setAttribute('index', value);
- }
-
- /**
- * Get if the video should be activated on load.
- * return true if the value is 'true' as a string.
- * @returns {boolean} Is active?
- */
- get active() {
- return this.getAttribute('active') === 'true';
- }
-
- /**
- * Set if the video should be activated on load.
- * @param {boolean} value Is active?
- */
- set active(value) {
- this.setAttribute('active', value);
- }
-
- /**
- * Constructor
- */
- constructor() {
- super();
- this.modals = {
- video: ``,
- iframe: `
`,
- audio: ``
- };
-
- this.translations = {
- close: VuFind.translate('close'),
- next: VuFind.translate('Next Record'),
- previous: VuFind.translate('Previous Record'),
- };
- this.scripts = {
- 'videojs': 'vendor/video.min.js',
- 'video-popup': 'finna-video-popup.js'
- };
- this.subScripts = {
- 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
- 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
- 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js',
- };
- }
-
- /**
- * Called after consent settings have been initialized.
- */
- onConsentInitialized() {
- // Check if this video is inside a record
- const record = this.closest('div.record');
- const self = this;
- let classes = 'video-popup';
- let modal = this.modals.video;
- switch (this.type) {
- case 'iframe':
- classes = 'finna-iframe';
- modal = this.modals.iframe;
- break;
- case 'audio':
- classes = 'finna-audio';
- modal = this.modals.audio;
- break;
- }
- const popupSettings = {
- id: this.popupId,
- modal: modal,
- cycle: typeof this.embedParent !== 'undefined',
- classes: classes,
- parent: this.embedParent,
- translations: this.translations,
- onPopupInit: (t) => {
- if (this.embedParent) {
- t.removeClass('active-video');
- }
- },
- onPopupOpen: function onPopupOpen() {
- if (!self.hasConsent) {
- return;
- }
- if (record) {
- const warnings
- = record.querySelector(`.video-warning[data-index="${self.index}"]`);
- if (this.parent) {
- record.querySelectorAll('.active-video').forEach(v => {
- v.classList.remove('active-video');
- });
- record.querySelectorAll('.video-warning').forEach(v => {
- if (v.dataset.index !== self.index) {
- v.classList.add('hidden');
- } else {
- v.classList.remove('hidden');
- VuFind.observerManager.observe(
- 'LazyImages',
- v.querySelectorAll('img[data-src]')
- );
- }
- });
- this.currentTrigger().addClass('active-video');
- } else {
- this.content.css('height', '100%');
- if (warnings) {
- const clone = warnings.cloneNode(true);
- clone.classList.remove('hidden');
- this.modalHolder.append(clone);
- VuFind.observerManager.observe(
- 'LazyImages',
- clone.querySelectorAll('img[data-src]')
- );
- setTimeout(function startFade() {
- $(clone).fadeOut(2000);
- }, 3000);
- }
- }
- }
- switch (self.type) {
- case 'video':
- finna.scriptLoader.loadInOrder(self.scripts, self.subScripts, () => {
- finna.videoPopup.initVideoJs('.video-popup', self.videoSources, self.posterUrl);
- });
- break;
- case 'iframe':
- // If using Chrome + VoiceOver, Chrome crashes if vimeo player video settings button has aria-haspopup=true
- document.querySelectorAll('.vp-prefs .js-prefs').forEach(e => {
- e.setAttribute('aria-haspopup', false);
- });
- this.content.find('iframe').attr('src', this.adjustEmbedLink(self.source));
- break;
- case 'audio':
- this.content.css('height', '100%');
- this.content.find('audio').attr('src', self.source);
- break;
- default:
- console.warn(`Unknown video type in video element: ${self.type}`);
- break;
- }
- }
- };
- this.hasConsent = this.type === 'iframe'
- ? VuFind.cookie.isServiceAllowed(this.consentService)
- : true;
- if (!this.hasConsent) {
- finna.scriptLoader.load(
- {'cookie-consent': 'finna-cookie-consent-element.js'},
- () => {
- const consentModal = document.createElement('finna-consent');
- consentModal.consentCategories = this.consentCategories;
- consentModal.serviceUrl = this.source;
-
- popupSettings.modal = consentModal;
- $(this).finnaPopup(popupSettings);
- if (this.active) {
- this.click();
- }
- }
- );
- } else {
- $(this).finnaPopup(popupSettings);
- if (this.active) {
- this.click();
- }
- }
- }
-
- /**
- * When the element is added to the dom
- */
- connectedCallback() {
- // Wait for the cookie consent to be initialized
- if (VuFind.cookie.getConsentConfig() === null) {
- VuFind.listen('cookie-consent-initialized', () => this.onConsentInitialized(), {once: true});
- } else {
- this.onConsentInitialized();
- }
- }
-
-
- /**
- * When the element is removed from the dom
- */
- disconnectedCallback() {
- $(this).trigger('removeclick.finna');
- }
-}
-
-customElements.define('finna-video', VideoElement);
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
new file mode 100644
index 00000000000..da4dd737f6e
--- /dev/null
+++ b/themes/finna2/js/finna-video-player.js
@@ -0,0 +1,163 @@
+/* global VuFind, finna */
+finna.videoPlayer = (() => {
+
+ /**
+ * These scripts must be loaded first for additional scripts requested to work properly
+ * @member {object} requiredVideoScripts
+ */
+ const requiredVideoScripts = {
+ 'videojs': 'vendor/video.min.js',
+ 'video-popup': 'finna-video-popup.js'
+ };
+
+ /**
+ * Additional scripts required to load when requesting video with videojs
+ * @member {object} additionalVideoScripts
+ */
+ const additionalVideoScripts = {
+ 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
+ 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
+ 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js',
+ };
+
+ /**
+ * Is the module already initialized?
+ * @member {boolean}
+ */
+ let initialized = false;
+
+ /**
+ * Adds a specific class name to VuFind modal and a listener to listen when
+ * the modal is closed to remove the class name.
+ * @param {string} className Classname to set for opened modal.
+ */
+ function overrideModalClass(className)
+ {
+ const container = document.getElementById('modal');
+ if (container) {
+ container.classList.add(className);
+ VuFind.listen('lightbox.closed', () => {
+ container.classList.remove(className);
+ }, {once: true});
+ }
+ }
+
+ /**
+ * Display warning icons when using inline video setting set to true.
+ * @param {HTMLElement} element Element clicked
+ */
+ function showWarningIcons(element)
+ {
+ // Open video warnings in inline videos
+ document.querySelectorAll('.warnings-wrapper .video-warning').forEach(warning => {
+ finna.getPromise('lazyImages').then(() => {
+ VuFind.observerManager.observe(
+ 'LazyImages',
+ warning.querySelectorAll('img[data-src]')
+ );
+ });
+ warning.classList.toggle('hidden', element.dataset.vcIndex !== warning.dataset.index);
+ });
+ }
+
+ /**
+ * When a video button which uses videojs has been requested.
+ * @param {HTMLElement} element Element clicked
+ */
+ function onVideoOpen(element)
+ {
+ const videoPlayer = document.createElement('video');
+ videoPlayer.className = 'video-js vjs-big-play-centered video-popup';
+ videoPlayer.controls = '';
+
+ // Is the video inline video or popup video
+ let container;
+ if (element.dataset.vcInline) {
+ container = document.getElementById('inline-video');
+ container.replaceChildren(videoPlayer);
+ showWarningIcons(element);
+ } else {
+ // Try to close any open finna popups so the video can be shown properly
+ $.fn.finnaPopup.closeOpen();
+ VuFind.lightbox.render(videoPlayer.outerHTML);
+ overrideModalClass('finna-video-modal');
+ container = document.getElementById('modal');
+ }
+ const videoSources = JSON.parse(element.dataset.videoSources);
+ finna.videoPopup.initVideoJs(container, videoSources, element.dataset.posterUrl);
+ }
+
+ /**
+ * When a video which uses iframe has been requested.
+ * @param {HTMLElement} element Element clicked
+ */
+ function onIFrameOpen(element)
+ {
+ const iFrame = document.createElement('iframe');
+ iFrame.className = 'player';
+ iFrame.frameborder = 0;
+ iFrame.allowFullscreen = 'true';
+ iFrame.src = element.dataset.vcUrl;
+ if (element.dataset.vcInline) {
+ const container = document.getElementById('inline-video');
+ container.replaceChildren(iFrame);
+ showWarningIcons(element);
+ } else {
+ // Try to close any open finna popups so the video can be shown properly
+ $.fn.finnaPopup.closeOpen();
+ VuFind.lightbox.render(iFrame.outerHTML);
+ overrideModalClass('finna-iframe-modal');
+ }
+ }
+
+ /**
+ * Provide a selector or HTMLButtonElement to initialize a button for videos.
+ * @param {HTMLButtonElement|string} elementOrSelector Element or selector
+ */
+ function initVideoButton(elementOrSelector)
+ {
+ const element = typeof elementOrSelector === 'string'
+ ? document.querySelector(elementOrSelector)
+ : elementOrSelector;
+ if (!element) {
+ return;
+ }
+ if (element.classList.contains('done')) {
+ return;
+ }
+ element.classList.add('done');
+ finna.getPromise('videoScripts').then(() => {
+ element.addEventListener('click', () => {
+ document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video'));
+ if (element.dataset.vcEmbed) {
+ onIFrameOpen(element);
+ } else {
+ onVideoOpen(element);
+ }
+ element.classList.add('active-video');
+ });
+ if (element.classList.contains('active-video')) {
+ element.click();
+ }
+ });
+ }
+
+ /**
+ * Initialize the module
+ */
+ function init()
+ {
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+ finna.scriptLoader.loadInOrder(requiredVideoScripts, additionalVideoScripts, () => {
+ finna.resolvePromise('videoScripts');
+ });
+ }
+
+ return {
+ initVideoButton,
+ init
+ };
+})();
\ No newline at end of file
diff --git a/themes/finna2/js/finna.js b/themes/finna2/js/finna.js
index 5ab6a4c2dba..3c4da79be19 100644
--- a/themes/finna2/js/finna.js
+++ b/themes/finna2/js/finna.js
@@ -13,7 +13,8 @@ var finna = (function finnaModule() {
* @member {object}
*/
let promises = {
- lazyImages: new Promise((resolve) => { resolves.lazyImages = resolve; })
+ lazyImages: new Promise((resolve) => { resolves.lazyImages = resolve; }),
+ videoScripts: new Promise((resolve) => { resolves.videoScripts = resolve; })
};
var my = {
diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss
index 7983cfe6353..461f7289269 100644
--- a/themes/finna2/scss/finna/video-player.scss
+++ b/themes/finna2/scss/finna/video-player.scss
@@ -28,28 +28,24 @@
height: 100%;
width: 100%;
}
-.inline-video, .video-popup, .finna-iframe {
- &.finna-popup {
- display: flex;
- align-content: center;
- justify-content: center;
- .popup-iframe-wrapper {
- // These values are from examples in https://icareus.fi/videosoittimet/
- position: relative;
- width: 100%;
- // This padding-top value prevents the player from overflowing
- padding-top: 56.25%;
- margin: auto;
- iframe {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
- }
- }
+
+// Template for using proper styles, when appending video viewer inside an element.
+%iframe-centering-template {
+ height: $inline-video-player-desktop-height;
+ @media (max-width: $screen-sm-max) {
+ height: $inline-video-player-mobile-height;
+ }
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ > iframe, > .video-js {
+ // This aspect ratio is result of division from 560/315, which is default aspect-ratio notified in icareus website
+ // It works miracles and using position absolute with padding is nightmare.
+ aspect-ratio: 1.7778/1;
+ height: 100%;
+ }
}
+
.inline-video-container {
width: 100vw;
position: relative;
@@ -58,13 +54,7 @@
background-color: black;
margin-bottom: 10px;
.inline-video {
- .embed-responsive-item {
- height: 100%;
- width: 100%;
- }
- .finna-iframe.modal-holder {
- max-width: 800px;
- }
+ @extend %iframe-centering-template;
}
.video-accordion {
padding-top: 15px;
@@ -126,14 +116,6 @@
}
}
-finna-video {
- display: inline-block;
- cursor: pointer;
- &.btn.btn-link {
- padding: 0;
- }
-}
-
.vjs-airplay-button {
.vjs-icon-placeholder {
background: $icon-airplay--default center center no-repeat;
@@ -150,3 +132,32 @@ finna-video {
}
}
}
+
+// Search results specific buttons without any padding to make things look smoother
+.btn.btn-link.video-link-container {
+ padding: 0;
+}
+
+.finna-video-modal,
+.finna-iframe-modal {
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+ .modal-dialog {
+ flex: 1 1 auto;
+ .modal-content {
+ width: 100%;
+ height: 100%;
+ background-color: transparent;
+ box-shadow: none;
+ border: none;
+ > button.close {
+ right: -20px;
+ top: -20px;
+ }
+ .modal-body {
+ @extend %iframe-centering-template;
+ }
+ }
+ }
+}
diff --git a/themes/finna2/scss/global/variables.scss b/themes/finna2/scss/global/variables.scss
index 365cf3da4a1..848ad214a46 100644
--- a/themes/finna2/scss/global/variables.scss
+++ b/themes/finna2/scss/global/variables.scss
@@ -350,3 +350,6 @@ $item-status-color-available: $success !default;
$item-status-color-unavailable: $danger !default;
$item-status-color-unknown: $warning !default;
$item-status-color-uncertain: $warning !default;
+
+$inline-video-player-desktop-height: 400px;
+$inline-video-player-mobile-height: 200px;
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
index 6f6b0766f0e..f40877b7c7b 100644
--- a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
+++ b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
@@ -57,31 +57,18 @@
$desc = $embeddedVideo === 'data-embed-iframe' ? $this->translate('format_Video') : $desc;
break;
}
-
- $popupId = ($embeddedVideo ?? '') === 'data-embed-iframe' ? 'finna-iframe' : 'finna-video';
- $type = ($embeddedVideo ?? '') === 'data-embed-iframe' ? 'iFrame' : 'video';
?>
-
- embed-parent="inline-video"
- active ="= $i === 0 ? 'true' : 'false'?>"
-
-
- video-sources="=htmlspecialchars(json_encode($url['videoSources']), ENT_QUOTES, 'UTF-8');?>"
-
- >
- =$this->icon('video-play', 'video-play-icon') ?>
- =$this->escapeHtml($this->truncate(ucfirst($desc), 30))?>
-
+ =
+ $this->component('finna-video', [
+ 'url' => $url,
+ 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']),
+ 'index' => $i,
+ 'videoSources' => $url['videoSources'] ?? [],
+ 'description' => $desc,
+ 'inline' => $inlineVideo,
+ 'active' => $inlineVideo && $i === 0,
+ ]);
+ ?>
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
index fec5f5eeb91..172f8a55008 100644
--- a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
+++ b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
@@ -50,13 +50,9 @@
?>
$url): ?>
$maxLinkCount)) {
continue;
}
-
- if ('popup' === $context && ++$linkCount > $maxLinkCount) {
- break;
- }
?>
=$i > 0 ? '
' : ''?>
$this->proxyUrl($url['url']),
'title' => $url['url'],
];
- switch ($url['embed'] ?? '') {
- case 'video':
- $currentUrl['data-embed-video'] = '';
- break;
- case 'iframe':
- $currentUrl['data-embed-iframe'] = '';
- break;
- default:
- if ($this->recordLinker()->getEmbeddedVideo($url['url']) == 'data-embed-iframe') {
- $url['embed'] = 'iframe';
- $currentUrl['data-embed-iframe'] = '';
- }
- break;
- }
- $desc = $url['desc'] ?? $url['url'];
- if ($desc === $url['url']) {
- $desc = $this->truncateUrl($desc);
- }
+ $desc = !empty($url['desc']) ? $url['desc'] : $this->truncateUrl($url['url']);
?>
- component('finna-video', [
+ 'url' => $url,
+ 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']),
+ 'index' => $i,
+ 'videoSources' => $url['videoSources'] ?? [],
+ 'description' => $desc,
+ 'class' => 'btn btn-link video-link-container',
+ ]);
?>
-
htmlElement()->getAttributes($currentUrl, 'url-base') ?>>=$this->icon('external-link') ?>
=$this->transEsc('default::link_' . $desc, null, $desc)?>
diff --git a/themes/finna2/templates/_ui/components/finna-video.phtml b/themes/finna2/templates/_ui/components/finna-video.phtml
new file mode 100644
index 00000000000..6463ae59758
--- /dev/null
+++ b/themes/finna2/templates/_ui/components/finna-video.phtml
@@ -0,0 +1,36 @@
+url['url'] ?? $this->url;
+ $embeddedVideo = $this->embed === 'data-embed-iframe' ?: false;
+ $videoIndex = $this->index;
+ $active = $this->active ?? false;
+ $description = $this->description ?? $this->translate('format_Video');
+ $videoSources = $this->videoSources ?? [];
+ $buttonAttributes = [
+ 'class' => $this->class ?? 'video-link-container',
+ 'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description,
+ 'title' => $url,
+ 'data-vc-url' => $url,
+ 'data-vc-invocation' => $this->_invocation,
+ 'data-vc-index' => $this->index ?? -1,
+ 'data-vc-inline' => $this->inline ?? false,
+ 'data-vc-embed' => $embeddedVideo,
+ 'aria-haspopup' => true,
+ ];
+ $buttonAttributes['class'] .= ' ' . $this->_componentClass;
+ if ($active) {
+ $buttonAttributes['class'] .= ' active-video';
+ }
+ // Generate unique selector for the button
+ $jsSelector = '.' . str_replace(' ', '.', $buttonAttributes['class']) . "[data-vc-invocation=\"$this->_invocation\"]";
+?>
+htmlAttributes($buttonAttributes);?> data-video-sources="=htmlspecialchars(json_encode($videoSources), ENT_QUOTES, 'UTF-8');?>">
+ =$this->icon('video-play', 'video-play-icon');?>
+ =$this->escapeHtml($this->truncate(ucfirst($description), 30))?>
+
+
+=$this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?>
diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml
index 07d5f8ace8b..f66a3474bad 100644
--- a/themes/finna2/templates/search/modals.phtml
+++ b/themes/finna2/templates/search/modals.phtml
@@ -30,7 +30,6 @@
-
=$this->icon('image-next') ?>= $this->transEsc('next_image'); ?>
+
+
+
+
+ =$this->transEsc('embedded_content_heading')?>
+
+
+ =$this->translate('embedded_content_description')?>
+
+
+
+
From 2ea67a0111733e7a4beff8da430540084f7a0031 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Tue, 18 Feb 2025 08:51:25 +0200
Subject: [PATCH 03/29] Small tuning
---
themes/finna2/js/finna-video-player.js | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 4980f609864..79b7b0637eb 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -130,13 +130,16 @@ finna.videoPlayer = (() => {
const description = wrapper.querySelector('.embedded-content-description');
if (description) {
const serviceBase = new URL(element.dataset.vcUrl);
- description.innerText = description.innerText.replace('%%consentCategories%%', element.dataset.vcConsent).replace('%%serviceBaseUrl%%', serviceBase.hostname);
+ description.innerText = description.innerText
+ .replace('%%consentCategories%%', element.dataset.vcConsent)
+ .replace('%%serviceBaseUrl%%', serviceBase.hostname);
}
VuFind.lightbox.render(wrapper.outerHTML);
overrideModalClass('finna-consent-modal');
const ccPreferences = document.getElementById('modal').querySelector('.embedded-content-actions button');
if (ccPreferences) {
- // Set cookie consent preferences event after the modal has been initialized so it properly initialized
+ // Set cookie consent preferences event after the modal has been initialized as the lightbox handles elements
+ // as a string, so it loses all the events applied before rendering
ccPreferences.addEventListener('click', () => {
VuFind.modal('hide');
CookieConsent.showPreferences();
@@ -182,7 +185,6 @@ finna.videoPlayer = (() => {
element.addEventListener('click', () => { displayConsentWindow(element); });
}
});
-
}
/**
@@ -203,4 +205,4 @@ finna.videoPlayer = (() => {
initVideoButton,
init
};
-})();
\ No newline at end of file
+})();
From 41e3f7d868e3a1c585722701b60c37695b1b2e4a Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Tue, 18 Feb 2025 09:09:15 +0200
Subject: [PATCH 04/29] Added finna prefix to the modal template
---
themes/finna2/js/finna-video-player.js | 4 +++-
themes/finna2/templates/search/modals.phtml | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 79b7b0637eb..3e20e7a09ff 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -116,8 +116,10 @@ finna.videoPlayer = (() => {
*/
function displayConsentWindow(element)
{
- const consentModal = document.getElementById('consent-modal');
+ const consentModal = document.getElementById('finna-consent-modal');
if (consentModal) {
+ // Append the cloned element, as templates return a DocumentFragment instead of node, which does not work
+ // if outerHTML is called.
const cloned = consentModal.content.cloneNode(true);
const wrapper = document.createElement('div');
wrapper.className = 'embedded-content-placeholder';
diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml
index 6241474ecb3..5e5e22f0078 100644
--- a/themes/finna2/templates/search/modals.phtml
+++ b/themes/finna2/templates/search/modals.phtml
@@ -50,7 +50,7 @@
-
+
=$this->transEsc('embedded_content_heading')?>
From 7f77562049d350c3f02995ca394167eb4c03b337 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Tue, 18 Feb 2025 09:18:49 +0200
Subject: [PATCH 05/29] Rename to -template so it is more obvious
---
themes/finna2/js/finna-video-player.js | 2 +-
themes/finna2/templates/search/modals.phtml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 3e20e7a09ff..4d146985ff4 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -116,7 +116,7 @@ finna.videoPlayer = (() => {
*/
function displayConsentWindow(element)
{
- const consentModal = document.getElementById('finna-consent-modal');
+ const consentModal = document.getElementById('finna-consent-modal-template');
if (consentModal) {
// Append the cloned element, as templates return a DocumentFragment instead of node, which does not work
// if outerHTML is called.
diff --git a/themes/finna2/templates/search/modals.phtml b/themes/finna2/templates/search/modals.phtml
index 5e5e22f0078..bd801c1dfbf 100644
--- a/themes/finna2/templates/search/modals.phtml
+++ b/themes/finna2/templates/search/modals.phtml
@@ -50,7 +50,7 @@
-
+
=$this->transEsc('embedded_content_heading')?>
From 52e26920a75443b58c14f95ae69d876de363f5bf Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Thu, 20 Feb 2025 16:46:39 +0200
Subject: [PATCH 06/29] Added legacy helper which helps loading js and css
files, if template has been marked as legacy and found in custom theme
---
local/config/finna/Legacy.ini.sample | 13 +
local/config/vufind/Legacy.ini.sample | 4 +
.../src/Finna/View/Helper/Root/Legacy.php | 138 +++++++
.../Finna/View/Helper/Root/LegacyFactory.php | 73 ++++
.../src/Finna/View/Helper/Root/Record.php | 16 +
themes/finna2/css/Legacy/FINNA-3012.css | 60 ++++
.../js/Legacy/finna-cookie-consent-element.js | 116 ++++++
.../finna2/js/Legacy/finna-video-element.js | 339 ++++++++++++++++++
themes/finna2/theme.config.php | 2 +
9 files changed, 761 insertions(+)
create mode 100644 local/config/finna/Legacy.ini.sample
create mode 100644 local/config/vufind/Legacy.ini.sample
create mode 100644 module/Finna/src/Finna/View/Helper/Root/Legacy.php
create mode 100644 module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php
create mode 100644 themes/finna2/css/Legacy/FINNA-3012.css
create mode 100644 themes/finna2/js/Legacy/finna-cookie-consent-element.js
create mode 100644 themes/finna2/js/Legacy/finna-video-element.js
diff --git a/local/config/finna/Legacy.ini.sample b/local/config/finna/Legacy.ini.sample
new file mode 100644
index 00000000000..81f2dcbbc4e
--- /dev/null
+++ b/local/config/finna/Legacy.ini.sample
@@ -0,0 +1,13 @@
+; Ini file to handle deprecated content.
+
+; Templates contains information about which templates loads additional styles when loaded.
+; Key is the template as relative path and value is the section which used to append style or js files
+[Templates]
+template['RecordDriver/DefaultRecord/example-template-name.phtml'] = 'FINNA-xxxx'
+template['RecordDriver/DefaultRecord/another-template-name.phtml'] = 'FINNA-xxxx'
+
+; Section name containing legacy data. After local adjustments has been made, this can be emptied locally to prevent
+; additional css and/or js files from being loaded
+[FINNA-xxxx]
+styles[] = 'Legacy/file-containing-legacy-styles.css'
+scripts[] = 'Legacy/file-containing-legacy-js-code.js'
diff --git a/local/config/vufind/Legacy.ini.sample b/local/config/vufind/Legacy.ini.sample
new file mode 100644
index 00000000000..6b989077102
--- /dev/null
+++ b/local/config/vufind/Legacy.ini.sample
@@ -0,0 +1,4 @@
+; Ini file to handle deprecated content.
+
+[Parent_Config]
+relative_path = "../finna/Legacy.ini"
diff --git a/module/Finna/src/Finna/View/Helper/Root/Legacy.php b/module/Finna/src/Finna/View/Helper/Root/Legacy.php
new file mode 100644
index 00000000000..8a3781db11d
--- /dev/null
+++ b/module/Finna/src/Finna/View/Helper/Root/Legacy.php
@@ -0,0 +1,138 @@
+
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+
+namespace Finna\View\Helper\Root;
+
+use Laminas\Config\Config;
+use Laminas\View\Helper\AbstractHelper;
+use VuFind\View\Helper\Root\ClassBasedTemplateRendererTrait;
+use VuFindTheme\ThemeInfo;
+
+/**
+ * Backwards compatibility helper
+ *
+ * @category VuFind
+ * @package View_Helpers
+ * @author Juha Luoma
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link http://vufind.org/wiki/vufind2:developer_manual Wiki
+ */
+class Legacy extends AbstractHelper
+{
+ use ClassBasedTemplateRendererTrait;
+
+ /**
+ * Constructor
+ *
+ * @param Config $legacyConfig Legacy configuration
+ * @param ThemeInfo $themeInfo Theme info helper
+ * @param array $markedTemplates Templates which has been marked as legacy. If the Site has a custom template
+ * present, this will load template with .legacy addition to load data which prevents
+ * breaking styles.
+ * @return void
+ */
+ public function __construct(
+ protected Config $legacyConfig,
+ protected ThemeInfo $themeInfo,
+ protected array $markedTemplates = []
+ ) {
+ }
+
+ /**
+ * Check if the template is marked as legacy. This will cause additional files to be loaded if the site
+ * has that template overwritten in custom folder.
+ *
+ * @param string $name Name of the template
+ * @param string $name Class name of the driver
+ *
+ * @return string
+ */
+ public function __invoke(string $name, string $className): string
+ {
+ if (!$this->markedTemplates) {
+ return $name;
+ }
+ if ($section = $this->markedTemplates[$name] ?? null && $this->hasCustomTemplate($name)) {
+ // If the template is found with single name without resolving, then try to find the file
+ $this->appendLegacyDependencies($section);
+ return $name;
+ }
+
+ $template = 'RecordDriver/%s/' . $name;
+ $resolved = $this->resolveClassTemplate(
+ $template,
+ $className,
+ $this->getView()->resolver()
+ );
+ if ($section = $this->markedTemplates[$resolved] ?? null && $this->hasCustomTemplate($name)) {
+ // If the template is found with single name without resolving, then try to find the file
+ $this->appendLegacyDependencies($section);
+ }
+ return $name;
+ }
+
+ /**
+ * Add legacy dependencies required for styles and scripts to work properly.
+ *
+ * @param string $sectionName Section name which contains js and/or css paths.
+ *
+ * @return void
+ */
+ protected function appendLegacyDependencies(string $sectionName): void
+ {
+ $sectionConfig = $this->legacyConfig->$sectionName ?? [];
+ if (!$sectionConfig) {
+ return;
+ }
+ $headScript = $this->getView()->plugin('headScript');
+ foreach ($sectionConfig->scripts ?? [] as $script) {
+ $headScript->appendFile($script);
+ }
+ $headLink = $this->getView()->plugin('headLink');
+ foreach ($sectionConfig->styles ?? [] as $style) {
+ $headLink->appendStylesheet($style);
+ }
+ }
+
+ /**
+ * Check if the current site has template overwritten in custom theme.
+ *
+ * @param string $templatePath Template path to check
+ *
+ * @return bool
+ */
+ protected function hasCustomTemplate(string $templatePath): bool
+ {
+ if ($result = $this->themeInfo->findInThemes('templates/' . $templatePath)) {
+ $theme = array_shift($result);
+ return $theme['theme'] === 'custom';
+ }
+ return false;
+ }
+}
diff --git a/module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php b/module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php
new file mode 100644
index 00000000000..8e63140cbf6
--- /dev/null
+++ b/module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php
@@ -0,0 +1,73 @@
+
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link https://vufind.org/wiki/development Wiki
+ */
+
+namespace Finna\View\Helper\Root;
+
+use Laminas\ServiceManager\Factory\FactoryInterface;
+use Psr\Container\ContainerInterface;
+
+/**
+ * Legacy helper factory
+ *
+ * @category VuFind
+ * @package View_Helpers
+ * @author Juha Luoma
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link https://vufind.org/wiki/development Wiki
+ */
+class LegacyFactory implements FactoryInterface
+{
+ /**
+ * Create an object
+ *
+ * @param ContainerInterface $container Service Manager
+ * @param string $requestedName Service being created
+ * @param array|null $options Extra options (optional)
+ *
+ * @return object
+ *
+ * @throws \Exception
+ */
+ public function __invoke(
+ ContainerInterface $container,
+ $requestedName,
+ array $options = null
+ ) {
+ if (!empty($options)) {
+ throw new \Exception('Unexpected options sent to factory.');
+ }
+ $legacyConfig = $container->get(\VuFind\Config\PluginManager::class)->get('Legacy');
+ $markedTemplates = $legacyConfig?->Templates?->template?->toArray() ?? [];
+ return new $requestedName(
+ $legacyConfig,
+ $container->get(\VuFindTheme\ThemeInfo::class),
+ $markedTemplates
+ );
+ }
+}
diff --git a/module/Finna/src/Finna/View/Helper/Root/Record.php b/module/Finna/src/Finna/View/Helper/Root/Record.php
index ab54fc5a661..8b726d024cb 100644
--- a/module/Finna/src/Finna/View/Helper/Root/Record.php
+++ b/module/Finna/src/Finna/View/Helper/Root/Record.php
@@ -1289,6 +1289,22 @@ public function getExternalRatingLink()
}
}
+ /**
+ *
+ */
+ public function renderTemplate($name, $context = null, $throw = true, $checkLegacy = true)
+ {
+ if ($checkLegacy) {
+ // Check if the requested template is marked as a legacy template.
+ // If so, download required template from the legacy/ path
+ // This is usually required if the Site has template overwritten inside a custom theme
+ $className = get_class($this->driver);
+ $legacyHelper = $this->getView()->plugin('legacy');
+ $name = ($legacyHelper)($name, $className);
+ }
+ return parent::renderTemplate($name, $context, $throw);
+ }
+
/**
* Returns a translated copyright text.
*
diff --git a/themes/finna2/css/Legacy/FINNA-3012.css b/themes/finna2/css/Legacy/FINNA-3012.css
new file mode 100644
index 00000000000..fc966cef88b
--- /dev/null
+++ b/themes/finna2/css/Legacy/FINNA-3012.css
@@ -0,0 +1,60 @@
+.inline-video.finna-popup, .video-popup.finna-popup, .finna-iframe.finna-popup {
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ .popup-iframe-wrapper {
+ position: relative;
+ width: 100%;
+ padding-top: 56.25%;
+ margin: auto;
+ iframe {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ }
+}
+
+finna-video {
+ display: inline-block;
+ cursor: pointer;
+}
+finna-video .btn.btn-link {
+ padding: 0;
+}
+
+.inline-video-container {
+ .inline-video {
+ all: initial;
+ .embed-responsive-item {
+ height: 100%;
+ width: 100%;
+ }
+ .finna-iframe.model-holder {
+ max-width: 800px;
+ }
+ }
+}
+
+finna-consent {
+ height: 100%;
+ background: black;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ .embedded-content-heading {
+ font-size: 18.4px;
+ }
+ .embedded-content-cookie-info > div {
+ padding: 1rem 2rem;
+ }
+ .embedded-content-actions {
+ display: flex;
+ gap: 2rem;
+ flex-wrap: wrap;
+ }
+}
+
diff --git a/themes/finna2/js/Legacy/finna-cookie-consent-element.js b/themes/finna2/js/Legacy/finna-cookie-consent-element.js
new file mode 100644
index 00000000000..2e4416cf5b3
--- /dev/null
+++ b/themes/finna2/js/Legacy/finna-cookie-consent-element.js
@@ -0,0 +1,116 @@
+/* global VuFind, CookieConsent */
+class FinnaCookieConsentElement extends HTMLElement {
+
+ /**
+ * Get consent categories
+ * @returns {string} Consent categories
+ */
+ get consentCategories() {
+ return this.getAttribute('consent-categories') || '';
+ }
+
+ /**
+ * Set consent categories
+ * @param {string} newValue Value to set
+ */
+ set consentCategories(newValue) {
+ this.setAttribute('consent-categories', newValue);
+ }
+
+ /**
+ * Get service base URL
+ * @returns {string} Service base URL
+ */
+ get serviceBaseUrl() {
+ const url = this.getAttribute('service-url');
+ if (url) {
+ try {
+ return new URL(url).host;
+ } catch (_) {
+ return url;
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Get service URL
+ * @returns {string} Service URL
+ */
+ get serviceUrl() {
+ return this.getAttribute('service-url') || '';
+ }
+
+ /**
+ * Set service URL
+ * @param {string} newValue Value to set
+ */
+ set serviceUrl(newValue) {
+ this.setAttribute('service-url', newValue);
+ }
+
+ /**
+ * Constructor
+ */
+ constructor() {
+ super();
+ }
+
+ /**
+ * When the element is added to the dom
+ */
+ connectedCallback() {
+ // Create the element
+ const divInfo = document.createElement('div');
+ divInfo.classList.add('embedded-content-cookie-info');
+
+ const divHeading = document.createElement('div');
+ divHeading.classList.add('embedded-content-heading');
+ divHeading.append(VuFind.translate('embedded_content_heading'));
+ divInfo.append(divHeading);
+
+ const divDescription = document.createElement('div');
+ divDescription.classList.add('embedded-content-description');
+ const replacements = {
+ '%%serviceBaseUrl%%': this.serviceBaseUrl,
+ '%%consentCategories%%': this.consentCategories
+ };
+ divDescription.append(VuFind.translate('embedded_content_description', replacements));
+ divInfo.append(divDescription);
+
+ const divActions = document.createElement('div');
+ divActions.classList.add('embedded-content-actions');
+
+ const aOuterLink = document.createElement('a');
+ aOuterLink.classList.add('btn', 'btn-primary');
+ aOuterLink.href = this.serviceUrl || '';
+ aOuterLink.target = '_blank';
+ aOuterLink.append(VuFind.translate('embedded_content_external_link'));
+
+ const linkIcon = document.createElement('i');
+ linkIcon.classList.add('fa', 'fa-new-window');
+ linkIcon.setAttribute('aria-hidden', true);
+ aOuterLink.append(' ', linkIcon);
+
+ const linkSpan = document.createElement('span');
+ linkSpan.classList.add('sr-only');
+ linkSpan.append(VuFind.translate('Open in a new window'));
+ aOuterLink.append(linkSpan);
+ divActions.append(aOuterLink);
+
+ const aShowModal = document.createElement('a');
+ aShowModal.classList.add('btn', 'btn-default');
+ aShowModal.href = '#';
+ aShowModal.setAttribute('aria-haspopup', 'dialog');
+ aShowModal.append(VuFind.translate('Cookie Settings'));
+ aShowModal.addEventListener('click', () => {
+ $.fn.finnaPopup.closeOpen();
+ CookieConsent.showPreferences();
+ });
+ divActions.append(aShowModal);
+ divInfo.append(divActions);
+ this.append(divInfo);
+ }
+}
+
+customElements.define('finna-consent', FinnaCookieConsentElement);
diff --git a/themes/finna2/js/Legacy/finna-video-element.js b/themes/finna2/js/Legacy/finna-video-element.js
new file mode 100644
index 00000000000..717d48b46ee
--- /dev/null
+++ b/themes/finna2/js/Legacy/finna-video-element.js
@@ -0,0 +1,339 @@
+/* global VuFind, finna */
+
+class VideoElement extends HTMLElement {
+
+ /**
+ * Get the type of the video, iFrame | video
+ * @returns {string} Video type
+ */
+ get type() {
+ return (this.getAttribute('type') || '').toLowerCase();
+ }
+
+ /**
+ * Set the type of the video, iFrame | video
+ * @param {string} value iFrame | video
+ */
+ set type(value) {
+ this.setAttribute('type', value);
+ }
+
+ /**
+ * Get the parent element to which the video player is being embedded into.
+ * @returns {string|undefined} Parent element id or undefined
+ */
+ get embedParent() {
+ return this.getAttribute('embed-parent') || undefined;
+ }
+
+ /**
+ * Set the parent element to which the video player is being embedded into.
+ * Omit to display in a new popup.
+ * @param {string|undefined} value Parent element id or undefined.
+ */
+ set embedParent(value) {
+ this.setAttribute('embed-parent', value);
+ }
+
+ /**
+ * Get the source of the video.
+ * @returns {string} Source of the video
+ */
+ get source() {
+ return this.getAttribute('source') || '';
+ }
+
+ /**
+ * Set the source of the video.
+ * @param {string} value The video source
+ */
+ set source(value) {
+ this.setAttribute('source', value);
+ }
+
+ /**
+ * Get the video sources as an object.
+ * @returns {object} Object containing video sources
+ */
+ get videoSources() {
+ return this.getAttribute('video-sources') ? JSON.parse(this.getAttribute('video-sources')) : {};
+ }
+
+ /**
+ * Set the video sources as an object.
+ * @param {object} value Video sources object
+ */
+ set videoSources(value) {
+ this.setAttribute('video-sources', JSON.stringify(value || {}));
+ }
+
+ /**
+ * Get the poster url to display in viewer.
+ * @returns {string} Poster url
+ */
+ get posterUrl() {
+ return this.getAttribute('poster-url') || '';
+ }
+
+ /**
+ * Set the poster url to display in viewer.
+ * @param {string} value Poster url
+ */
+ set posterUrl(value) {
+ this.setAttribute('poster-url', value);
+ }
+
+ /**
+ * Get the identity for the popup group.
+ * @returns {string} Id of the popup group
+ */
+ get popupId() {
+ return this.getAttribute('popup-id') || '';
+ }
+
+ /**
+ * Set the identity for the popup group.
+ * @param {string} value Id of the popup group
+ */
+ set popupId(value) {
+ this.setAttribute('popup-id', value);
+ }
+
+ /**
+ * Get consent service required for the video.
+ * @returns {string} Consent service accepted to display this video
+ */
+ get consentService() {
+ return this.getAttribute('consent-service') || '';
+ }
+
+ /**
+ * Get consent service required for the video.
+ * @param {string} value Consent service accepted to display this video
+ */
+ set consentService(value) {
+ this.setAttribute('consent-service', value);
+ }
+
+ /**
+ * Get consent categories required for the video.
+ * @returns {string} Consent categories
+ */
+ get consentCategories() {
+ return this.getAttribute('consent-categories') || '';
+ }
+
+ /**
+ * Set consent categories required for the video.
+ * @param {string} value Consent categories
+ */
+ set consentCategories(value) {
+ this.setAttribute('consent-categories', value);
+ }
+
+ /**
+ * Get index.
+ * @returns {string} Index of this video element
+ */
+ get index() {
+ return this.getAttribute('index') || '';
+ }
+
+ /**
+ * Set index.
+ * @param {number} value Value
+ */
+ set index(value) {
+ this.setAttribute('index', value);
+ }
+
+ /**
+ * Get if the video should be activated on load.
+ * return true if the value is 'true' as a string.
+ * @returns {boolean} Is active?
+ */
+ get active() {
+ return this.getAttribute('active') === 'true';
+ }
+
+ /**
+ * Set if the video should be activated on load.
+ * @param {boolean} value Is active?
+ */
+ set active(value) {
+ this.setAttribute('active', value);
+ }
+
+ /**
+ * Constructor
+ */
+ constructor() {
+ super();
+ this.modals = {
+ video: ``,
+ iframe: ``,
+ audio: ``
+ };
+
+ this.translations = {
+ close: VuFind.translate('close'),
+ next: VuFind.translate('Next Record'),
+ previous: VuFind.translate('Previous Record'),
+ };
+ this.scripts = {
+ 'videojs': 'vendor/video.min.js',
+ 'video-popup': 'finna-video-popup.js'
+ };
+ this.subScripts = {
+ 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
+ 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
+ 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js',
+ };
+ }
+
+ /**
+ * Called after consent settings have been initialized.
+ */
+ onConsentInitialized() {
+ // Check if this video is inside a record
+ const record = this.closest('div.record');
+ const self = this;
+ let classes = 'video-popup';
+ let modal = this.modals.video;
+ switch (this.type) {
+ case 'iframe':
+ classes = 'finna-iframe';
+ modal = this.modals.iframe;
+ break;
+ case 'audio':
+ classes = 'finna-audio';
+ modal = this.modals.audio;
+ break;
+ }
+ const popupSettings = {
+ id: this.popupId,
+ modal: modal,
+ cycle: typeof this.embedParent !== 'undefined',
+ classes: classes,
+ parent: this.embedParent,
+ translations: this.translations,
+ onPopupInit: (t) => {
+ if (this.embedParent) {
+ t.removeClass('active-video');
+ }
+ },
+ onPopupOpen: function onPopupOpen() {
+ if (!self.hasConsent) {
+ return;
+ }
+ if (record) {
+ const warnings
+ = record.querySelector(`.video-warning[data-index="${self.index}"]`);
+ if (this.parent) {
+ record.querySelectorAll('.active-video').forEach(v => {
+ v.classList.remove('active-video');
+ });
+ record.querySelectorAll('.video-warning').forEach(v => {
+ if (v.dataset.index !== self.index) {
+ v.classList.add('hidden');
+ } else {
+ v.classList.remove('hidden');
+ VuFind.observerManager.observe(
+ 'LazyImages',
+ v.querySelectorAll('img[data-src]')
+ );
+ }
+ });
+ this.currentTrigger().addClass('active-video');
+ } else {
+ this.content.css('height', '100%');
+ if (warnings) {
+ const clone = warnings.cloneNode(true);
+ clone.classList.remove('hidden');
+ this.modalHolder.append(clone);
+ VuFind.observerManager.observe(
+ 'LazyImages',
+ clone.querySelectorAll('img[data-src]')
+ );
+ setTimeout(function startFade() {
+ $(clone).fadeOut(2000);
+ }, 3000);
+ }
+ }
+ }
+ switch (self.type) {
+ case 'video':
+ finna.scriptLoader.loadInOrder(self.scripts, self.subScripts, () => {
+ finna.videoPopup.initVideoJs('.video-popup', self.videoSources, self.posterUrl);
+ });
+ break;
+ case 'iframe':
+ // If using Chrome + VoiceOver, Chrome crashes if vimeo player video settings button has aria-haspopup=true
+ document.querySelectorAll('.vp-prefs .js-prefs').forEach(e => {
+ e.setAttribute('aria-haspopup', false);
+ });
+ this.content.find('iframe').attr('src', this.adjustEmbedLink(self.source));
+ break;
+ case 'audio':
+ this.content.css('height', '100%');
+ this.content.find('audio').attr('src', self.source);
+ break;
+ default:
+ console.warn(`Unknown video type in video element: ${self.type}`);
+ break;
+ }
+ }
+ };
+ this.hasConsent = this.type === 'iframe'
+ ? VuFind.cookie.isServiceAllowed(this.consentService)
+ : true;
+ if (!this.hasConsent) {
+ finna.scriptLoader.load(
+ {'cookie-consent': 'finna-cookie-consent-element.js'},
+ () => {
+ const consentModal = document.createElement('finna-consent');
+ consentModal.consentCategories = this.consentCategories;
+ consentModal.serviceUrl = this.source;
+
+ popupSettings.modal = consentModal;
+ $(this).finnaPopup(popupSettings);
+ if (this.active) {
+ this.click();
+ }
+ }
+ );
+ } else {
+ $(this).finnaPopup(popupSettings);
+ if (this.active) {
+ this.click();
+ }
+ }
+ }
+
+ /**
+ * When the element is added to the dom
+ */
+ connectedCallback() {
+ // Wait for the cookie consent to be initialized
+ if (VuFind.cookie.getConsentConfig() === null) {
+ VuFind.listen('cookie-consent-initialized', () => this.onConsentInitialized(), {once: true});
+ } else {
+ this.onConsentInitialized();
+ }
+ }
+
+
+ /**
+ * When the element is removed from the dom
+ */
+ disconnectedCallback() {
+ $(this).trigger('removeclick.finna');
+ }
+}
+
+customElements.define('finna-video', VideoElement);
diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php
index 34935c8e2fe..b3f8067d067 100644
--- a/themes/finna2/theme.config.php
+++ b/themes/finna2/theme.config.php
@@ -35,6 +35,7 @@
'Finna\View\Helper\Root\Iframe' => 'Finna\View\Helper\Root\IframeFactory',
'Finna\View\Helper\Root\ImageSrc' => 'Finna\View\Helper\Root\HelperWithThemeInfoFactory',
'Finna\View\Helper\Root\LayoutClass' => 'VuFind\View\Helper\Bootstrap5\LayoutClassFactory',
+ \Finna\View\Helper\Root\Legacy::class => \Finna\View\Helper\Root\LegacyFactory::class,
'Finna\View\Helper\Root\LinkedEventsTabs' => 'Laminas\ServiceManager\Factory\InvokableFactory',
'Finna\View\Helper\Root\Markdown' => 'VuFind\View\Helper\Root\MarkdownFactory',
'Finna\View\Helper\Root\Matomo' => 'Finna\View\Helper\Root\MatomoFactory',
@@ -112,6 +113,7 @@
'iframe' => 'Finna\View\Helper\Root\Iframe',
'imageSrc' => 'Finna\View\Helper\Root\ImageSrc',
'indexedTotal' => 'Finna\View\Helper\Root\TotalIndexed',
+ 'legacy' => \Finna\View\Helper\Root\Legacy::class,
'linkedEventsTabs' => 'Finna\View\Helper\Root\LinkedEventsTabs',
'markdown' => 'Finna\View\Helper\Root\Markdown',
'matomoTracking' => 'Finna\View\Helper\Root\MatomoTracking',
From 1f20fe8044def08b0fcd9775534c0f27a39517a9 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Thu, 11 Sep 2025 15:11:17 +0300
Subject: [PATCH 07/29] Removed legacy helper
---
local/config/finna/Legacy.ini.sample | 13 -
local/config/vufind/Legacy.ini.sample | 4 -
.../src/Finna/View/Helper/Root/Legacy.php | 138 -------
.../Finna/View/Helper/Root/LegacyFactory.php | 73 ----
.../src/Finna/View/Helper/Root/Record.php | 16 -
.../js/Legacy/finna-cookie-consent-element.js | 116 ------
.../finna2/js/Legacy/finna-video-element.js | 339 ------------------
themes/finna2/theme.config.php | 1 -
8 files changed, 700 deletions(-)
delete mode 100644 local/config/finna/Legacy.ini.sample
delete mode 100644 local/config/vufind/Legacy.ini.sample
delete mode 100644 module/Finna/src/Finna/View/Helper/Root/Legacy.php
delete mode 100644 module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php
delete mode 100644 themes/finna2/js/Legacy/finna-cookie-consent-element.js
delete mode 100644 themes/finna2/js/Legacy/finna-video-element.js
diff --git a/local/config/finna/Legacy.ini.sample b/local/config/finna/Legacy.ini.sample
deleted file mode 100644
index 81f2dcbbc4e..00000000000
--- a/local/config/finna/Legacy.ini.sample
+++ /dev/null
@@ -1,13 +0,0 @@
-; Ini file to handle deprecated content.
-
-; Templates contains information about which templates loads additional styles when loaded.
-; Key is the template as relative path and value is the section which used to append style or js files
-[Templates]
-template['RecordDriver/DefaultRecord/example-template-name.phtml'] = 'FINNA-xxxx'
-template['RecordDriver/DefaultRecord/another-template-name.phtml'] = 'FINNA-xxxx'
-
-; Section name containing legacy data. After local adjustments has been made, this can be emptied locally to prevent
-; additional css and/or js files from being loaded
-[FINNA-xxxx]
-styles[] = 'Legacy/file-containing-legacy-styles.css'
-scripts[] = 'Legacy/file-containing-legacy-js-code.js'
diff --git a/local/config/vufind/Legacy.ini.sample b/local/config/vufind/Legacy.ini.sample
deleted file mode 100644
index 6b989077102..00000000000
--- a/local/config/vufind/Legacy.ini.sample
+++ /dev/null
@@ -1,4 +0,0 @@
-; Ini file to handle deprecated content.
-
-[Parent_Config]
-relative_path = "../finna/Legacy.ini"
diff --git a/module/Finna/src/Finna/View/Helper/Root/Legacy.php b/module/Finna/src/Finna/View/Helper/Root/Legacy.php
deleted file mode 100644
index 8a3781db11d..00000000000
--- a/module/Finna/src/Finna/View/Helper/Root/Legacy.php
+++ /dev/null
@@ -1,138 +0,0 @@
-
- * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link http://vufind.org/wiki/vufind2:developer_manual Wiki
- */
-
-namespace Finna\View\Helper\Root;
-
-use Laminas\Config\Config;
-use Laminas\View\Helper\AbstractHelper;
-use VuFind\View\Helper\Root\ClassBasedTemplateRendererTrait;
-use VuFindTheme\ThemeInfo;
-
-/**
- * Backwards compatibility helper
- *
- * @category VuFind
- * @package View_Helpers
- * @author Juha Luoma
- * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link http://vufind.org/wiki/vufind2:developer_manual Wiki
- */
-class Legacy extends AbstractHelper
-{
- use ClassBasedTemplateRendererTrait;
-
- /**
- * Constructor
- *
- * @param Config $legacyConfig Legacy configuration
- * @param ThemeInfo $themeInfo Theme info helper
- * @param array $markedTemplates Templates which has been marked as legacy. If the Site has a custom template
- * present, this will load template with .legacy addition to load data which prevents
- * breaking styles.
- * @return void
- */
- public function __construct(
- protected Config $legacyConfig,
- protected ThemeInfo $themeInfo,
- protected array $markedTemplates = []
- ) {
- }
-
- /**
- * Check if the template is marked as legacy. This will cause additional files to be loaded if the site
- * has that template overwritten in custom folder.
- *
- * @param string $name Name of the template
- * @param string $name Class name of the driver
- *
- * @return string
- */
- public function __invoke(string $name, string $className): string
- {
- if (!$this->markedTemplates) {
- return $name;
- }
- if ($section = $this->markedTemplates[$name] ?? null && $this->hasCustomTemplate($name)) {
- // If the template is found with single name without resolving, then try to find the file
- $this->appendLegacyDependencies($section);
- return $name;
- }
-
- $template = 'RecordDriver/%s/' . $name;
- $resolved = $this->resolveClassTemplate(
- $template,
- $className,
- $this->getView()->resolver()
- );
- if ($section = $this->markedTemplates[$resolved] ?? null && $this->hasCustomTemplate($name)) {
- // If the template is found with single name without resolving, then try to find the file
- $this->appendLegacyDependencies($section);
- }
- return $name;
- }
-
- /**
- * Add legacy dependencies required for styles and scripts to work properly.
- *
- * @param string $sectionName Section name which contains js and/or css paths.
- *
- * @return void
- */
- protected function appendLegacyDependencies(string $sectionName): void
- {
- $sectionConfig = $this->legacyConfig->$sectionName ?? [];
- if (!$sectionConfig) {
- return;
- }
- $headScript = $this->getView()->plugin('headScript');
- foreach ($sectionConfig->scripts ?? [] as $script) {
- $headScript->appendFile($script);
- }
- $headLink = $this->getView()->plugin('headLink');
- foreach ($sectionConfig->styles ?? [] as $style) {
- $headLink->appendStylesheet($style);
- }
- }
-
- /**
- * Check if the current site has template overwritten in custom theme.
- *
- * @param string $templatePath Template path to check
- *
- * @return bool
- */
- protected function hasCustomTemplate(string $templatePath): bool
- {
- if ($result = $this->themeInfo->findInThemes('templates/' . $templatePath)) {
- $theme = array_shift($result);
- return $theme['theme'] === 'custom';
- }
- return false;
- }
-}
diff --git a/module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php b/module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php
deleted file mode 100644
index 8e63140cbf6..00000000000
--- a/module/Finna/src/Finna/View/Helper/Root/LegacyFactory.php
+++ /dev/null
@@ -1,73 +0,0 @@
-
- * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link https://vufind.org/wiki/development Wiki
- */
-
-namespace Finna\View\Helper\Root;
-
-use Laminas\ServiceManager\Factory\FactoryInterface;
-use Psr\Container\ContainerInterface;
-
-/**
- * Legacy helper factory
- *
- * @category VuFind
- * @package View_Helpers
- * @author Juha Luoma
- * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link https://vufind.org/wiki/development Wiki
- */
-class LegacyFactory implements FactoryInterface
-{
- /**
- * Create an object
- *
- * @param ContainerInterface $container Service Manager
- * @param string $requestedName Service being created
- * @param array|null $options Extra options (optional)
- *
- * @return object
- *
- * @throws \Exception
- */
- public function __invoke(
- ContainerInterface $container,
- $requestedName,
- array $options = null
- ) {
- if (!empty($options)) {
- throw new \Exception('Unexpected options sent to factory.');
- }
- $legacyConfig = $container->get(\VuFind\Config\PluginManager::class)->get('Legacy');
- $markedTemplates = $legacyConfig?->Templates?->template?->toArray() ?? [];
- return new $requestedName(
- $legacyConfig,
- $container->get(\VuFindTheme\ThemeInfo::class),
- $markedTemplates
- );
- }
-}
diff --git a/module/Finna/src/Finna/View/Helper/Root/Record.php b/module/Finna/src/Finna/View/Helper/Root/Record.php
index 670e56c7d6f..08d4e218639 100644
--- a/module/Finna/src/Finna/View/Helper/Root/Record.php
+++ b/module/Finna/src/Finna/View/Helper/Root/Record.php
@@ -1290,22 +1290,6 @@ public function getExternalRatingLink()
}
}
- /**
- *
- */
- public function renderTemplate($name, $context = null, $throw = true, $checkLegacy = true)
- {
- if ($checkLegacy) {
- // Check if the requested template is marked as a legacy template.
- // If so, download required template from the legacy/ path
- // This is usually required if the Site has template overwritten inside a custom theme
- $className = get_class($this->driver);
- $legacyHelper = $this->getView()->plugin('legacy');
- $name = ($legacyHelper)($name, $className);
- }
- return parent::renderTemplate($name, $context, $throw);
- }
-
/**
* Returns a translated copyright text.
*
diff --git a/themes/finna2/js/Legacy/finna-cookie-consent-element.js b/themes/finna2/js/Legacy/finna-cookie-consent-element.js
deleted file mode 100644
index ea8e0fe907a..00000000000
--- a/themes/finna2/js/Legacy/finna-cookie-consent-element.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/* global VuFind, CookieConsent */
-class FinnaCookieConsentElement extends HTMLElement {
-
- /**
- * Get consent categories
- * @returns {string} Consent categories
- */
- get consentCategories() {
- return this.getAttribute('consent-categories') || '';
- }
-
- /**
- * Set consent categories
- * @param {string} newValue Value to set
- */
- set consentCategories(newValue) {
- this.setAttribute('consent-categories', newValue);
- }
-
- /**
- * Get service base URL
- * @returns {string} Service base URL
- */
- get serviceBaseUrl() {
- const url = this.getAttribute('service-url');
- if (url) {
- try {
- return new URL(url).host;
- } catch (_) {
- return url;
- }
- }
- return '';
- }
-
- /**
- * Get service URL
- * @returns {string} Service URL
- */
- get serviceUrl() {
- return this.getAttribute('service-url') || '';
- }
-
- /**
- * Set service URL
- * @param {string} newValue Value to set
- */
- set serviceUrl(newValue) {
- this.setAttribute('service-url', newValue);
- }
-
- /**
- * Constructor
- */
- constructor() {
- super();
- }
-
- /**
- * When the element is added to the dom
- */
- connectedCallback() {
- // Create the element
- const divInfo = document.createElement('div');
- divInfo.classList.add('embedded-content-cookie-info');
-
- const divHeading = document.createElement('div');
- divHeading.classList.add('embedded-content-heading');
- divHeading.append(VuFind.translate('embedded_content_heading'));
- divInfo.append(divHeading);
-
- const divDescription = document.createElement('div');
- divDescription.classList.add('embedded-content-description');
- const replacements = {
- '%%serviceBaseUrl%%': this.serviceBaseUrl,
- '%%consentCategories%%': this.consentCategories
- };
- divDescription.append(VuFind.translate('embedded_content_description', replacements));
- divInfo.append(divDescription);
-
- const divActions = document.createElement('div');
- divActions.classList.add('embedded-content-actions');
-
- const aOuterLink = document.createElement('a');
- aOuterLink.classList.add('btn', 'btn-primary');
- aOuterLink.href = this.serviceUrl || '';
- aOuterLink.target = '_blank';
- aOuterLink.append(VuFind.translate('embedded_content_external_link'));
-
- const linkIcon = document.createElement('i');
- linkIcon.classList.add('fa', 'fa-new-window');
- linkIcon.setAttribute('aria-hidden', true);
- aOuterLink.append(' ', linkIcon);
-
- const linkSpan = document.createElement('span');
- linkSpan.classList.add('visually-hidden');
- linkSpan.append(VuFind.translate('Open in a new window'));
- aOuterLink.append(linkSpan);
- divActions.append(aOuterLink);
-
- const aShowModal = document.createElement('a');
- aShowModal.classList.add('btn', 'btn-default');
- aShowModal.href = '#';
- aShowModal.setAttribute('aria-haspopup', 'dialog');
- aShowModal.append(VuFind.translate('Cookie Settings'));
- aShowModal.addEventListener('click', () => {
- $.fn.finnaPopup.closeOpen();
- CookieConsent.showPreferences();
- });
- divActions.append(aShowModal);
- divInfo.append(divActions);
- this.append(divInfo);
- }
-}
-
-customElements.define('finna-consent', FinnaCookieConsentElement);
diff --git a/themes/finna2/js/Legacy/finna-video-element.js b/themes/finna2/js/Legacy/finna-video-element.js
deleted file mode 100644
index 717d48b46ee..00000000000
--- a/themes/finna2/js/Legacy/finna-video-element.js
+++ /dev/null
@@ -1,339 +0,0 @@
-/* global VuFind, finna */
-
-class VideoElement extends HTMLElement {
-
- /**
- * Get the type of the video, iFrame | video
- * @returns {string} Video type
- */
- get type() {
- return (this.getAttribute('type') || '').toLowerCase();
- }
-
- /**
- * Set the type of the video, iFrame | video
- * @param {string} value iFrame | video
- */
- set type(value) {
- this.setAttribute('type', value);
- }
-
- /**
- * Get the parent element to which the video player is being embedded into.
- * @returns {string|undefined} Parent element id or undefined
- */
- get embedParent() {
- return this.getAttribute('embed-parent') || undefined;
- }
-
- /**
- * Set the parent element to which the video player is being embedded into.
- * Omit to display in a new popup.
- * @param {string|undefined} value Parent element id or undefined.
- */
- set embedParent(value) {
- this.setAttribute('embed-parent', value);
- }
-
- /**
- * Get the source of the video.
- * @returns {string} Source of the video
- */
- get source() {
- return this.getAttribute('source') || '';
- }
-
- /**
- * Set the source of the video.
- * @param {string} value The video source
- */
- set source(value) {
- this.setAttribute('source', value);
- }
-
- /**
- * Get the video sources as an object.
- * @returns {object} Object containing video sources
- */
- get videoSources() {
- return this.getAttribute('video-sources') ? JSON.parse(this.getAttribute('video-sources')) : {};
- }
-
- /**
- * Set the video sources as an object.
- * @param {object} value Video sources object
- */
- set videoSources(value) {
- this.setAttribute('video-sources', JSON.stringify(value || {}));
- }
-
- /**
- * Get the poster url to display in viewer.
- * @returns {string} Poster url
- */
- get posterUrl() {
- return this.getAttribute('poster-url') || '';
- }
-
- /**
- * Set the poster url to display in viewer.
- * @param {string} value Poster url
- */
- set posterUrl(value) {
- this.setAttribute('poster-url', value);
- }
-
- /**
- * Get the identity for the popup group.
- * @returns {string} Id of the popup group
- */
- get popupId() {
- return this.getAttribute('popup-id') || '';
- }
-
- /**
- * Set the identity for the popup group.
- * @param {string} value Id of the popup group
- */
- set popupId(value) {
- this.setAttribute('popup-id', value);
- }
-
- /**
- * Get consent service required for the video.
- * @returns {string} Consent service accepted to display this video
- */
- get consentService() {
- return this.getAttribute('consent-service') || '';
- }
-
- /**
- * Get consent service required for the video.
- * @param {string} value Consent service accepted to display this video
- */
- set consentService(value) {
- this.setAttribute('consent-service', value);
- }
-
- /**
- * Get consent categories required for the video.
- * @returns {string} Consent categories
- */
- get consentCategories() {
- return this.getAttribute('consent-categories') || '';
- }
-
- /**
- * Set consent categories required for the video.
- * @param {string} value Consent categories
- */
- set consentCategories(value) {
- this.setAttribute('consent-categories', value);
- }
-
- /**
- * Get index.
- * @returns {string} Index of this video element
- */
- get index() {
- return this.getAttribute('index') || '';
- }
-
- /**
- * Set index.
- * @param {number} value Value
- */
- set index(value) {
- this.setAttribute('index', value);
- }
-
- /**
- * Get if the video should be activated on load.
- * return true if the value is 'true' as a string.
- * @returns {boolean} Is active?
- */
- get active() {
- return this.getAttribute('active') === 'true';
- }
-
- /**
- * Set if the video should be activated on load.
- * @param {boolean} value Is active?
- */
- set active(value) {
- this.setAttribute('active', value);
- }
-
- /**
- * Constructor
- */
- constructor() {
- super();
- this.modals = {
- video: ``,
- iframe: ``,
- audio: ``
- };
-
- this.translations = {
- close: VuFind.translate('close'),
- next: VuFind.translate('Next Record'),
- previous: VuFind.translate('Previous Record'),
- };
- this.scripts = {
- 'videojs': 'vendor/video.min.js',
- 'video-popup': 'finna-video-popup.js'
- };
- this.subScripts = {
- 'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
- 'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
- 'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js',
- };
- }
-
- /**
- * Called after consent settings have been initialized.
- */
- onConsentInitialized() {
- // Check if this video is inside a record
- const record = this.closest('div.record');
- const self = this;
- let classes = 'video-popup';
- let modal = this.modals.video;
- switch (this.type) {
- case 'iframe':
- classes = 'finna-iframe';
- modal = this.modals.iframe;
- break;
- case 'audio':
- classes = 'finna-audio';
- modal = this.modals.audio;
- break;
- }
- const popupSettings = {
- id: this.popupId,
- modal: modal,
- cycle: typeof this.embedParent !== 'undefined',
- classes: classes,
- parent: this.embedParent,
- translations: this.translations,
- onPopupInit: (t) => {
- if (this.embedParent) {
- t.removeClass('active-video');
- }
- },
- onPopupOpen: function onPopupOpen() {
- if (!self.hasConsent) {
- return;
- }
- if (record) {
- const warnings
- = record.querySelector(`.video-warning[data-index="${self.index}"]`);
- if (this.parent) {
- record.querySelectorAll('.active-video').forEach(v => {
- v.classList.remove('active-video');
- });
- record.querySelectorAll('.video-warning').forEach(v => {
- if (v.dataset.index !== self.index) {
- v.classList.add('hidden');
- } else {
- v.classList.remove('hidden');
- VuFind.observerManager.observe(
- 'LazyImages',
- v.querySelectorAll('img[data-src]')
- );
- }
- });
- this.currentTrigger().addClass('active-video');
- } else {
- this.content.css('height', '100%');
- if (warnings) {
- const clone = warnings.cloneNode(true);
- clone.classList.remove('hidden');
- this.modalHolder.append(clone);
- VuFind.observerManager.observe(
- 'LazyImages',
- clone.querySelectorAll('img[data-src]')
- );
- setTimeout(function startFade() {
- $(clone).fadeOut(2000);
- }, 3000);
- }
- }
- }
- switch (self.type) {
- case 'video':
- finna.scriptLoader.loadInOrder(self.scripts, self.subScripts, () => {
- finna.videoPopup.initVideoJs('.video-popup', self.videoSources, self.posterUrl);
- });
- break;
- case 'iframe':
- // If using Chrome + VoiceOver, Chrome crashes if vimeo player video settings button has aria-haspopup=true
- document.querySelectorAll('.vp-prefs .js-prefs').forEach(e => {
- e.setAttribute('aria-haspopup', false);
- });
- this.content.find('iframe').attr('src', this.adjustEmbedLink(self.source));
- break;
- case 'audio':
- this.content.css('height', '100%');
- this.content.find('audio').attr('src', self.source);
- break;
- default:
- console.warn(`Unknown video type in video element: ${self.type}`);
- break;
- }
- }
- };
- this.hasConsent = this.type === 'iframe'
- ? VuFind.cookie.isServiceAllowed(this.consentService)
- : true;
- if (!this.hasConsent) {
- finna.scriptLoader.load(
- {'cookie-consent': 'finna-cookie-consent-element.js'},
- () => {
- const consentModal = document.createElement('finna-consent');
- consentModal.consentCategories = this.consentCategories;
- consentModal.serviceUrl = this.source;
-
- popupSettings.modal = consentModal;
- $(this).finnaPopup(popupSettings);
- if (this.active) {
- this.click();
- }
- }
- );
- } else {
- $(this).finnaPopup(popupSettings);
- if (this.active) {
- this.click();
- }
- }
- }
-
- /**
- * When the element is added to the dom
- */
- connectedCallback() {
- // Wait for the cookie consent to be initialized
- if (VuFind.cookie.getConsentConfig() === null) {
- VuFind.listen('cookie-consent-initialized', () => this.onConsentInitialized(), {once: true});
- } else {
- this.onConsentInitialized();
- }
- }
-
-
- /**
- * When the element is removed from the dom
- */
- disconnectedCallback() {
- $(this).trigger('removeclick.finna');
- }
-}
-
-customElements.define('finna-video', VideoElement);
diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php
index fa3af657b41..3e42badd4e3 100644
--- a/themes/finna2/theme.config.php
+++ b/themes/finna2/theme.config.php
@@ -35,7 +35,6 @@
'Finna\View\Helper\Root\Iframe' => 'Finna\View\Helper\Root\IframeFactory',
'Finna\View\Helper\Root\ImageSrc' => 'Finna\View\Helper\Root\HelperWithThemeInfoFactory',
'Finna\View\Helper\Root\LayoutClass' => 'VuFind\View\Helper\Bootstrap5\LayoutClassFactory',
- \Finna\View\Helper\Root\Legacy::class => \Finna\View\Helper\Root\LegacyFactory::class,
'Finna\View\Helper\Root\LinkedEventsTabs' => 'Laminas\ServiceManager\Factory\InvokableFactory',
'Finna\View\Helper\Root\Markdown' => 'VuFind\View\Helper\Root\MarkdownFactory',
'Finna\View\Helper\Root\Matomo' => 'Finna\View\Helper\Root\MatomoFactory',
From d522e9d0bc1de7749be2795419f44facac6fb130 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Thu, 11 Sep 2025 15:40:07 +0300
Subject: [PATCH 08/29] REmoved unused css
---
module/Finna/src/Finna/Record/Loader.php | 2 +-
themes/finna2/css/Legacy/FINNA-3012.css | 60 ------------------------
themes/finna2/theme.config.php | 1 +
3 files changed, 2 insertions(+), 61 deletions(-)
delete mode 100644 themes/finna2/css/Legacy/FINNA-3012.css
diff --git a/module/Finna/src/Finna/Record/Loader.php b/module/Finna/src/Finna/Record/Loader.php
index 0e804ef3360..213d5dfe372 100644
--- a/module/Finna/src/Finna/Record/Loader.php
+++ b/module/Finna/src/Finna/Record/Loader.php
@@ -148,7 +148,7 @@ public function load(
&& $this->fallbackLoader->has($source)
&& null !== $id
&& '' !== $id
- && $result?->getExtraDetail('cached_record')
+ //&& $result?->getExtraDetail('cached_record')
) {
// Check for a redirected record without overwriting $result
if ($redirectedRecord = $this->fallbackLoader->get($source)->load((array)$id)) {
diff --git a/themes/finna2/css/Legacy/FINNA-3012.css b/themes/finna2/css/Legacy/FINNA-3012.css
deleted file mode 100644
index fc966cef88b..00000000000
--- a/themes/finna2/css/Legacy/FINNA-3012.css
+++ /dev/null
@@ -1,60 +0,0 @@
-.inline-video.finna-popup, .video-popup.finna-popup, .finna-iframe.finna-popup {
- display: flex;
- align-content: center;
- justify-content: center;
- .popup-iframe-wrapper {
- position: relative;
- width: 100%;
- padding-top: 56.25%;
- margin: auto;
- iframe {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- }
- }
-}
-
-finna-video {
- display: inline-block;
- cursor: pointer;
-}
-finna-video .btn.btn-link {
- padding: 0;
-}
-
-.inline-video-container {
- .inline-video {
- all: initial;
- .embed-responsive-item {
- height: 100%;
- width: 100%;
- }
- .finna-iframe.model-holder {
- max-width: 800px;
- }
- }
-}
-
-finna-consent {
- height: 100%;
- background: black;
- color: white;
- display: flex;
- align-items: center;
- justify-content: center;
- .embedded-content-heading {
- font-size: 18.4px;
- }
- .embedded-content-cookie-info > div {
- padding: 1rem 2rem;
- }
- .embedded-content-actions {
- display: flex;
- gap: 2rem;
- flex-wrap: wrap;
- }
-}
-
diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php
index 3e42badd4e3..54d44d68a48 100644
--- a/themes/finna2/theme.config.php
+++ b/themes/finna2/theme.config.php
@@ -267,6 +267,7 @@
[ 'file' => 'finna-a11y.js' ],
[ 'file' => 'finna-datepicker.js' ],
[ 'file' => 'finna-reservation-list.js' ],
+ [ 'file' => 'finna-video-player.js' ],
[ 'file' => 'components/finna-bazaar-browse-bar.js' ],
[ 'file' => 'components/finna-md-editable.js' ],
[ 'file' => 'components/finna-tabs-nav.js' ],
From b87ac03f7e0dd71faa093dff223c998595f53876 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Fri, 12 Sep 2025 15:01:15 +0300
Subject: [PATCH 09/29] Adjusted script loader to asynchronously download
scripts in order.
---
themes/finna2/js/finna-script-loader.js | 51 ++++-----
themes/finna2/js/finna-video-player.js | 103 +++++++++---------
themes/finna2/js/finna.js | 5 +
.../DefaultRecord/result-online-urls.phtml | 2 +-
.../_ui/components/finna-video.phtml | 13 ++-
.../download-checkout-history.phtml | 8 +-
themes/finna2/theme.config.php | 1 -
7 files changed, 86 insertions(+), 97 deletions(-)
diff --git a/themes/finna2/js/finna-script-loader.js b/themes/finna2/js/finna-script-loader.js
index f6d468067ee..f69e61726ce 100644
--- a/themes/finna2/js/finna-script-loader.js
+++ b/themes/finna2/js/finna-script-loader.js
@@ -10,43 +10,35 @@
finna.scriptLoader = (() => {
/**
- * Load given scripts asynchronously.
+ * Asynchronous function to load scripts in given order.
* @param {object} scripts Object of scripts to load
* Key is an unique identifier used to check if
* script has already been loaded
* Value is the js file name to load
* @param {?Function} scriptsLoaded Callback when the scripts are loaded
*/
- function load(scripts, scriptsLoaded) {
- let keyCount = Object.keys(scripts).length;
- let onScriptLoad;
- if (keyCount) {
- onScriptLoad = () => {
- if (--keyCount === 0) {
- scriptsLoaded();
- }
- };
- }
+ async function load(scripts, scriptsLoaded = () => {}) {
for (let [key, value] of Object.entries(scripts)) {
key = `scriptloader-js-${key}`;
- const found = document.getElementById(key);
- if (found) {
- keyCount--;
- continue;
- }
- const scriptElement = document.createElement('script');
- scriptElement.async = 'async';
- scriptElement.src = `${VuFind.path}/themes/finna2/js/${value}?_=${Date.now()}`;
- if (typeof onScriptLoad === 'function' && typeof scriptsLoaded === 'function') {
- scriptElement.addEventListener('load', onScriptLoad);
+ // Create a promise for the current script to see if it has been resolved/loaded
+ let promise = finna.getPromise(key);
+ if (!promise) {
+ finna.setPromise(key);
+ promise = finna.getPromise(key);
+ const scriptElement = document.createElement('script');
+ scriptElement.async = 'async';
+ scriptElement.src = `${VuFind.path}/themes/finna2/js/${value}?_=${Date.now()}`;
+ scriptElement.addEventListener('load', () => {
+ finna.resolvePromise(key);
+ });
+ scriptElement.id = key;
+ scriptElement.setAttribute('nonce', VuFind.getCspNonce());
+ document.head.appendChild(scriptElement);
}
- scriptElement.id = key;
- scriptElement.setAttribute('nonce', VuFind.getCspNonce());
- document.head.appendChild(scriptElement);
- }
- if (keyCount === 0 && typeof scriptsLoaded === 'function') {
- scriptsLoaded();
+ // Wait until the promise has resolved (Script has loaded) until loading the next one.
+ await promise;
}
+ scriptsLoaded();
}
/**
@@ -63,9 +55,8 @@ finna.scriptLoader = (() => {
* @param {?Function} scriptsLoaded Callback when the scripts are loaded
*/
function loadInOrder(first, last, scriptsLoaded) {
- load(first, () => {
- load(last, scriptsLoaded);
- });
+ let combined = {...first, ...last};
+ load(combined, scriptsLoaded);
}
return {
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 4d146985ff4..70f8123396e 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -2,30 +2,17 @@
finna.videoPlayer = (() => {
/**
- * These scripts must be loaded first for additional scripts requested to work properly
+ * Scripts to load in order
* @member {object} requiredVideoScripts
*/
const requiredVideoScripts = {
'videojs': 'vendor/video.min.js',
- 'video-popup': 'finna-video-popup.js'
- };
-
- /**
- * Additional scripts required to load when requesting video with videojs
- * @member {object} additionalVideoScripts
- */
- const additionalVideoScripts = {
+ 'video-popup': 'finna-video-popup.js',
'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
'videojs-airplay': 'vendor/silvermine-videojs-airplay.min.js',
};
- /**
- * Is the module already initialized?
- * @member {boolean}
- */
- let initialized = false;
-
/**
* Adds a specific class name to VuFind modal and a listener to listen when
* the modal is closed to remove the class name.
@@ -151,60 +138,68 @@ finna.videoPlayer = (() => {
}
/**
- * Provide a selector or HTMLButtonElement to initialize a button for videos.
- * @param {HTMLButtonElement|string} elementOrSelector Element or selector
+ * Sets the video elements click event.
+ * @param {HTMLElement} element Element which displays the video popup
*/
- function initVideoButton(elementOrSelector)
+ function setElementClickEvent(element)
{
- const element = typeof elementOrSelector === 'string'
- ? document.querySelector(elementOrSelector)
- : elementOrSelector;
- if (!element) {
- return;
- }
- if (element.classList.contains('done')) {
- return;
+ if (VuFind.cookie.isServiceAllowed(element.dataset.vcConsent)) {
+ element.addEventListener('click', () => {
+ document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video'));
+ if (element.dataset.vcEmbed) {
+ onIFrameOpen(element);
+ } else {
+ onVideoOpen(element);
+ }
+ element.classList.add('active-video');
+ });
+ if (element.classList.contains('active-video')) {
+ element.click();
+ }
+ } else {
+ // We should display a consent approval here
+ element.addEventListener('click', () => { displayConsentWindow(element); });
}
- element.classList.add('done');
- VuFind.listen('cookie-consent-initialized', () => {
- if (VuFind.cookie.isServiceAllowed(element.dataset.vcConsent)) {
- finna.getPromise('videoScripts').then(() => {
- element.addEventListener('click', () => {
- document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video'));
- if (element.dataset.vcEmbed) {
- onIFrameOpen(element);
- } else {
- onVideoOpen(element);
- }
- element.classList.add('active-video');
- });
- if (element.classList.contains('active-video')) {
- element.click();
- }
+ }
+
+ /**
+ * When video scripts have loaded callback
+ * @param {HTMLElement|string} elementOrSelector Either the html element which acts as video player open or selector
+ * @returns
+ */
+ function onVideoScriptsLoaded(elementOrSelector)
+ {
+ const element = typeof elementOrSelector === 'string'
+ ? document.querySelector(elementOrSelector)
+ : elementOrSelector;
+ if (!element || element.classList.contains('done')) {
+ return;
+ }
+
+ element.classList.add('done');
+ const consentInitialized = VuFind.cookie.getConsentConfig();
+ // If consent configuration has not been initialized, wait for it
+ if (!consentInitialized) {
+ VuFind.listen('cookie-consent-initialized', () => {
+ setElementClickEvent(element);
});
} else {
- // We should display a consent approval here
- element.addEventListener('click', () => { displayConsentWindow(element); });
+ setElementClickEvent(element);
}
- });
}
/**
- * Initialize the module
+ * Provide a selector or HTMLButtonElement to initialize a button for videos.
+ * @param {HTMLButtonElement|string} elementOrSelector Element or selector
*/
- function init()
+ function initVideoButton(elementOrSelector)
{
- if (initialized) {
- return;
- }
- initialized = true;
- finna.scriptLoader.loadInOrder(requiredVideoScripts, additionalVideoScripts, () => {
- finna.resolvePromise('videoScripts');
+ finna.scriptLoader.load(requiredVideoScripts, () => {
+ onVideoScriptsLoaded(elementOrSelector);
});
}
return {
initVideoButton,
- init
};
})();
diff --git a/themes/finna2/js/finna.js b/themes/finna2/js/finna.js
index 3c4da79be19..0d428407ca9 100644
--- a/themes/finna2/js/finna.js
+++ b/themes/finna2/js/finna.js
@@ -62,6 +62,11 @@ var finna = (function finnaModule() {
getPromise: (name) => {
return promises[name];
},
+ setPromise: (name) => {
+ if (!promises[name]) {
+ promises[name] = new Promise((resolve) => resolves[name] = resolve);
+ }
+ }
};
return my;
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
index 66e1bf347ca..032d4a752ca 100644
--- a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
+++ b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
@@ -69,7 +69,7 @@
'index' => $i,
'videoSources' => $url['videoSources'] ?? [],
'description' => $desc,
- 'class' => 'btn btn-link video-link-container',
+ 'class' => "btn btn-link video-link-container $context",
]);
?>
diff --git a/themes/finna2/templates/_ui/components/finna-video.phtml b/themes/finna2/templates/_ui/components/finna-video.phtml
index 4164fd6b61a..81446bf3b4f 100644
--- a/themes/finna2/templates/_ui/components/finna-video.phtml
+++ b/themes/finna2/templates/_ui/components/finna-video.phtml
@@ -5,13 +5,14 @@
$active = $this->active ?? false;
$description = $this->description ?? $this->translate('format_Video');
$videoSources = $this->videoSources ?? [];
+ $videoIndex = $this->index ?? 1;
$buttonAttributes = [
'class' => $this->class ?? 'video-link-container',
'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description,
'title' => $url,
'data-vc-url' => $url,
+ 'data-vc-index' => $index,
'data-vc-invocation' => $this->_invocation,
- 'data-vc-index' => $this->index ?? -1,
'data-vc-inline' => $this->inline ?? false,
'data-vc-embed' => $embeddedVideo,
'data-vc-consent' => $this->consent ?? 'recordvideo',
@@ -30,8 +31,12 @@
{finna.videoPlayer.initVideoButton('{$jsSelector}');});
+ finna.scriptLoader.load(
+ {'finna-video-player': 'finna-video-player.js'},
+ () => {
+ finna.videoPlayer.initVideoButton('{$jsSelector}');
+ }
+ );
JS;
?>
-=$this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?>
+=$this->assetManager()->outputInlineScriptString($script);?>
diff --git a/themes/finna2/templates/myresearch/download-checkout-history.phtml b/themes/finna2/templates/myresearch/download-checkout-history.phtml
index ad59406156c..f0701643685 100644
--- a/themes/finna2/templates/myresearch/download-checkout-history.phtml
+++ b/themes/finna2/templates/myresearch/download-checkout-history.phtml
@@ -36,13 +36,7 @@ $js = << {
- if (document.readyState === 'complete') {
- finna.checkoutHistory.init();
- } else {
- document.addEventListener('DOMContentLoaded', () => {
- finna.checkoutHistory.init();
- });
- }
+ finna.checkoutHistory.init();
}
);
JS;
diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php
index 54d44d68a48..3e42badd4e3 100644
--- a/themes/finna2/theme.config.php
+++ b/themes/finna2/theme.config.php
@@ -267,7 +267,6 @@
[ 'file' => 'finna-a11y.js' ],
[ 'file' => 'finna-datepicker.js' ],
[ 'file' => 'finna-reservation-list.js' ],
- [ 'file' => 'finna-video-player.js' ],
[ 'file' => 'components/finna-bazaar-browse-bar.js' ],
[ 'file' => 'components/finna-md-editable.js' ],
[ 'file' => 'components/finna-tabs-nav.js' ],
From 068e398e4dfab86b57cf7b930be8071438c27fe5 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 11:43:49 +0300
Subject: [PATCH 10/29] Adjusted script loading to work properly with using
promises and adjusted player height
---
themes/finna2/js/finna-script-loader.js | 36 +++++++++------
themes/finna2/js/finna-video-player.js | 44 +++++++++++--------
themes/finna2/js/finna.js | 1 +
themes/finna2/scss/finna/video-player.scss | 5 ++-
themes/finna2/scss/global/variables.scss | 3 +-
.../_ui/components/finna-video.phtml | 4 +-
6 files changed, 58 insertions(+), 35 deletions(-)
diff --git a/themes/finna2/js/finna-script-loader.js b/themes/finna2/js/finna-script-loader.js
index f69e61726ce..9035256c94d 100644
--- a/themes/finna2/js/finna-script-loader.js
+++ b/themes/finna2/js/finna-script-loader.js
@@ -8,37 +8,45 @@
* @returns {object} Exposed functions
*/
finna.scriptLoader = (() => {
-
/**
* Asynchronous function to load scripts in given order.
+ * If this is the first time calling this function with a certain key,
+ * assign all the script loading tasks to the first caller to avoid duplicates.
* @param {object} scripts Object of scripts to load
* Key is an unique identifier used to check if
* script has already been loaded
* Value is the js file name to load
* @param {?Function} scriptsLoaded Callback when the scripts are loaded
*/
- async function load(scripts, scriptsLoaded = () => {}) {
+ function load(scripts, scriptsLoaded = () => {}) {
+ let promisesToWait = [];
+ const onLoadFunc = (e) => finna.resolvePromise(e.currentTarget.id);
for (let [key, value] of Object.entries(scripts)) {
key = `scriptloader-js-${key}`;
- // Create a promise for the current script to see if it has been resolved/loaded
- let promise = finna.getPromise(key);
- if (!promise) {
- finna.setPromise(key);
- promise = finna.getPromise(key);
+ let foundPromise = finna.getPromise(key);
+ if (!foundPromise) {
+ foundPromise = finna.setPromise(key);
const scriptElement = document.createElement('script');
- scriptElement.async = 'async';
scriptElement.src = `${VuFind.path}/themes/finna2/js/${value}?_=${Date.now()}`;
- scriptElement.addEventListener('load', () => {
- finna.resolvePromise(key);
- });
+ scriptElement.async = 'async';
scriptElement.id = key;
+ scriptElement.addEventListener('load', onLoadFunc);
scriptElement.setAttribute('nonce', VuFind.getCspNonce());
document.head.appendChild(scriptElement);
}
+ promisesToWait.push(foundPromise);
// Wait until the promise has resolved (Script has loaded) until loading the next one.
- await promise;
}
- scriptsLoaded();
+ const handlePromise = (cb) => {
+ promisesToWait.shift().then(() => {
+ if (promisesToWait.length > 0) {
+ handlePromise(cb);
+ } else {
+ cb();
+ }
+ });
+ };
+ handlePromise(scriptsLoaded);
}
/**
@@ -55,7 +63,7 @@ finna.scriptLoader = (() => {
* @param {?Function} scriptsLoaded Callback when the scripts are loaded
*/
function loadInOrder(first, last, scriptsLoaded) {
- let combined = {...first, ...last};
+ let combined = Object.assign({}, first, last);
load(combined, scriptsLoaded);
}
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 70f8123396e..2e58341df54 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -7,6 +7,13 @@ finna.videoPlayer = (() => {
*/
const requiredVideoScripts = {
'videojs': 'vendor/video.min.js',
+ };
+
+ /**
+ * Scritps which are dependant of the videojs script
+ * @member {object} dependantVideoScripts
+ */
+ const dependantVideoScripts = {
'video-popup': 'finna-video-popup.js',
'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
@@ -120,7 +127,7 @@ finna.videoPlayer = (() => {
if (description) {
const serviceBase = new URL(element.dataset.vcUrl);
description.innerText = description.innerText
- .replace('%%consentCategories%%', element.dataset.vcConsent)
+ .replace('%%consentCategories%%', element.dataset.vcConsentTitle)
.replace('%%serviceBaseUrl%%', serviceBase.hostname);
}
VuFind.lightbox.render(wrapper.outerHTML);
@@ -165,27 +172,26 @@ finna.videoPlayer = (() => {
/**
* When video scripts have loaded callback
* @param {HTMLElement|string} elementOrSelector Either the html element which acts as video player open or selector
- * @returns
*/
function onVideoScriptsLoaded(elementOrSelector)
{
- const element = typeof elementOrSelector === 'string'
- ? document.querySelector(elementOrSelector)
- : elementOrSelector;
- if (!element || element.classList.contains('done')) {
- return;
- }
+ const element = typeof elementOrSelector === 'string'
+ ? document.querySelector(elementOrSelector)
+ : elementOrSelector;
+ if (!element || element.classList.contains('done')) {
+ return;
+ }
- element.classList.add('done');
- const consentInitialized = VuFind.cookie.getConsentConfig();
- // If consent configuration has not been initialized, wait for it
- if (!consentInitialized) {
- VuFind.listen('cookie-consent-initialized', () => {
- setElementClickEvent(element);
- });
- } else {
+ element.classList.add('done');
+ const consentInitialized = VuFind.cookie.getConsentConfig();
+ // If consent configuration has not been initialized, wait for it
+ if (!consentInitialized) {
+ VuFind.listen('cookie-consent-initialized', () => {
setElementClickEvent(element);
- }
+ });
+ } else {
+ setElementClickEvent(element);
+ }
}
/**
@@ -195,7 +201,9 @@ finna.videoPlayer = (() => {
function initVideoButton(elementOrSelector)
{
finna.scriptLoader.load(requiredVideoScripts, () => {
- onVideoScriptsLoaded(elementOrSelector);
+ finna.scriptLoader.load(dependantVideoScripts, () => {
+ onVideoScriptsLoaded(elementOrSelector);
+ });
});
}
diff --git a/themes/finna2/js/finna.js b/themes/finna2/js/finna.js
index 0d428407ca9..36d20f89f86 100644
--- a/themes/finna2/js/finna.js
+++ b/themes/finna2/js/finna.js
@@ -66,6 +66,7 @@ var finna = (function finnaModule() {
if (!promises[name]) {
promises[name] = new Promise((resolve) => resolves[name] = resolve);
}
+ return promises[name];
}
};
diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss
index 093b101a3e6..941ef3be04e 100644
--- a/themes/finna2/scss/finna/video-player.scss
+++ b/themes/finna2/scss/finna/video-player.scss
@@ -32,7 +32,10 @@
// Template for using proper styles, when appending video viewer inside an element.
%iframe-centering-template {
height: $inline-video-player-desktop-height;
- @media (max-width: $screen-sm-max) {
+ @media (max-width: $screen-md-max) {
+ height: $inline-video-player-tablet-height;
+ }
+ @media (max-width: $screen-sm-min) {
height: $inline-video-player-mobile-height;
}
display: flex;
diff --git a/themes/finna2/scss/global/variables.scss b/themes/finna2/scss/global/variables.scss
index 126382c5df1..c5c666e16c8 100644
--- a/themes/finna2/scss/global/variables.scss
+++ b/themes/finna2/scss/global/variables.scss
@@ -347,5 +347,6 @@ $item-status-color-unavailable: $danger !default;
$item-status-color-unknown: $warning !default;
$item-status-color-uncertain: $warning !default;
-$inline-video-player-desktop-height: 400px;
+$inline-video-player-desktop-height: 500px;
+$inline-video-player-tablet-height: 400px;
$inline-video-player-mobile-height: 200px;
diff --git a/themes/finna2/templates/_ui/components/finna-video.phtml b/themes/finna2/templates/_ui/components/finna-video.phtml
index 81446bf3b4f..b889307129c 100644
--- a/themes/finna2/templates/_ui/components/finna-video.phtml
+++ b/themes/finna2/templates/_ui/components/finna-video.phtml
@@ -6,6 +6,7 @@
$description = $this->description ?? $this->translate('format_Video');
$videoSources = $this->videoSources ?? [];
$videoIndex = $this->index ?? 1;
+ $consent = $this->consent ?? 'recordvideo';
$buttonAttributes = [
'class' => $this->class ?? 'video-link-container',
'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description,
@@ -15,7 +16,8 @@
'data-vc-invocation' => $this->_invocation,
'data-vc-inline' => $this->inline ?? false,
'data-vc-embed' => $embeddedVideo,
- 'data-vc-consent' => $this->consent ?? 'recordvideo',
+ 'data-vc-consent' => $consent,
+ 'data-vc-consent-title' => $this->translate($this->cookieConsent()->getCategoryTitleForService($consent)),
'aria-haspopup' => true,
];
$buttonAttributes['class'] .= ' ' . $this->_componentClass;
From c76740f121abf8ef4ea13291908f0d126cc82579 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 11:45:00 +0300
Subject: [PATCH 11/29] Removed bad comment
---
module/Finna/src/Finna/Record/Loader.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/module/Finna/src/Finna/Record/Loader.php b/module/Finna/src/Finna/Record/Loader.php
index 213d5dfe372..0e804ef3360 100644
--- a/module/Finna/src/Finna/Record/Loader.php
+++ b/module/Finna/src/Finna/Record/Loader.php
@@ -148,7 +148,7 @@ public function load(
&& $this->fallbackLoader->has($source)
&& null !== $id
&& '' !== $id
- //&& $result?->getExtraDetail('cached_record')
+ && $result?->getExtraDetail('cached_record')
) {
// Check for a redirected record without overwriting $result
if ($redirectedRecord = $this->fallbackLoader->get($source)->load((array)$id)) {
From 18cf829581a37f8f674bf47f395b12ee627ba1e0 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 11:57:00 +0300
Subject: [PATCH 12/29] Allow wider popup modal
---
themes/finna2/scss/finna/video-player.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss
index 941ef3be04e..a23e8efc50b 100644
--- a/themes/finna2/scss/finna/video-player.scss
+++ b/themes/finna2/scss/finna/video-player.scss
@@ -148,6 +148,7 @@
justify-content: center;
.modal-dialog {
flex: 1 1 auto;
+ max-width: 1200px;
.modal-content {
width: 100%;
height: 100%;
From c31d42d4f9f65861751e44a34f385553e8dee042 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 12:01:12 +0300
Subject: [PATCH 13/29] adjusted comment
---
themes/finna2/js/finna-script-loader.js | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/themes/finna2/js/finna-script-loader.js b/themes/finna2/js/finna-script-loader.js
index 9035256c94d..f75b997b7ec 100644
--- a/themes/finna2/js/finna-script-loader.js
+++ b/themes/finna2/js/finna-script-loader.js
@@ -9,9 +9,7 @@
*/
finna.scriptLoader = (() => {
/**
- * Asynchronous function to load scripts in given order.
- * If this is the first time calling this function with a certain key,
- * assign all the script loading tasks to the first caller to avoid duplicates.
+ * Load given scripts asynchronously.
* @param {object} scripts Object of scripts to load
* Key is an unique identifier used to check if
* script has already been loaded
From 2dff0afb24e4b548df59b8fc6389be94a6997fba Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 12:02:46 +0300
Subject: [PATCH 14/29] Remove unused promise
---
themes/finna2/js/finna.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/themes/finna2/js/finna.js b/themes/finna2/js/finna.js
index 36d20f89f86..3f57ef21020 100644
--- a/themes/finna2/js/finna.js
+++ b/themes/finna2/js/finna.js
@@ -13,8 +13,7 @@ var finna = (function finnaModule() {
* @member {object}
*/
let promises = {
- lazyImages: new Promise((resolve) => { resolves.lazyImages = resolve; }),
- videoScripts: new Promise((resolve) => { resolves.videoScripts = resolve; })
+ lazyImages: new Promise((resolve) => { resolves.lazyImages = resolve; })
};
var my = {
From a9471daba95b643058de39f831858e247e3de896 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 12:05:14 +0300
Subject: [PATCH 15/29] removed unused alias
---
themes/finna2/theme.config.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/themes/finna2/theme.config.php b/themes/finna2/theme.config.php
index 3e42badd4e3..46f33581db6 100644
--- a/themes/finna2/theme.config.php
+++ b/themes/finna2/theme.config.php
@@ -113,7 +113,6 @@
'iframe' => 'Finna\View\Helper\Root\Iframe',
'imageSrc' => 'Finna\View\Helper\Root\ImageSrc',
'indexedTotal' => 'Finna\View\Helper\Root\TotalIndexed',
- 'legacy' => \Finna\View\Helper\Root\Legacy::class,
'linkedEventsTabs' => 'Finna\View\Helper\Root\LinkedEventsTabs',
'markdown' => 'Finna\View\Helper\Root\Markdown',
'matomoTracking' => 'Finna\View\Helper\Root\MatomoTracking',
From da4b7de703fab153c8c355b4d2b5a1e763aded95 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Mon, 15 Sep 2025 12:13:08 +0300
Subject: [PATCH 16/29] Added start to available online links to avoid popup
center rule
---
themes/finna2/scss/finna/search.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/themes/finna2/scss/finna/search.scss b/themes/finna2/scss/finna/search.scss
index 116cd38847b..9869e5df1e2 100644
--- a/themes/finna2/scss/finna/search.scss
+++ b/themes/finna2/scss/finna/search.scss
@@ -145,6 +145,7 @@ mark, .highlight {
font-weight: 400;
clear: both;
word-break: break-all;
+ text-align: start;
.online-source {
color: $gray-light;
font-size: .9em;
From a63c33952b64e5a284658803903e335e6a026df1 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Wed, 24 Sep 2025 14:33:33 +0300
Subject: [PATCH 17/29] Adjusted comments, try to separate iframe video path
from videojs path
---
themes/finna2/js/finna-video-player.js | 73 +++++++++++--------
.../DefaultRecord/record-video-player.phtml | 7 +-
.../DefaultRecord/result-online-urls.phtml | 6 +-
.../_ui/components/finna-video-button.phtml | 57 +++++++++++++++
.../_ui/components/finna-video.phtml | 44 -----------
5 files changed, 106 insertions(+), 81 deletions(-)
create mode 100644 themes/finna2/templates/_ui/components/finna-video-button.phtml
delete mode 100644 themes/finna2/templates/_ui/components/finna-video.phtml
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 2e58341df54..23933d75127 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -50,7 +50,7 @@ finna.videoPlayer = (() => {
warning.querySelectorAll('img[data-src]')
);
});
- warning.classList.toggle('hidden', element.dataset.vcIndex !== warning.dataset.index);
+ warning.classList.toggle('hidden', element.dataset.index !== warning.dataset.index);
});
}
@@ -66,7 +66,7 @@ finna.videoPlayer = (() => {
// Is the video inline video or popup video
let container;
- if (element.dataset.vcInline) {
+ if (element.dataset.inline) {
container = document.getElementById('inline-video');
container.replaceChildren(videoPlayer);
showWarningIcons(element);
@@ -91,8 +91,8 @@ finna.videoPlayer = (() => {
iFrame.className = 'player';
iFrame.frameborder = 0;
iFrame.allowFullscreen = 'true';
- iFrame.src = element.dataset.vcUrl;
- if (element.dataset.vcInline) {
+ iFrame.src = element.dataset.url;
+ if (element.dataset.inline) {
const container = document.getElementById('inline-video');
container.replaceChildren(iFrame);
showWarningIcons(element);
@@ -121,13 +121,13 @@ finna.videoPlayer = (() => {
// Replace %%consentCategories%% and %%serviceBaseUrl%% with proper values
const externalLink = wrapper.querySelector('.embedded-content-actions a[href="%%HREF%%"]');
if (externalLink) {
- externalLink.setAttribute('href', element.dataset.vcUrl);
+ externalLink.setAttribute('href', element.dataset.url);
}
const description = wrapper.querySelector('.embedded-content-description');
if (description) {
- const serviceBase = new URL(element.dataset.vcUrl);
+ const serviceBase = new URL(element.dataset.url);
description.innerText = description.innerText
- .replace('%%consentCategories%%', element.dataset.vcConsentTitle)
+ .replace('%%consentCategories%%', element.dataset.consentTitle)
.replace('%%serviceBaseUrl%%', serviceBase.hostname);
}
VuFind.lightbox.render(wrapper.outerHTML);
@@ -147,18 +147,14 @@ finna.videoPlayer = (() => {
/**
* Sets the video elements click event.
* @param {HTMLElement} element Element which displays the video popup
+ * @param {Function} onClick Function to run when clicking the button.
*/
- function setElementClickEvent(element)
+ function setElementClickEvent(element, onClick)
{
- if (VuFind.cookie.isServiceAllowed(element.dataset.vcConsent)) {
+ if (VuFind.cookie.isServiceAllowed(element.dataset.consent)) {
element.addEventListener('click', () => {
- document.querySelectorAll('.vc-finna-video').forEach(b => b.classList.remove('active-video'));
- if (element.dataset.vcEmbed) {
- onIFrameOpen(element);
- } else {
- onVideoOpen(element);
- }
- element.classList.add('active-video');
+ document.querySelectorAll('.vc-finna-video-button').forEach(b => b.classList.remove('active-video'));
+ onClick(element);
});
if (element.classList.contains('active-video')) {
element.click();
@@ -170,44 +166,61 @@ finna.videoPlayer = (() => {
}
/**
- * When video scripts have loaded callback
- * @param {HTMLElement|string} elementOrSelector Either the html element which acts as video player open or selector
+ * Function to check for consent config initialization.
+ *
+ * @param {HTMLElement} element Element to check consent for
*/
- function onVideoScriptsLoaded(elementOrSelector)
+ function waitConsentInitialized(element, onClick)
{
- const element = typeof elementOrSelector === 'string'
- ? document.querySelector(elementOrSelector)
- : elementOrSelector;
- if (!element || element.classList.contains('done')) {
- return;
- }
-
- element.classList.add('done');
const consentInitialized = VuFind.cookie.getConsentConfig();
+
// If consent configuration has not been initialized, wait for it
if (!consentInitialized) {
VuFind.listen('cookie-consent-initialized', () => {
- setElementClickEvent(element);
+ setElementClickEvent(element, onClick);
});
} else {
- setElementClickEvent(element);
+ setElementClickEvent(element, onClick);
}
}
+ /**
+ * Provide a selector or HTMLButtonElement to initialize a button for embedded videos.
+ * @param {HTMLButtonElement|string} elementOrSelector Element or selector
+ */
+ function initIFrameButton(elementOrSelector)
+ {
+ const element = typeof elementOrSelector === 'string'
+ ? document.querySelector(elementOrSelector)
+ : elementOrSelector;
+ if (!element || element.classList.contains('initialized')) {
+ return;
+ }
+ waitConsentInitialized(element, onIFrameOpen);
+ }
+
/**
* Provide a selector or HTMLButtonElement to initialize a button for videos.
+ * Handles loading the proper scripts.
* @param {HTMLButtonElement|string} elementOrSelector Element or selector
*/
function initVideoButton(elementOrSelector)
{
+ const element = typeof elementOrSelector === 'string'
+ ? document.querySelector(elementOrSelector)
+ : elementOrSelector;
+ if (!element || element.classList.contains('initialized')) {
+ return;
+ }
finna.scriptLoader.load(requiredVideoScripts, () => {
finna.scriptLoader.load(dependantVideoScripts, () => {
- onVideoScriptsLoaded(elementOrSelector);
+ waitConsentInitialized(element, onVideoOpen);
});
});
}
return {
initVideoButton,
+ initIFrameButton
};
})();
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
index c87d8aa2921..1461ab45108 100644
--- a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
+++ b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
@@ -60,14 +60,13 @@
}
?>
=
- $this->component('finna-video', [
- 'url' => $url,
- 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']),
+ $this->component('finna-video-button', [
+ 'url' => $url['url'],
+ 'embed' => $embeddedVideo === 'data-embed-iframe',
'index' => $i,
'videoSources' => $url['videoSources'] ?? [],
'description' => $desc,
'inline' => $inlineVideo,
- 'active' => $inlineVideo && $i === 0,
]);
?>
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
index fafc18f85dd..322f9b1916f 100644
--- a/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
+++ b/themes/finna2/templates/RecordDriver/DefaultRecord/result-online-urls.phtml
@@ -63,9 +63,9 @@
?>
=
- $this->component('finna-video', [
- 'url' => $url,
- 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']),
+ $this->component('finna-video-button', [
+ 'url' => $url['url'],
+ 'embed' => $this->recordLinker()->getEmbeddedVideo($url['url']) === 'data-embed-iframe',
'index' => $i,
'videoSources' => $url['videoSources'] ?? [],
'description' => $desc,
diff --git a/themes/finna2/templates/_ui/components/finna-video-button.phtml b/themes/finna2/templates/_ui/components/finna-video-button.phtml
new file mode 100644
index 00000000000..5ddd3d1e4b8
--- /dev/null
+++ b/themes/finna2/templates/_ui/components/finna-video-button.phtml
@@ -0,0 +1,57 @@
+inline ?? false;
+ $index = $this->index ?? 0;
+ $description = $this->description ?? $this->translate('format_Video');
+ $videoSources = $this->videoSources ?? [];
+ $consent = $this->consent ?? 'recordvideo';
+ $embeddedVideo = $this->embed ?? false;
+ $buttonAttributes = [
+ 'class' => trim(($this->class ?? 'video-link-container') . " vc-invocation-$this->_invocation $this->_componentClass"),
+ 'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description,
+ 'data-url' => $this->url ?? '',
+ 'data-index' => $index,
+ 'data-inline' => $inlineVideoPlayer,
+ 'data-embed' => $embeddedVideo,
+ 'data-consent' => $consent,
+ 'data-consent-title' => $this->translate($this->cookieConsent()->getCategoryTitleForService($consent)),
+ 'aria-haspopup' => true,
+ ];
+ $buttonAttributes['class'] .= ' ' . $this->_componentClass;
+ if ($index === 0 && $inlineVideoPlayer) {
+ $buttonAttributes['class'] .= ' active-video';
+ }
+ // Generate unique selector for the button
+ $jsSelector = '.' . str_replace(' ', '.', $buttonAttributes['class']);
+?>
+htmlAttributes($buttonAttributes);?> data-video-sources="=htmlspecialchars(json_encode($videoSources), ENT_QUOTES, 'UTF-8');?>">
+ =$this->icon('video-play', 'video-play-icon');?>
+ =$this->escapeHtml($this->truncate(ucfirst($description), 30))?>
+
+ {
+ const embedded = '{$embeddedVideo}';
+ if (embedded) {
+ finna.videoPlayer.initIFrameButton('{$jsSelector}');
+ } else {
+ finna.videoPlayer.initVideoButton('{$jsSelector}');
+ }
+ }
+ );
+ JS;
+?>
+=$this->assetManager()->outputInlineScriptString($script);?>
diff --git a/themes/finna2/templates/_ui/components/finna-video.phtml b/themes/finna2/templates/_ui/components/finna-video.phtml
deleted file mode 100644
index b889307129c..00000000000
--- a/themes/finna2/templates/_ui/components/finna-video.phtml
+++ /dev/null
@@ -1,44 +0,0 @@
-url['url'] ?? $this->url;
- $embeddedVideo = $this->embed === 'data-embed-iframe' ?: false;
- $videoIndex = $this->index;
- $active = $this->active ?? false;
- $description = $this->description ?? $this->translate('format_Video');
- $videoSources = $this->videoSources ?? [];
- $videoIndex = $this->index ?? 1;
- $consent = $this->consent ?? 'recordvideo';
- $buttonAttributes = [
- 'class' => $this->class ?? 'video-link-container',
- 'aria-label' => $this->ariaLabel ?? $this->translate('Link to video') . ' ' . $description,
- 'title' => $url,
- 'data-vc-url' => $url,
- 'data-vc-index' => $index,
- 'data-vc-invocation' => $this->_invocation,
- 'data-vc-inline' => $this->inline ?? false,
- 'data-vc-embed' => $embeddedVideo,
- 'data-vc-consent' => $consent,
- 'data-vc-consent-title' => $this->translate($this->cookieConsent()->getCategoryTitleForService($consent)),
- 'aria-haspopup' => true,
- ];
- $buttonAttributes['class'] .= ' ' . $this->_componentClass;
- if ($active) {
- $buttonAttributes['class'] .= ' active-video';
- }
- // Generate unique selector for the button
- $jsSelector = '.' . str_replace(' ', '.', $buttonAttributes['class']) . "[data-vc-invocation=\"$this->_invocation\"]";
-?>
-htmlAttributes($buttonAttributes);?> data-video-sources="=htmlspecialchars(json_encode($videoSources), ENT_QUOTES, 'UTF-8');?>">
- =$this->icon('video-play', 'video-play-icon');?>
- =$this->escapeHtml($this->truncate(ucfirst($description), 30))?>
-
- {
- finna.videoPlayer.initVideoButton('{$jsSelector}');
- }
- );
- JS;
-?>
-=$this->assetManager()->outputInlineScriptString($script);?>
From d369a4d8a14669108f383b25071041f446ed7482 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Wed, 24 Sep 2025 15:15:58 +0300
Subject: [PATCH 18/29] Comments
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 23933d75127..40985a378f1 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -167,8 +167,8 @@ finna.videoPlayer = (() => {
/**
* Function to check for consent config initialization.
- *
* @param {HTMLElement} element Element to check consent for
+ * @param {Function} onClick Function to run when clicking the button.
*/
function waitConsentInitialized(element, onClick)
{
From 28ea1019002ee4cfaf9597bfcf6a2235b81757a0 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Wed, 24 Sep 2025 15:26:05 +0300
Subject: [PATCH 19/29] Reverted removed line
---
.../RecordDriver/DefaultRecord/record-video-player.phtml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
index 1461ab45108..dc3168dd815 100644
--- a/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
+++ b/themes/finna2/templates/RecordDriver/DefaultRecord/record-video-player.phtml
@@ -69,7 +69,12 @@
'inline' => $inlineVideo,
]);
?>
-
+ fileSize($fileSize['value']) . ')' : '';
+ }
+ ?>
=$this->record($this->driver)->renderTemplate('video-information.phtml', ['i' => $i, 'url' => $url]);?>
From a93bb80f20743d91e1a733070b22a5f1b7e37642 Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Wed, 24 Sep 2025 16:48:49 +0300
Subject: [PATCH 20/29] Display consent only on embedded videos
---
themes/finna2/js/finna-video-player.js | 63 ++++++++++---------
.../_ui/components/finna-video-button.phtml | 2 +-
2 files changed, 36 insertions(+), 29 deletions(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 40985a378f1..d91529f71c9 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -130,9 +130,16 @@ finna.videoPlayer = (() => {
.replace('%%consentCategories%%', element.dataset.consentTitle)
.replace('%%serviceBaseUrl%%', serviceBase.hostname);
}
- VuFind.lightbox.render(wrapper.outerHTML);
- overrideModalClass('finna-consent-modal');
- const ccPreferences = document.getElementById('modal').querySelector('.embedded-content-actions button');
+ let consentHolder;
+ if (element.dataset.inline) {
+ consentHolder = document.getElementById('inline-video');
+ consentHolder.replaceChildren(wrapper);
+ } else {
+ VuFind.lightbox.render(wrapper.outerHTML);
+ overrideModalClass('finna-consent-modal');
+ consentHolder = document.getElementById('modal');
+ }
+ const ccPreferences = consentHolder.querySelector('.embedded-content-actions button');
if (ccPreferences) {
// Set cookie consent preferences event after the modal has been initialized as the lightbox handles elements
// as a string, so it loses all the events applied before rendering
@@ -147,40 +154,25 @@ finna.videoPlayer = (() => {
/**
* Sets the video elements click event.
* @param {HTMLElement} element Element which displays the video popup
- * @param {Function} onClick Function to run when clicking the button.
*/
- function setElementClickEvent(element, onClick)
+ function setIFrameStateFromConsent(element)
{
if (VuFind.cookie.isServiceAllowed(element.dataset.consent)) {
element.addEventListener('click', () => {
document.querySelectorAll('.vc-finna-video-button').forEach(b => b.classList.remove('active-video'));
- onClick(element);
+ element.classList.add('active-video');
+ onIFrameOpen(element);
});
if (element.classList.contains('active-video')) {
- element.click();
+ onIFrameOpen(element);
}
+ return;
} else {
// We should display a consent approval here
element.addEventListener('click', () => { displayConsentWindow(element); });
- }
- }
-
- /**
- * Function to check for consent config initialization.
- * @param {HTMLElement} element Element to check consent for
- * @param {Function} onClick Function to run when clicking the button.
- */
- function waitConsentInitialized(element, onClick)
- {
- const consentInitialized = VuFind.cookie.getConsentConfig();
-
- // If consent configuration has not been initialized, wait for it
- if (!consentInitialized) {
- VuFind.listen('cookie-consent-initialized', () => {
- setElementClickEvent(element, onClick);
- });
- } else {
- setElementClickEvent(element, onClick);
+ if (element.dataset.inline && element.classList.contains('active-video')) {
+ displayConsentWindow(element);
+ }
}
}
@@ -196,7 +188,16 @@ finna.videoPlayer = (() => {
if (!element || element.classList.contains('initialized')) {
return;
}
- waitConsentInitialized(element, onIFrameOpen);
+
+ const consentInitialized = VuFind.cookie.getConsentConfig();
+ // If consent configuration has not been initialized, wait for it
+ if (!consentInitialized) {
+ VuFind.listen('cookie-consent-initialized', () => {
+ setIFrameStateFromConsent(element);
+ });
+ } else {
+ setIFrameStateFromConsent(element);
+ }
}
/**
@@ -214,7 +215,13 @@ finna.videoPlayer = (() => {
}
finna.scriptLoader.load(requiredVideoScripts, () => {
finna.scriptLoader.load(dependantVideoScripts, () => {
- waitConsentInitialized(element, onVideoOpen);
+ element.addEventListener('click', () => {
+ document.querySelectorAll('.vc-finna-video-button').forEach(b => b.classList.remove('active-video'));
+ onVideoOpen(element);
+ });
+ if (element.classList.contains('active-video')) {
+ element.click();
+ }
});
});
}
diff --git a/themes/finna2/templates/_ui/components/finna-video-button.phtml b/themes/finna2/templates/_ui/components/finna-video-button.phtml
index 5ddd3d1e4b8..67847d61361 100644
--- a/themes/finna2/templates/_ui/components/finna-video-button.phtml
+++ b/themes/finna2/templates/_ui/components/finna-video-button.phtml
@@ -28,7 +28,7 @@
'data-consent-title' => $this->translate($this->cookieConsent()->getCategoryTitleForService($consent)),
'aria-haspopup' => true,
];
- $buttonAttributes['class'] .= ' ' . $this->_componentClass;
+
if ($index === 0 && $inlineVideoPlayer) {
$buttonAttributes['class'] .= ' active-video';
}
From ac7f11bebf6a3b408423dbe8576a486089a9cea9 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Wed, 8 Oct 2025 10:43:42 +0300
Subject: [PATCH 21/29] Update themes/finna2/js/finna-video-player.js
Co-authored-by: Ere Maijala
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index d91529f71c9..0ad8b03e01b 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -10,7 +10,7 @@ finna.videoPlayer = (() => {
};
/**
- * Scritps which are dependant of the videojs script
+ * Scripts that depend on the videojs script
* @member {object} dependantVideoScripts
*/
const dependantVideoScripts = {
From bc3c6f4b13c59a6dde433182f2ab46ffb9854c30 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Wed, 8 Oct 2025 10:43:55 +0300
Subject: [PATCH 22/29] Update themes/finna2/js/finna-video-player.js
Co-authored-by: Ere Maijala
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 0ad8b03e01b..ab3a991ae2c 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -11,7 +11,7 @@ finna.videoPlayer = (() => {
/**
* Scripts that depend on the videojs script
- * @member {object} dependantVideoScripts
+ * @member {object} dependentVideoScripts
*/
const dependantVideoScripts = {
'video-popup': 'finna-video-popup.js',
From 50fabb3ee6e634e22b9582e435be80f7917f1161 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Wed, 8 Oct 2025 10:44:05 +0300
Subject: [PATCH 23/29] Update themes/finna2/js/finna-video-player.js
Co-authored-by: Ere Maijala
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index ab3a991ae2c..8804da8b131 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -13,7 +13,7 @@ finna.videoPlayer = (() => {
* Scripts that depend on the videojs script
* @member {object} dependentVideoScripts
*/
- const dependantVideoScripts = {
+ const dependentVideoScripts = {
'video-popup': 'finna-video-popup.js',
'videojs-hotkeys': 'vendor/videojs.hotkeys.min.js',
'videojs-quality': 'vendor/videojs-contrib-quality-levels.js',
From 3f138d0bb48707aa0534e4fe3b9af3e524790da1 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Wed, 8 Oct 2025 10:44:22 +0300
Subject: [PATCH 24/29] Apply suggestion from @EreMaijala
Co-authored-by: Ere Maijala
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 8804da8b131..8335a120d94 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -105,7 +105,7 @@ finna.videoPlayer = (() => {
}
/**
- * Display a consent window warning for the user.
+ * Display a cookie consent window warning for the user.
* @param {HTMLElement} element The element which was clicked
*/
function displayConsentWindow(element)
From 63d76e252f1900b1e045acfc46b086fe97da0a0e Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Wed, 8 Oct 2025 10:44:37 +0300
Subject: [PATCH 25/29] Apply suggestion from @EreMaijala
Co-authored-by: Ere Maijala
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index 8335a120d94..f6ad1dd3b88 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -168,7 +168,7 @@ finna.videoPlayer = (() => {
}
return;
} else {
- // We should display a consent approval here
+ // We should display a consent information instead of the video
element.addEventListener('click', () => { displayConsentWindow(element); });
if (element.dataset.inline && element.classList.contains('active-video')) {
displayConsentWindow(element);
From 1fb3a652631b2d30d794f71e994e5e74cb6ede5e Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Fri, 17 Oct 2025 16:06:35 +0300
Subject: [PATCH 26/29] Added comments about inline viewer and active, moved
truncate and ucfirst out from button
---
themes/finna2/scss/finna/video-player.scss | 18 +++++++++-------
.../_ui/components/finna-video-button.phtml | 21 +++++++++++--------
2 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss
index a23e8efc50b..79b3b386a58 100644
--- a/themes/finna2/scss/finna/video-player.scss
+++ b/themes/finna2/scss/finna/video-player.scss
@@ -32,11 +32,13 @@
// Template for using proper styles, when appending video viewer inside an element.
%iframe-centering-template {
height: $inline-video-player-desktop-height;
- @media (max-width: $screen-md-max) {
- height: $inline-video-player-tablet-height;
- }
- @media (max-width: $screen-sm-min) {
- height: $inline-video-player-mobile-height;
+ @media screen {
+ @include media-breakpoint-down(md) {
+ height: $inline-video-player-tablet-height;
+ }
+ @include media-breakpoint-down(sm) {
+ height: $inline-video-player-mobile-height;
+ }
}
display: flex;
align-items: center;
@@ -141,9 +143,9 @@
padding: 0;
}
-.finna-video-modal,
-.finna-iframe-modal {
- display: flex !important;
+#modal.finna-video-modal,
+#modal.finna-iframe-modal {
+ display: flex;
align-items: center;
justify-content: center;
.modal-dialog {
diff --git a/themes/finna2/templates/_ui/components/finna-video-button.phtml b/themes/finna2/templates/_ui/components/finna-video-button.phtml
index 67847d61361..95fa0a360c1 100644
--- a/themes/finna2/templates/_ui/components/finna-video-button.phtml
+++ b/themes/finna2/templates/_ui/components/finna-video-button.phtml
@@ -1,16 +1,17 @@
inline ?? false;
$index = $this->index ?? 0;
$description = $this->description ?? $this->translate('format_Video');
@@ -29,7 +30,9 @@
'aria-haspopup' => true,
];
- if ($index === 0 && $inlineVideoPlayer) {
+ // If inline video player is user and current video index is 0, this video button will be clicked to initialize the player.
+ // This will not cause the video to start playing.
+ if ($inlineVideoPlayer && $index === 0) {
$buttonAttributes['class'] .= ' active-video';
}
// Generate unique selector for the button
@@ -37,7 +40,7 @@
?>
htmlAttributes($buttonAttributes);?> data-video-sources="=htmlspecialchars(json_encode($videoSources), ENT_QUOTES, 'UTF-8');?>">
=$this->icon('video-play', 'video-play-icon');?>
- =$this->escapeHtml($this->truncate(ucfirst($description), 30))?>
+ =$this->escapeHtml($description)?>
Date: Fri, 17 Oct 2025 16:07:45 +0300
Subject: [PATCH 27/29] Adjusted call to scripts
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index f6ad1dd3b88..d88a2a86a30 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -214,7 +214,7 @@ finna.videoPlayer = (() => {
return;
}
finna.scriptLoader.load(requiredVideoScripts, () => {
- finna.scriptLoader.load(dependantVideoScripts, () => {
+ finna.scriptLoader.load(dependentVideoScripts, () => {
element.addEventListener('click', () => {
document.querySelectorAll('.vc-finna-video-button').forEach(b => b.classList.remove('active-video'));
onVideoOpen(element);
From ff80b7ab94274b2717cdd72beba57ae26ed5e7ea Mon Sep 17 00:00:00 2001
From: Juha Luoma
Date: Fri, 17 Oct 2025 16:30:10 +0300
Subject: [PATCH 28/29] height from viewport
---
themes/finna2/js/finna-video-player.js | 13 +++++++++----
themes/finna2/scss/finna/video-player.scss | 11 ++++-------
themes/finna2/scss/global/variables.scss | 4 ++--
3 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index d88a2a86a30..aa122c38072 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -29,10 +29,15 @@ finna.videoPlayer = (() => {
{
const container = document.getElementById('modal');
if (container) {
- container.classList.add(className);
- VuFind.listen('lightbox.closed', () => {
- container.classList.remove(className);
- }, {once: true});
+ const dialog = container.querySelector('.modal-dialog');
+ if (dialog) {
+ container.classList.add(className);
+ dialog.classList.add('modal-dialog-centered');
+ VuFind.listen('lightbox.closed', () => {
+ dialog.classList.remove('modal-dialog-centered');
+ container.classList.remove(className);
+ }, {once: true});
+ }
}
}
diff --git a/themes/finna2/scss/finna/video-player.scss b/themes/finna2/scss/finna/video-player.scss
index 79b3b386a58..748dec50007 100644
--- a/themes/finna2/scss/finna/video-player.scss
+++ b/themes/finna2/scss/finna/video-player.scss
@@ -31,15 +31,16 @@
// Template for using proper styles, when appending video viewer inside an element.
%iframe-centering-template {
- height: $inline-video-player-desktop-height;
+ max-height: $inline-video-player-desktop-height;
@media screen {
@include media-breakpoint-down(md) {
- height: $inline-video-player-tablet-height;
+ max-height: $inline-video-player-tablet-height;
}
@include media-breakpoint-down(sm) {
- height: $inline-video-player-mobile-height;
+ max-height: $inline-video-player-mobile-height;
}
}
+ height: 100vh;
display: flex;
align-items: center;
justify-content: center;
@@ -145,11 +146,7 @@
#modal.finna-video-modal,
#modal.finna-iframe-modal {
- display: flex;
- align-items: center;
- justify-content: center;
.modal-dialog {
- flex: 1 1 auto;
max-width: 1200px;
.modal-content {
width: 100%;
diff --git a/themes/finna2/scss/global/variables.scss b/themes/finna2/scss/global/variables.scss
index c5c666e16c8..275d7367287 100644
--- a/themes/finna2/scss/global/variables.scss
+++ b/themes/finna2/scss/global/variables.scss
@@ -347,6 +347,6 @@ $item-status-color-unavailable: $danger !default;
$item-status-color-unknown: $warning !default;
$item-status-color-uncertain: $warning !default;
-$inline-video-player-desktop-height: 500px;
-$inline-video-player-tablet-height: 400px;
+$inline-video-player-desktop-height: 600px;
+$inline-video-player-tablet-height: 500px;
$inline-video-player-mobile-height: 200px;
From a954eb4dd9a815bc3ab69975f8afb737206fe2c5 Mon Sep 17 00:00:00 2001
From: Juha Luoma <33253757+LuomaJuha@users.noreply.github.com>
Date: Thu, 23 Oct 2025 09:26:36 +0300
Subject: [PATCH 29/29] Apply suggestion from @EreMaijala
Co-authored-by: Ere Maijala
---
themes/finna2/js/finna-video-player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/finna2/js/finna-video-player.js b/themes/finna2/js/finna-video-player.js
index aa122c38072..c8cd004917f 100644
--- a/themes/finna2/js/finna-video-player.js
+++ b/themes/finna2/js/finna-video-player.js
@@ -42,7 +42,7 @@ finna.videoPlayer = (() => {
}
/**
- * Display warning icons when using inline video setting set to true.
+ * Display warning icons when inline video setting is enabled.
* @param {HTMLElement} element Element clicked
*/
function showWarningIcons(element)