diff --git a/local/config/vufind/rss.ini.sample b/local/config/vufind/rss.ini.sample index 074c8087576..bd491c24c51 100644 --- a/local/config/vufind/rss.ini.sample +++ b/local/config/vufind/rss.ini.sample @@ -1,7 +1,7 @@ ;; Mandatory settings: ; active = [0|1] -; type = "[list|carousel|carousel-vertical|grid]" +; type = "[list|carousel|carousel-vertical|grid|slider]" ; url[language code|*] = "" Feed URL, for example: ; Absolute URL: http://blogs.helsinki.fi/scriptaselecta/feed/ @@ -94,7 +94,12 @@ ; autoplay = [false|milliseconds] Autoplay slide change interval (autoplay is off by default). ; dots = [0|1] Display current slide indicator (default 1). +;; Optional slider settings: +; This type only displays one item per page, but otherwise uses carousel settings. +; backgroundColor = "#f7f7f7" Background colour of the slider (default none). +; stackedHeight = [pixels] The height of the slider when it uses the stacked (vertical) layout, e.g. on mobile device (sets regular height value by default). +; imagePlacement = [left|right] The side of which the image is on the slider when it uses the horizontal layout (default left). ; Number of visible items in the carousel @@ -117,7 +122,7 @@ ; content[link] = 0 ; content[image] = 0 ; content[date] = 0 - +; content[description] = 0 [carousel] type = "carousel" @@ -176,3 +181,13 @@ content[text] = 0 content[image] = 0 linkTarget = "_blank" +[slider] +type = "slider" +active = 1 +url[*] = "https://musasto.wordpress.com/feed/" +title = "rss" +height = 450 +stackedHeight = 600 +backgroundColor = "#f7f7f7" +imagePlacement = "left" + diff --git a/module/Finna/src/Finna/AjaxHandler/AddToList.php b/module/Finna/src/Finna/AjaxHandler/AddToList.php index 1ff50a3ffa9..c2d6318f2cb 100644 --- a/module/Finna/src/Finna/AjaxHandler/AddToList.php +++ b/module/Finna/src/Finna/AjaxHandler/AddToList.php @@ -139,6 +139,7 @@ public function handleRequest(Params $params) ); } $listId = $listParams['listId']; + $currentListId = $listParams['currentListId']; $ids = (array)$listParams['ids']; $list = $this->userList->getExisting($listId); @@ -154,7 +155,9 @@ public function handleRequest(Params $params) $recId = $id[1]; try { $driver = $this->recordLoader->load($recId, $source, true); - $this->favorites->save(['list' => $listId], $this->user, $driver); + $notes = $driver->getListNotes($currentListId ?: null, $this->user->id); + $notes = implode(PHP_EOL, $notes); + $this->favorites->save(['list' => $listId, 'notes' => $notes], $this->user, $driver); } catch (\Exception $e) { return $this->formatResponse( $this->translate('Failed'), diff --git a/module/Finna/src/Finna/AjaxHandler/FeedTrait.php b/module/Finna/src/Finna/AjaxHandler/FeedTrait.php index 0f667971a8d..5af568b2e97 100644 --- a/module/Finna/src/Finna/AjaxHandler/FeedTrait.php +++ b/module/Finna/src/Finna/AjaxHandler/FeedTrait.php @@ -122,7 +122,8 @@ protected function formatFeed( $feed['visualItems'] = $config->visualItems; } - $template = str_contains($type, 'carousel') ? 'carousel' : $type; + $isCarouselStyle = str_contains($type, 'carousel') || $type === 'slider'; + $template = $isCarouselStyle ? 'carousel' : $type; $html = $viewRenderer->partial("ajax/feed-$template.phtml", $feed); $settings = [ @@ -132,8 +133,17 @@ protected function formatFeed( if (isset($config->height)) { $settings['height'] = $config->height; } + if (isset($config->stackedHeight)) { + $settings['stackedHeight'] = $config->stackedHeight; + } + if (isset($config->backgroundColor)) { + $settings['backgroundColor'] = $config->backgroundColor; + } + if (isset($config->imagePlacement)) { + $settings['imagePlacement'] = $config->imagePlacement; + } - if ('carousel' === $type || 'carousel-vertical' === $type) { + if ($isCarouselStyle) { $settings['images'] = $images; $settings['autoplay'] = $config->autoplay ?? false; @@ -144,16 +154,19 @@ protected function formatFeed( $breakPoints = [ 'desktop' => 4, 'desktop-small' => 3, 'tablet' => 2, 'mobile' => 1, ]; - - foreach ($breakPoints as $breakPoint => $default) { - $settings['slidesToShow'][$breakPoint] - = isset($config->itemsPerPage[$breakPoint]) - ? (int)$config->itemsPerPage[$breakPoint] : $default; - - $settings['scrolledItems'][$breakPoint] - = isset($config->scrolledItems[$breakPoint]) - ? (int)$config->scrolledItems[$breakPoint] - : $settings['slidesToShow'][$breakPoint]; + if ('slider' === $type) { + $settings['slidesToShow']['desktop'] = $settings['scrolledItems']['desktop'] = 1; + } else { + foreach ($breakPoints as $breakPoint => $default) { + $settings['slidesToShow'][$breakPoint] + = isset($config->itemsPerPage[$breakPoint]) + ? (int)$config->itemsPerPage[$breakPoint] : $default; + + $settings['scrolledItems'][$breakPoint] + = isset($config->scrolledItems[$breakPoint]) + ? (int)$config->scrolledItems[$breakPoint] + : $settings['slidesToShow'][$breakPoint]; + } } if ('carousel' === $type) { diff --git a/module/Finna/src/Finna/Feed/Feed.php b/module/Finna/src/Finna/Feed/Feed.php index 3c1f009ea5d..22f13d58f31 100644 --- a/module/Finna/src/Finna/Feed/Feed.php +++ b/module/Finna/src/Finna/Feed/Feed.php @@ -464,6 +464,7 @@ public function parseFeed($channel, $config, $id = null) 'id' => 'getId', 'title' => 'getTitle', 'text' => 'getContent', + 'description' => 'getDescription', 'image' => 'getEnclosure', 'link' => 'getLink', 'date' => 'getDateCreated', diff --git a/themes/finna2/js/finna-carousel-manager.js b/themes/finna2/js/finna-carousel-manager.js index d630df9b438..0600883631f 100644 --- a/themes/finna2/js/finna-carousel-manager.js +++ b/themes/finna2/js/finna-carousel-manager.js @@ -38,6 +38,9 @@ finna.carouselManager = (() => { return {autoplay: false}; }, height: (value) => { return {height: parseInt(value)}; }, + stackedHeight: (value) => { return {stackedHeight: parseInt(value)}; }, + backgroundColor: (value) => { return {backgroundColor: value}; }, + imagePlacement: (value) => { return {imagePlacement: value}; }, width: (value) => { return {fixedWidth: parseInt(value)}; }, omitEnd: 'omitEnd', pagination: 'pagination', @@ -86,6 +89,7 @@ finna.carouselManager = (() => { direction = 'ttb'; break; case 'carousel': + case 'slider': direction = 'ltr'; classes.prev += 'left'; classes.next += 'right'; diff --git a/themes/finna2/js/finna-feed-element.js b/themes/finna2/js/finna-feed-element.js index c4332c47d41..cb581eb386b 100644 --- a/themes/finna2/js/finna-feed-element.js +++ b/themes/finna2/js/finna-feed-element.js @@ -136,7 +136,7 @@ class FinnaFeedElement extends HTMLElement { var settings = Object.assign({}, jsonResponse.data.settings); settings.height = settings.height || 300; const type = settings.type; - const carousel = ['carousel', 'carousel-vertical'].includes(type); + const carousel = ['carousel', 'carousel-vertical', 'slider'].includes(type); if (carousel) { const hasContent = holder.querySelector('.carousel-feed > li, .carousel-feed > div'); if (!hasContent) { @@ -151,11 +151,13 @@ class FinnaFeedElement extends HTMLElement { } const vertical = 'carousel-vertical' === settings.type; + const slider = 'slider' === settings.type; this.adjustArrowButtons(vertical); settings.vertical = vertical; this.splide = finna.carouselManager.createCarousel(this, settings); var titleBottom = typeof settings.titlePosition !== 'undefined' && settings.titlePosition === 'bottom'; - if (!vertical) { + if (!vertical && !slider) { + holder.classList.add('carousel'); if (titleBottom) { holder.setTitleBottom(settings); holder.querySelectorAll('.carousel-hover-title').forEach(el => { @@ -177,9 +179,31 @@ class FinnaFeedElement extends HTMLElement { }); } } + if (slider) { + holder.classList.add('carousel-slider'); + if (settings.backgroundColor) { + holder.classList.add('slider-with-background'); + holder.style.setProperty('--background-color', settings.backgroundColor); + } + if (settings.imagePlacement && settings.imagePlacement === 'right') { + holder.classList.add('image-right'); + } + if (settings.stackedHeight) { + holder.style.setProperty('--height', `${settings.stackedHeight}px`); + } else { + holder.style.setProperty('--height', `${settings.height}px`); + } + holder.querySelectorAll('.slider-text-container').forEach(el => { + if (el.clientHeight < el.scrollHeight) { + el.classList.add('scrollable'); + } else { + el.parentElement.style.alignItems = 'center'; + } + }); + } // Text hover for touch devices - if (finna.layout.isTouchDevice() && typeof settings.linkText === 'undefined') { + if (!slider && finna.layout.isTouchDevice() && typeof settings.linkText === 'undefined') { holder.querySelectorAll('.carousel-slide-more.carousel-show').forEach(el => { if (holder.querySelector('.carousel-text:not(.no-text)') !== null) { el.classList.remove('hidden'); diff --git a/themes/finna2/js/finna-mylist.js b/themes/finna2/js/finna-mylist.js index 6c7337893af..ffff60dbe9e 100644 --- a/themes/finna2/js/finna-mylist.js +++ b/themes/finna2/js/finna-mylist.js @@ -123,7 +123,7 @@ finna.myList = (function finnaMyList() { }); } - function addResourcesToList(listId) { + function addResourcesToList(listId, currentListId = '') { toggleErrorMessage(false); var ids = []; @@ -145,7 +145,7 @@ finna.myList = (function finnaMyList() { type: 'POST', dataType: 'json', url: VuFind.path + '/AJAX/JSON?method=addToList', - data: {params: {'listId': listId, 'source': 'Solr', 'ids': ids}} + data: {params: {'listId': listId, 'currentListId': currentListId, 'source': 'Solr', 'ids': ids}} }) .done(function onAddToListDone(/*data*/) { // Don't reload to avoid trouble with POST requests @@ -362,7 +362,7 @@ finna.myList = (function finnaMyList() { $('.mylist-functions #add-to-list').off('change').on("change", function onChangeAddToList(/*e*/) { var val = $(this).val(); if (val !== '') { - addResourcesToList(val); + addResourcesToList(val, getActiveListId()); } }); diff --git a/themes/finna2/less/finna/feed.less b/themes/finna2/less/finna/feed.less index e2ead73b15d..8916280fab9 100644 --- a/themes/finna2/less/finna/feed.less +++ b/themes/finna2/less/finna/feed.less @@ -710,7 +710,7 @@ finna-feed { } } } -finna-feed.splide--ltr { +finna-feed.carousel.splide--ltr { .splide__arrows { width: 100%; left: 0; @@ -846,6 +846,131 @@ finna-feed.splide--ltr { } } +finna-feed.carousel-slider.splide--ltr { + container-type: inline-size; + .splide__arrows { + width: 100%; + left: 0; + right: 0; + button { + &.left { + left: -24px; + transform: rotate(180deg); + } + &.right { + right: -24px; + } + } + } + --background-color: transparent; + &.slider-with-background { + &::before { + content: ''; + background-color: var(--background-color); + position: absolute; + height: 100%; + width: 100%; + } + .carousel-pagination-wrapper { + position: relative; + } + } + .carousel-header { + display: none; + } + .slider-image, .slider-text-part { + height: 100%; + padding: 10px 15px; + } + .slider-image { + display: flex; + justify-content: center; + width: 50%; + img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + flex: left; + } + } + .slider-text-part { + display: flex; + color: @text-color; + max-width: 50%; + height: 100%; + .slider-text-container { + overflow-y: auto; + overscroll-behavior-y: contain; + &.scrollable { + padding-right: 10px; + } + } + .slider-title { + color: @carousel-header-color; + } + .carousel-text { + width: 100%; + } + .text { + overflow-wrap: anywhere; + } + .slider-header { + display: block; + font-size: @font-size-h4; + font-weight: 500; + } + } +} +@container (min-width: @slider-stacked-breakpoint) { + finna-feed.carousel-slider.splide--ltr { + .slider-text-part { + .slider-title { + font-size: @font-size-h2; + } + } + &.image-right .feed-link { + display: flex; + flex-direction: row-reverse; + } + } +} +@container (max-width: @slider-stacked-breakpoint) { + finna-feed.carousel-slider.splide--ltr { + .feed-item-holder[style] { + height: var(--height) !important; + } + .feed-link { + flex-direction: column; + } + .slider-image, .slider-text-part { + width: auto; + max-width: 100%; + height: 50%; + .slider-header { + font-size: @font-size-base; + margin-top: 0; + margin-bottom: 5px; + } + .slider-title { + padding: 0; + word-break: break-all; + } + .text { + font-size: @font-size-small; + } + } + .slider-image { + padding: 5px; + img { + height: 100%; + } + } + .carousel-pagination-wrapper { + height: 40px; + } + } +} + finna-feed.splide--ttb { .splide__arrows { height: auto; diff --git a/themes/finna2/less/global/variables.less b/themes/finna2/less/global/variables.less index 73304e2fbce..20113b7e314 100644 --- a/themes/finna2/less/global/variables.less +++ b/themes/finna2/less/global/variables.less @@ -152,6 +152,9 @@ @similar-carousel-format-text-color: @text-color; @similar-carousel-format-background: #f2f2f2; +//== Breakpoint for slider layout +@slider-stacked-breakpoint: @screen-sm; + //== List feed styles @list-feed-background: @body-bg; @list-feed-padding: 10px; diff --git a/themes/finna2/templates/ajax/feed-carousel.phtml b/themes/finna2/templates/ajax/feed-carousel.phtml index 0fb3d53fccb..0339e0aa6d0 100644 --- a/themes/finna2/templates/ajax/feed-carousel.phtml +++ b/themes/finna2/templates/ajax/feed-carousel.phtml @@ -1,6 +1,7 @@ title || $this->translateTitle): ?> - + translateTitle ? $this->transEsc($this->translateTitle) : $this->escapeHtml($this->title); ?> + description)): ?>

translate($this->description) // HTML, intentionally left unescaped ?>

@@ -43,9 +44,10 @@ $linkElement['data-lightbox-title'] = $item['title']; } $linkTextEsc = $this->transEsc($this->linkText ? $this->linkText : 'To the record'); + $hasLinkText = isset($this->linkText); ?> htmlElement()->getAttributes($linkElement)?>> - partial("ajax/feed-$this->type-item.phtml", compact('item', 'linkTextEsc'))?> + partial("ajax/feed-$this->type-item.phtml", compact('item', 'linkTextEsc', 'hasLinkText', 'feedTitleEsc'))?> partial("ajax/feed-$this->type-item.phtml", compact('item', 'linkTextEsc'))?> diff --git a/themes/finna2/templates/ajax/feed-slider-item.phtml b/themes/finna2/templates/ajax/feed-slider-item.phtml new file mode 100644 index 00000000000..d6d4711cbcb --- /dev/null +++ b/themes/finna2/templates/ajax/feed-slider-item.phtml @@ -0,0 +1,29 @@ + + + + + +
+
+ +
+
+