Loading editor/widgets/slider/slider.cpp +51 −10 Original line number Diff line number Diff line Loading @@ -559,6 +559,31 @@ extern "C" { counter = counter->nextElement(); } // Items Per View -- only the plain "slide" transition supports // multiple side-by-side items: "fade" stacks every slide in one // grid cell and "carousel" always centers exactly one active // slide with peeking neighbors, neither of which has a // meaningful notion of "N items visible at once". int itemsPerViewN = 1; try { itemsPerViewN = std::stoi(itemsPerView); } catch (...) { itemsPerViewN = 1; } if (itemsPerViewN < 1) itemsPerViewN = 1; int mobileItemsPerViewN = itemsPerViewN; if (!m_itemsPerView.empty()) { try { mobileItemsPerViewN = std::stoi(m_itemsPerView); } catch (...) { mobileItemsPerViewN = itemsPerViewN; } if (mobileItemsPerViewN < 1) mobileItemsPerViewN = 1; } bool multiItem = (transition != "fade" && transition != "carousel" && itemsPerViewN > 1); // Dots/prev/next always page by whole groups of itemsPerViewN // slides (the desktop setting) -- the mobile override only // changes how many of those slides are visually shown per // page, not the paging granularity itself, since the page // count is baked into the server-rendered dots and can't // change at the mobile breakpoint. int pageCount = multiItem ? (slideCount + itemsPerViewN - 1) / itemsPerViewN : slideCount; if (pageCount < 1) pageCount = 1; auto gradientCss = [](const std::string &type, const std::string &angle, const std::string &c1, const std::string &c2) -> std::string { if (type == "linear") { std::string ang = angle.empty() ? "135deg" : angle; Loading Loading @@ -629,7 +654,13 @@ extern "C" { " overflow-x: hidden; -webkit-user-drag: none; touch-action: pan-y; user-select: none; box-sizing: border-box;" " transition: transform 0.4s ease, opacity 0.4s ease; opacity: 0.6; transform: scale(0.85);"; } else { slideStyle = "width: 100%; flex: 0 0 100%; height: 100%; position: relative;" std::string slideWidthPct = "100%"; if (multiItem) { std::ostringstream pctStream; pctStream << (100.0 / itemsPerViewN); slideWidthPct = pctStream.str() + "%"; } slideStyle = "width: " + slideWidthPct + "; flex: 0 0 " + slideWidthPct + "; height: 100%; position: relative;" " overflow-x: hidden; -webkit-user-drag: none; touch-action: pan-y; user-select: none; box-sizing: border-box;"; } slide.setAttribute("style", slideStyle.c_str()); Loading @@ -642,7 +673,7 @@ extern "C" { wrapper.appendChild(track); // --- Navigation Arrows --- if (showArrows && slideCount > 1) { if (showArrows && pageCount > 1) { std::string arrowBase = "position: absolute; top: 50%; transform: translateY(-50%);" " background: rgba(0,0,0,0.5); color: white; border: none; padding: 12px 16px;" " font-size: 18px; cursor: pointer; z-index: 10; border-radius: 4px;"; Loading @@ -665,14 +696,14 @@ extern "C" { } // --- Dot Navigation --- if (showDots && slideCount > 1) { if (showDots && pageCount > 1) { libhtmlpp::HtmlElement dots("div"); dots.setAttribute("class", (cls + "-dots").c_str()); dots.setAttribute("style", "position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);" " display: flex; gap: 8px; z-index: 10;"); for (int i = 0; i < slideCount; i++) { for (int i = 0; i < pageCount; i++) { libhtmlpp::HtmlElement dot("button"); dot.setAttribute("type", "button"); dot.setAttribute("style", Loading @@ -680,7 +711,7 @@ extern "C" { " border: 2px solid white; background: rgba(255,255,255,0.4);" " cursor: pointer; padding: 0;"); dot.setAttribute("aria-label", (std::string("Slide ") + std::to_string(i + 1) + " von " + std::to_string(slideCount)).c_str()); (std::string("Slide ") + std::to_string(i + 1) + " von " + std::to_string(pageCount)).c_str()); dot.setAttribute("onclick", (std::string("(function(){var s=document.getElementById('") + sliderId + "');s._blogiSlider&&s._blogiSlider.goTo(" + std::to_string(i) + ")})()").c_str()); Loading @@ -700,7 +731,7 @@ extern "C" { js << "if(!el)return;"; js << "var slides=el.querySelectorAll('." << cls << "-slide');"; js << "var dots=el.querySelectorAll('." << cls << "-dots button');"; js << "var n=slides.length,cur=0,timer=null;"; js << "var n=slides.length,units=" << pageCount << ",cur=0,timer=null;"; if (transition == "fade") { // Fade mode uses CSS grid stacking — no size override needed js << "function setSizes(){}"; Loading @@ -708,12 +739,22 @@ extern "C" { // Slides are a fraction of the track width so neighbors peek in js << "function setSizes(){var w=el.offsetWidth,sw=w*0.7;for(var j=0;j<n;j++){slides[j].style.width=sw+'px';slides[j].style.flex='0 0 '+sw+'px';}}"; } else { // Set slide widths in pixels for reliable mobile rendering js << "function setSizes(){var w=el.offsetWidth;for(var j=0;j<n;j++){slides[j].style.width=w+'px';slides[j].style.flex='0 0 '+w+'px';}}"; // Items Per View: each slide is a 1/perView fraction of the // container, where perView switches between the desktop and // mobile setting at the same 768px breakpoint the rest of // this widget's responsive CSS uses. Paging always advances // by whole pages of the DESKTOP perView count (see `units` // above, computed server-side) -- translating the track by // one full container width per page is correct regardless // of perView, since perView slides of width w/perView // together always span exactly w. js << "var desktopPV=" << itemsPerViewN << ",mobilePV=" << mobileItemsPerViewN << ";"; js << "function currentPV(){return window.innerWidth<=768?mobilePV:desktopPV;}"; js << "function setSizes(){var w=el.offsetWidth,pv=currentPV(),sw=w/pv;for(var j=0;j<n;j++){slides[j].style.width=sw+'px';slides[j].style.flex='0 0 '+sw+'px';}}"; } js << "setSizes();"; js << "function show(i){"; js << " cur=((i%n)+n)%n;"; js << " cur=((i%units)+units)%units;"; if (transition == "fade") { js << " for(var j=0;j<n;j++){slides[j].style.opacity=j===cur?'1':'0';}"; } else if (transition == "carousel") { Loading @@ -739,7 +780,7 @@ extern "C" { js << "}"; js << "el.addEventListener('click',function(){stopAuto();});"; js << "show(0);"; if (autoplay && slideCount > 1) { if (autoplay && pageCount > 1) { js << "startAuto();"; } // Recalculate sizes and position on resize/orientation change Loading editor/widgets/slider/translations.h +2 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ namespace blogi { namespace webedit { namespace slider_i18n { {"Height", "Höhe"}, {"Hidden", "Ausgeblendet"}, {"Interval (ms)", "Intervall (ms)"}, {"Items Per View (Slide mode only)", "Elemente nebeneinander (nur Modus \"Slide\")"}, {"Items Per View (Mobile, Slide mode only)", "Elemente nebeneinander (Mobil, nur Modus \"Slide\")"}, {"Mobile Background Color", "Hintergrundfarbe (Mobil)"}, {"Mobile Background Gradient", "Hintergrund-Farbverlauf (Mobil)"}, {"Mobile Gradient Angle", "Verlaufswinkel (Mobil)"}, Loading Loading
editor/widgets/slider/slider.cpp +51 −10 Original line number Diff line number Diff line Loading @@ -559,6 +559,31 @@ extern "C" { counter = counter->nextElement(); } // Items Per View -- only the plain "slide" transition supports // multiple side-by-side items: "fade" stacks every slide in one // grid cell and "carousel" always centers exactly one active // slide with peeking neighbors, neither of which has a // meaningful notion of "N items visible at once". int itemsPerViewN = 1; try { itemsPerViewN = std::stoi(itemsPerView); } catch (...) { itemsPerViewN = 1; } if (itemsPerViewN < 1) itemsPerViewN = 1; int mobileItemsPerViewN = itemsPerViewN; if (!m_itemsPerView.empty()) { try { mobileItemsPerViewN = std::stoi(m_itemsPerView); } catch (...) { mobileItemsPerViewN = itemsPerViewN; } if (mobileItemsPerViewN < 1) mobileItemsPerViewN = 1; } bool multiItem = (transition != "fade" && transition != "carousel" && itemsPerViewN > 1); // Dots/prev/next always page by whole groups of itemsPerViewN // slides (the desktop setting) -- the mobile override only // changes how many of those slides are visually shown per // page, not the paging granularity itself, since the page // count is baked into the server-rendered dots and can't // change at the mobile breakpoint. int pageCount = multiItem ? (slideCount + itemsPerViewN - 1) / itemsPerViewN : slideCount; if (pageCount < 1) pageCount = 1; auto gradientCss = [](const std::string &type, const std::string &angle, const std::string &c1, const std::string &c2) -> std::string { if (type == "linear") { std::string ang = angle.empty() ? "135deg" : angle; Loading Loading @@ -629,7 +654,13 @@ extern "C" { " overflow-x: hidden; -webkit-user-drag: none; touch-action: pan-y; user-select: none; box-sizing: border-box;" " transition: transform 0.4s ease, opacity 0.4s ease; opacity: 0.6; transform: scale(0.85);"; } else { slideStyle = "width: 100%; flex: 0 0 100%; height: 100%; position: relative;" std::string slideWidthPct = "100%"; if (multiItem) { std::ostringstream pctStream; pctStream << (100.0 / itemsPerViewN); slideWidthPct = pctStream.str() + "%"; } slideStyle = "width: " + slideWidthPct + "; flex: 0 0 " + slideWidthPct + "; height: 100%; position: relative;" " overflow-x: hidden; -webkit-user-drag: none; touch-action: pan-y; user-select: none; box-sizing: border-box;"; } slide.setAttribute("style", slideStyle.c_str()); Loading @@ -642,7 +673,7 @@ extern "C" { wrapper.appendChild(track); // --- Navigation Arrows --- if (showArrows && slideCount > 1) { if (showArrows && pageCount > 1) { std::string arrowBase = "position: absolute; top: 50%; transform: translateY(-50%);" " background: rgba(0,0,0,0.5); color: white; border: none; padding: 12px 16px;" " font-size: 18px; cursor: pointer; z-index: 10; border-radius: 4px;"; Loading @@ -665,14 +696,14 @@ extern "C" { } // --- Dot Navigation --- if (showDots && slideCount > 1) { if (showDots && pageCount > 1) { libhtmlpp::HtmlElement dots("div"); dots.setAttribute("class", (cls + "-dots").c_str()); dots.setAttribute("style", "position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%);" " display: flex; gap: 8px; z-index: 10;"); for (int i = 0; i < slideCount; i++) { for (int i = 0; i < pageCount; i++) { libhtmlpp::HtmlElement dot("button"); dot.setAttribute("type", "button"); dot.setAttribute("style", Loading @@ -680,7 +711,7 @@ extern "C" { " border: 2px solid white; background: rgba(255,255,255,0.4);" " cursor: pointer; padding: 0;"); dot.setAttribute("aria-label", (std::string("Slide ") + std::to_string(i + 1) + " von " + std::to_string(slideCount)).c_str()); (std::string("Slide ") + std::to_string(i + 1) + " von " + std::to_string(pageCount)).c_str()); dot.setAttribute("onclick", (std::string("(function(){var s=document.getElementById('") + sliderId + "');s._blogiSlider&&s._blogiSlider.goTo(" + std::to_string(i) + ")})()").c_str()); Loading @@ -700,7 +731,7 @@ extern "C" { js << "if(!el)return;"; js << "var slides=el.querySelectorAll('." << cls << "-slide');"; js << "var dots=el.querySelectorAll('." << cls << "-dots button');"; js << "var n=slides.length,cur=0,timer=null;"; js << "var n=slides.length,units=" << pageCount << ",cur=0,timer=null;"; if (transition == "fade") { // Fade mode uses CSS grid stacking — no size override needed js << "function setSizes(){}"; Loading @@ -708,12 +739,22 @@ extern "C" { // Slides are a fraction of the track width so neighbors peek in js << "function setSizes(){var w=el.offsetWidth,sw=w*0.7;for(var j=0;j<n;j++){slides[j].style.width=sw+'px';slides[j].style.flex='0 0 '+sw+'px';}}"; } else { // Set slide widths in pixels for reliable mobile rendering js << "function setSizes(){var w=el.offsetWidth;for(var j=0;j<n;j++){slides[j].style.width=w+'px';slides[j].style.flex='0 0 '+w+'px';}}"; // Items Per View: each slide is a 1/perView fraction of the // container, where perView switches between the desktop and // mobile setting at the same 768px breakpoint the rest of // this widget's responsive CSS uses. Paging always advances // by whole pages of the DESKTOP perView count (see `units` // above, computed server-side) -- translating the track by // one full container width per page is correct regardless // of perView, since perView slides of width w/perView // together always span exactly w. js << "var desktopPV=" << itemsPerViewN << ",mobilePV=" << mobileItemsPerViewN << ";"; js << "function currentPV(){return window.innerWidth<=768?mobilePV:desktopPV;}"; js << "function setSizes(){var w=el.offsetWidth,pv=currentPV(),sw=w/pv;for(var j=0;j<n;j++){slides[j].style.width=sw+'px';slides[j].style.flex='0 0 '+sw+'px';}}"; } js << "setSizes();"; js << "function show(i){"; js << " cur=((i%n)+n)%n;"; js << " cur=((i%units)+units)%units;"; if (transition == "fade") { js << " for(var j=0;j<n;j++){slides[j].style.opacity=j===cur?'1':'0';}"; } else if (transition == "carousel") { Loading @@ -739,7 +780,7 @@ extern "C" { js << "}"; js << "el.addEventListener('click',function(){stopAuto();});"; js << "show(0);"; if (autoplay && slideCount > 1) { if (autoplay && pageCount > 1) { js << "startAuto();"; } // Recalculate sizes and position on resize/orientation change Loading
editor/widgets/slider/translations.h +2 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,8 @@ namespace blogi { namespace webedit { namespace slider_i18n { {"Height", "Höhe"}, {"Hidden", "Ausgeblendet"}, {"Interval (ms)", "Intervall (ms)"}, {"Items Per View (Slide mode only)", "Elemente nebeneinander (nur Modus \"Slide\")"}, {"Items Per View (Mobile, Slide mode only)", "Elemente nebeneinander (Mobil, nur Modus \"Slide\")"}, {"Mobile Background Color", "Hintergrundfarbe (Mobil)"}, {"Mobile Background Gradient", "Hintergrund-Farbverlauf (Mobil)"}, {"Mobile Gradient Angle", "Verlaufswinkel (Mobil)"}, Loading