diff --git a/lib/classes/output/sticky_footer.php b/lib/classes/output/sticky_footer.php new file mode 100644 index 00000000000..27ba5828641 --- /dev/null +++ b/lib/classes/output/sticky_footer.php @@ -0,0 +1,130 @@ +. + +namespace core\output; + +use renderable; + +/** + * Class to render a sticky footer element. + * + * Sticky footer can be rendered at any moment if the page (even inside a form) but + * it will be displayed at the bottom of the page. + * + * Important: note that pages can only display one sticky footer at once. + * + * Important: not all themes are compatible with sticky footer. If the current theme + * is not compatible it will be rendered as a standard div element. + * + * @package core + * @category output + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class sticky_footer implements named_templatable, renderable { + + /** + * @var string content of the sticky footer. + */ + protected $stickycontent = ''; + + /** + * @var string extra CSS classes. By default, elements are justified to the end. + */ + protected $stickyclasses = 'justify-content-end'; + + /** + * @var array extra HTML attributes (attribute => value). + */ + protected $attributes = []; + + /** + * Constructor. + * + * @param string $stickycontent the footer content + * @param string|null $stickyclasses extra CSS classes + * @param array $attributes extra html attributes (attribute => value) + */ + public function __construct(string $stickycontent = '', ?string $stickyclasses = null, array $attributes = []) { + $this->stickycontent = $stickycontent; + if ($stickyclasses !== null) { + $this->stickyclasses = $stickyclasses; + } + $this->attributes = $attributes; + } + + /** + * Set the footer contents. + * + * @param string $stickycontent the footer content + */ + public function set_content(string $stickycontent) { + $this->stickycontent = $stickycontent; + } + + /** + * Add extra classes to the sticky footer. + * + * @param string $stickyclasses the extra classes + */ + public function add_classes(string $stickyclasses) { + if (!empty($this->stickyclasses)) { + $this->stickyclasses .= ' '; + } + $this->stickyclasses = $stickyclasses; + } + + /** + * Add extra attributes to the sticky footer element. + * + * @param string $atribute the attribute + * @param string $value the value + */ + public function add_attribute(string $atribute, string $value) { + $this->attributes[$atribute] = $value; + } + + /** + * Export this data so it can be used as the context for a mustache template (core/inplace_editable). + * + * @param renderer_base $output typically, the renderer that's calling this function + * @return array data context for a mustache template + */ + public function export_for_template(\renderer_base $output) { + $extras = []; + foreach ($this->attributes as $attribute => $value) { + $extras[] = [ + 'attribute' => $attribute, + 'value' => $value, + ]; + } + return [ + 'stickycontent' => (string)$this->stickycontent, + 'stickyclasses' => $this->stickyclasses, + 'extras' => $extras, + ]; + } + + /** + * Get the name of the template to use for this templatable. + * + * @param \renderer_base $renderer The renderer requesting the template name + * @return string the template name + */ + public function get_template_name(\renderer_base $renderer): string { + return 'core/sticky_footer'; + } +} diff --git a/lib/templates/sticky_footer.mustache b/lib/templates/sticky_footer.mustache new file mode 100644 index 00000000000..545f139359d --- /dev/null +++ b/lib/templates/sticky_footer.mustache @@ -0,0 +1,47 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core/sticky_footer + + Displays a page sticky footer element. + + Sticky footer behaviour depends on the theme. The default template is + a regular element. + + Example context (json): + { + "stickycontent" : "Moodle", + "extras" : [ + { + "attribute" : "data-example", + "value" : "stickyfooter" + } + ], + "stickyclasses" : "extraclasses" + } +}} + diff --git a/theme/boost/amd/build/sticky-footer.min.js b/theme/boost/amd/build/sticky-footer.min.js new file mode 100644 index 00000000000..ffdb25ce739 --- /dev/null +++ b/theme/boost/amd/build/sticky-footer.min.js @@ -0,0 +1,10 @@ +define("theme_boost/sticky-footer",["exports","core/pending"],(function(_exports,_pending){var obj; +/** + * Sticky footer module. + * + * @module theme_boost/sticky-footer + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=_exports.enableStickyFooter=_exports.disableStickyFooter=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};const SELECTORS_STICKYFOOTER=".stickyfooter",SELECTORS_PAGE="#page",CLASSES_HASSTICKYFOOTER="hasstickyfooter";let initialized=!1,previousScrollPosition=0;const scrollSpy=()=>{if(document.body.clientWidth>=768)return;let scrollPosition=(()=>{const page=document.querySelector(SELECTORS_PAGE);return page?page.scrollTop:window.pageYOffset})();scrollPosition>previousScrollPosition?disableStickyFooter():enableStickyFooter(),previousScrollPosition=scrollPosition},enableStickyFooter=()=>{const pendingPromise=new _pending.default("theme_boost/sticky-footer:enabling"),footer=document.querySelector(SELECTORS_STICKYFOOTER),page=document.querySelector(SELECTORS_PAGE);footer&&page&&(document.body.classList.add(CLASSES_HASSTICKYFOOTER),page.classList.add(CLASSES_HASSTICKYFOOTER)),setTimeout((()=>pendingPromise.resolve()),1e3)};_exports.enableStickyFooter=enableStickyFooter;const disableStickyFooter=()=>{document.body.classList.remove(CLASSES_HASSTICKYFOOTER);const page=document.querySelector(SELECTORS_PAGE);null==page||page.classList.remove(CLASSES_HASSTICKYFOOTER)};_exports.disableStickyFooter=disableStickyFooter;_exports.init=()=>{var _document$querySelect;if(initialized||document.body.classList.contains("behat-site"))return;initialized=!0,enableStickyFooter();(null!==(_document$querySelect=document.querySelector(SELECTORS_PAGE))&&void 0!==_document$querySelect?_document$querySelect:document.body).addEventListener("scroll",scrollSpy)}})); + +//# sourceMappingURL=sticky-footer.min.js.map \ No newline at end of file diff --git a/theme/boost/amd/build/sticky-footer.min.js.map b/theme/boost/amd/build/sticky-footer.min.js.map new file mode 100644 index 00000000000..d875c5e052b --- /dev/null +++ b/theme/boost/amd/build/sticky-footer.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sticky-footer.min.js","sources":["../src/sticky-footer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Sticky footer module.\n *\n * @module theme_boost/sticky-footer\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\n\nconst SELECTORS = {\n STICKYFOOTER: '.stickyfooter',\n PAGE: '#page',\n};\n\nconst CLASSES = {\n HASSTICKYFOOTER: 'hasstickyfooter',\n};\n\nlet initialized = false;\n\nlet previousScrollPosition = 0;\n\n/**\n * Return the current page scroll position.\n * @package\n * @returns {number} the current scroll position\n */\nconst getScrollPosition = () => {\n const page = document.querySelector(SELECTORS.PAGE);\n if (page) {\n return page.scrollTop;\n }\n return window.pageYOffset;\n};\n\n/**\n * Scroll handler.\n * @package\n */\nconst scrollSpy = () => {\n // Ignore scroll if page size is not small.\n if (document.body.clientWidth >= 768) {\n return;\n }\n // Detect if scroll is going down.\n let scrollPosition = getScrollPosition();\n if (scrollPosition > previousScrollPosition) {\n disableStickyFooter();\n } else {\n enableStickyFooter();\n }\n previousScrollPosition = scrollPosition;\n};\n\n/**\n * Enable sticky footer in the page.\n */\nexport const enableStickyFooter = () => {\n // We need some seconds to make sure the CSS animation is ready.\n const pendingPromise = new Pending('theme_boost/sticky-footer:enabling');\n const footer = document.querySelector(SELECTORS.STICKYFOOTER);\n const page = document.querySelector(SELECTORS.PAGE);\n if (footer && page) {\n document.body.classList.add(CLASSES.HASSTICKYFOOTER);\n page.classList.add(CLASSES.HASSTICKYFOOTER);\n }\n setTimeout(() => pendingPromise.resolve(), 1000);\n};\n\n/**\n * Disable sticky footer in the page.\n */\nexport const disableStickyFooter = () => {\n document.body.classList.remove(CLASSES.HASSTICKYFOOTER);\n const page = document.querySelector(SELECTORS.PAGE);\n page?.classList.remove(CLASSES.HASSTICKYFOOTER);\n};\n\n/**\n * Initialize the module.\n */\nexport const init = () => {\n // Prevent sticky footer in behat.\n if (initialized || document.body.classList.contains('behat-site')) {\n return;\n }\n initialized = true;\n enableStickyFooter();\n const content = document.querySelector(SELECTORS.PAGE) ?? document.body;\n content.addEventListener(\"scroll\", scrollSpy);\n};\n"],"names":["SELECTORS","CLASSES","initialized","previousScrollPosition","scrollSpy","document","body","clientWidth","scrollPosition","page","querySelector","scrollTop","window","pageYOffset","getScrollPosition","disableStickyFooter","enableStickyFooter","pendingPromise","Pending","footer","classList","add","setTimeout","resolve","remove","contains","addEventListener"],"mappings":";;;;;;;2MAyBMA,uBACY,gBADZA,eAEI,QAGJC,wBACe,sBAGjBC,aAAc,EAEdC,uBAAyB,QAmBvBC,UAAY,QAEVC,SAASC,KAAKC,aAAe,eAI7BC,eAlBkB,YAChBC,KAAOJ,SAASK,cAAcV,uBAChCS,KACOA,KAAKE,UAETC,OAAOC,aAaOC,GACjBN,eAAiBL,uBACjBY,sBAEAC,qBAEJb,uBAAyBK,gBAMhBQ,mBAAqB,WAExBC,eAAiB,IAAIC,iBAAQ,sCAC7BC,OAASd,SAASK,cAAcV,wBAChCS,KAAOJ,SAASK,cAAcV,gBAChCmB,QAAUV,OACVJ,SAASC,KAAKc,UAAUC,IAAIpB,yBAC5BQ,KAAKW,UAAUC,IAAIpB,0BAEvBqB,YAAW,IAAML,eAAeM,WAAW,2DAMlCR,oBAAsB,KAC/BV,SAASC,KAAKc,UAAUI,OAAOvB,+BACzBQ,KAAOJ,SAASK,cAAcV,gBACpCS,MAAAA,MAAAA,KAAMW,UAAUI,OAAOvB,yFAMP,kCAEZC,aAAeG,SAASC,KAAKc,UAAUK,SAAS,qBAGpDvB,aAAc,EACdc,oDACgBX,SAASK,cAAcV,uEAAmBK,SAASC,MAC3DoB,iBAAiB,SAAUtB"} \ No newline at end of file diff --git a/theme/boost/amd/src/sticky-footer.js b/theme/boost/amd/src/sticky-footer.js new file mode 100644 index 00000000000..09842ea9ac6 --- /dev/null +++ b/theme/boost/amd/src/sticky-footer.js @@ -0,0 +1,107 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Sticky footer module. + * + * @module theme_boost/sticky-footer + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Pending from 'core/pending'; + +const SELECTORS = { + STICKYFOOTER: '.stickyfooter', + PAGE: '#page', +}; + +const CLASSES = { + HASSTICKYFOOTER: 'hasstickyfooter', +}; + +let initialized = false; + +let previousScrollPosition = 0; + +/** + * Return the current page scroll position. + * @package + * @returns {number} the current scroll position + */ +const getScrollPosition = () => { + const page = document.querySelector(SELECTORS.PAGE); + if (page) { + return page.scrollTop; + } + return window.pageYOffset; +}; + +/** + * Scroll handler. + * @package + */ +const scrollSpy = () => { + // Ignore scroll if page size is not small. + if (document.body.clientWidth >= 768) { + return; + } + // Detect if scroll is going down. + let scrollPosition = getScrollPosition(); + if (scrollPosition > previousScrollPosition) { + disableStickyFooter(); + } else { + enableStickyFooter(); + } + previousScrollPosition = scrollPosition; +}; + +/** + * Enable sticky footer in the page. + */ +export const enableStickyFooter = () => { + // We need some seconds to make sure the CSS animation is ready. + const pendingPromise = new Pending('theme_boost/sticky-footer:enabling'); + const footer = document.querySelector(SELECTORS.STICKYFOOTER); + const page = document.querySelector(SELECTORS.PAGE); + if (footer && page) { + document.body.classList.add(CLASSES.HASSTICKYFOOTER); + page.classList.add(CLASSES.HASSTICKYFOOTER); + } + setTimeout(() => pendingPromise.resolve(), 1000); +}; + +/** + * Disable sticky footer in the page. + */ +export const disableStickyFooter = () => { + document.body.classList.remove(CLASSES.HASSTICKYFOOTER); + const page = document.querySelector(SELECTORS.PAGE); + page?.classList.remove(CLASSES.HASSTICKYFOOTER); +}; + +/** + * Initialize the module. + */ +export const init = () => { + // Prevent sticky footer in behat. + if (initialized || document.body.classList.contains('behat-site')) { + return; + } + initialized = true; + enableStickyFooter(); + const content = document.querySelector(SELECTORS.PAGE) ?? document.body; + content.addEventListener("scroll", scrollSpy); +}; diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 23881b13c5b..0a900e37334 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -2320,6 +2320,10 @@ $footer-link-color: $bg-inverse-link-color !default; @include box-shadow($popover-box-shadow); } +.hasstickyfooter .btn-footer-popover { + bottom: calc(2rem + #{$navbar-height}); +} + .popover.footer { .popover-body { padding: 0; diff --git a/theme/boost/scss/moodle/debug.scss b/theme/boost/scss/moodle/debug.scss index a9a7e3e1cc6..cd9638ced0a 100644 --- a/theme/boost/scss/moodle/debug.scss +++ b/theme/boost/scss/moodle/debug.scss @@ -7,6 +7,13 @@ body.behat-site { position: absolute; } + // Sticky footer can overlap with elements so we keep it relative for behat. + &.hasstickyfooter .stickyfooter, + .stickyfooter { + position: inherit; + z-index: inherit; + } + // We need more spacing in action menus so behat does not click on the wrong menu item. .dropdown-item { margin-top: 4px !important; /* stylelint-disable declaration-no-important */ @@ -100,4 +107,3 @@ body > .debuggingmessage { body > .debuggingmessage ~ .debuggingmessage { margin-top: .5rem; } - diff --git a/theme/boost/scss/moodle/layout.scss b/theme/boost/scss/moodle/layout.scss index 153050c0244..bf56e6e4d52 100644 --- a/theme/boost/scss/moodle/layout.scss +++ b/theme/boost/scss/moodle/layout.scss @@ -192,6 +192,13 @@ top: $navbar-height; height: calc(100vh - #{$navbar-height}); } + .hasstickyfooter { + .drawer-left, + .drawer-right { + top: $navbar-height; + height: calc(100vh - #{$navbar-height} - #{$navbar-height}); + } + } #page.drawers { position: relative; @@ -217,6 +224,9 @@ margin-left: $drawer-left-width; margin-right: $drawer-right-width; } + &.hasstickyfooter { + height: calc(100vh - #{$navbar-height} - #{$navbar-height}); + } } } diff --git a/theme/boost/scss/moodle/sticky-footer.scss b/theme/boost/scss/moodle/sticky-footer.scss index b371c984496..afd4fed6c8a 100644 --- a/theme/boost/scss/moodle/sticky-footer.scss +++ b/theme/boost/scss/moodle/sticky-footer.scss @@ -7,6 +7,30 @@ body { height: 100%; } +.stickyfooter { + position: fixed; + right: 0; + left: 0; + height: $navbar-height + 1px; + bottom: -$navbar-height; + transition: bottom .5s; + z-index: $zindex-dropdown; + overflow: hidden; +} + +.hasstickyfooter .stickyfooter { + bottom: 0; +} + +/* Standard components fixes for sticky footer. */ + +.stickyfooter ul.pagination { + margin-bottom: map-get($spacers, 1); +} + + +/* Breakpoints fixes. */ + @include media-breakpoint-up(sm) { #page-wrapper { height: 100%; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 7120d1ba330..cb3ad930ee1 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -11727,6 +11727,9 @@ ul { bottom: 2rem; right: 2rem; } +.hasstickyfooter .btn-footer-popover { + bottom: calc(2rem + 60px); } + .popover.footer .popover-body { padding: 0; } .popover.footer .popover-body .footer-section a { @@ -19923,6 +19926,11 @@ body.reset-style .btn:not(.btn-icon) { body.behat-site .fixed-top { position: absolute; } +body.behat-site.hasstickyfooter .stickyfooter, +body.behat-site .stickyfooter { + position: inherit; + z-index: inherit; } + body.behat-site .dropdown-item { margin-top: 4px !important; /* stylelint-disable declaration-no-important */ } @@ -20001,6 +20009,24 @@ html, body { height: 100%; } +.stickyfooter { + position: fixed; + right: 0; + left: 0; + height: 61px; + bottom: -60px; + transition: bottom .5s; + z-index: 1000; + overflow: hidden; } + +.hasstickyfooter .stickyfooter { + bottom: 0; } + +/* Standard components fixes for sticky footer. */ +.stickyfooter ul.pagination { + margin-bottom: 0.25rem; } + +/* Breakpoints fixes. */ @media (min-width: 576px) { #page-wrapper { height: 100%; @@ -20624,6 +20650,10 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont .drawer-right { top: 60px; height: calc(100vh - 60px); } + .hasstickyfooter .drawer-left, + .hasstickyfooter .drawer-right { + top: 60px; + height: calc(100vh - 60px - 60px); } #page.drawers { position: relative; overflow-y: auto; @@ -20648,7 +20678,9 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont right: calc(315px + 2rem); } #page.drawers.show-drawer-left.show-drawer-right { margin-left: 285px; - margin-right: 315px; } } + margin-right: 315px; } + #page.drawers.hasstickyfooter { + height: calc(100vh - 60px - 60px); } } .drawercontrolbuttons { margin-top: 92px; } diff --git a/theme/boost/templates/core/sticky_footer.mustache b/theme/boost/templates/core/sticky_footer.mustache new file mode 100644 index 00000000000..ef6b9a7bf0e --- /dev/null +++ b/theme/boost/templates/core/sticky_footer.mustache @@ -0,0 +1,53 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template theme_boost/core/sticky_footer + + Displays a page sticky footer element. + + Example context (json): + { + "stickycontent": "Moodle", + "stickyclasses": "justify-content-end", + "extras": [ + { + "attribute" : "data-example", + "value" : "stickyfooter" + } + ] + } +}} + +{{#js}} +require(['theme_boost/sticky-footer'], function(footer) { + footer.init(); +}); +{{/js}} diff --git a/theme/classic/scss/classic/post.scss b/theme/classic/scss/classic/post.scss index 573225d612b..90310eb0cbf 100644 --- a/theme/classic/scss/classic/post.scss +++ b/theme/classic/scss/classic/post.scss @@ -263,3 +263,12 @@ padding-top: 0 !important; /* stylelint-disable-line declaration-no-important */ } } + +#page-footer { + padding-top: $spacer * .5; + padding-bottom: $spacer * .5; +} + +body.hasstickyfooter #page-footer { + padding-bottom: calc(#{$spacer} * .5 + #{$navbar-height}); +} diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 85667cd7740..a9a6fa6f30e 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -11727,6 +11727,9 @@ ul { bottom: 2rem; right: 2rem; } +.hasstickyfooter .btn-footer-popover { + bottom: calc(2rem + 50px); } + .popover.footer .popover-body { padding: 0; } .popover.footer .popover-body .footer-section a { @@ -19869,6 +19872,11 @@ body:not(.jsenabled) .langmenu:hover > .dropdown-menu, body.behat-site .fixed-top { position: absolute; } +body.behat-site.hasstickyfooter .stickyfooter, +body.behat-site .stickyfooter { + position: inherit; + z-index: inherit; } + body.behat-site .dropdown-item { margin-top: 4px !important; /* stylelint-disable declaration-no-important */ } @@ -19947,6 +19955,24 @@ html, body { height: 100%; } +.stickyfooter { + position: fixed; + right: 0; + left: 0; + height: 51px; + bottom: -50px; + transition: bottom .5s; + z-index: 1000; + overflow: hidden; } + +.hasstickyfooter .stickyfooter { + bottom: 0; } + +/* Standard components fixes for sticky footer. */ +.stickyfooter ul.pagination { + margin-bottom: 0.25rem; } + +/* Breakpoints fixes. */ @media (min-width: 576px) { #page-wrapper { height: 100%; @@ -20570,6 +20596,10 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont .drawer-right { top: 50px; height: calc(100vh - 50px); } + .hasstickyfooter .drawer-left, + .hasstickyfooter .drawer-right { + top: 50px; + height: calc(100vh - 50px - 50px); } #page.drawers { position: relative; overflow-y: auto; @@ -20594,7 +20624,9 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont right: calc(315px + 2rem); } #page.drawers.show-drawer-left.show-drawer-right { margin-left: 285px; - margin-right: 315px; } } + margin-right: 315px; } + #page.drawers.hasstickyfooter { + height: calc(100vh - 50px - 50px); } } .drawercontrolbuttons { margin-top: 92px; } @@ -22075,3 +22107,10 @@ body { .block.block_settings #settingsnav { padding-top: 0 !important; /* stylelint-disable-line declaration-no-important */ } + +#page-footer { + padding-top: 0.5rem; + padding-bottom: 0.5rem; } + +body.hasstickyfooter #page-footer { + padding-bottom: calc(1rem * .5 + 50px); } diff --git a/theme/classic/templates/footer.mustache b/theme/classic/templates/footer.mustache index e40ecf3c68a..17f415eda06 100644 --- a/theme/classic/templates/footer.mustache +++ b/theme/classic/templates/footer.mustache @@ -27,7 +27,7 @@ } } }} -