MDL-55074 theme_boost: Navigation and blocks
authorDamyon Wiese <damyon@moodle.com>
Thu, 6 Oct 2016 06:10:14 +0000 (14:10 +0800)
committerDamyon Wiese <damyon@moodle.com>
Fri, 21 Oct 2016 04:37:33 +0000 (12:37 +0800)
This patch includes a big set of changes that are all designed to work together to provide
a better way to navigate in the new theme, and a different way of working with blocks.

Blocks have been moved to a "drawer" that can be opened and closed (this is remembered in a user pref).

A new "flat navigation" element is also available in a drawer - which should let you do 90% of things
without needing to open the "blocks" drawer.

The flat navigation is build from specific parts of the nav tree - the top nodes like "calendar, dashboard" are
hand picked. There is a mycourses node listing your enrolled courses.

There is a node for the current course, built from the top nodes in the current course node in the nav tree.

Administrators have a link to the Site admin settings here too.

These nav elements are used by the templates for the new theme, which also has a resigned layout for login and signup.

There have also been some additional fixes / improvements to the scss for the new theme which goes along with these
layout changes.

This set of changes is a collaboration between Martin, Damyon and Alberto (thanks!).

52 files changed:
admin/admin_settings_search_form.php [new file with mode: 0644]
admin/search.php
admin/settings/top.php
course/admin.php [new file with mode: 0644]
course/format/topics/lib.php
course/format/weeks/lib.php
course/lib.php
course/switchrole.php
course/switchrole_form.php [new file with mode: 0644]
course/tests/externallib_test.php
lang/en/admin.php
lang/en/block.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/pagelib.php
lib/templates/settings_link_page.mustache [new file with mode: 0644]
lib/templates/settings_link_page_single.mustache [new file with mode: 0644]
lib/templates/signup_form_layout.mustache [new file with mode: 0644]
login/signup.php
login/signup_form.php
theme/boost/amd/build/blocks_drawer.min.js [new file with mode: 0644]
theme/boost/amd/build/drawer.min.js [new file with mode: 0644]
theme/boost/amd/src/drawer.js [new file with mode: 0644]
theme/boost/classes/output/core_renderer.php
theme/boost/config.php
theme/boost/layout/columns1.php
theme/boost/layout/columns2.php
theme/boost/layout/login.php [new file with mode: 0644]
theme/boost/layout/secure.php
theme/boost/pix/blocksdrawer.png [new file with mode: 0644]
theme/boost/pix/blocksdrawer.svg [new file with mode: 0644]
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/bootswatch.scss [new file with mode: 0644]
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/drawer.scss [new file with mode: 0644]
theme/boost/scss/moodle/icons.scss
theme/boost/scss/moodle/login.scss [new file with mode: 0644]
theme/boost/scss/preset-default.scss
theme/boost/templates/blocks-drawer.mustache [new file with mode: 0644]
theme/boost/templates/columns1.mustache
theme/boost/templates/columns2.mustache
theme/boost/templates/core/login.mustache
theme/boost/templates/core/navbar.mustache
theme/boost/templates/core/signup_form_layout.mustache [new file with mode: 0644]
theme/boost/templates/flat_navigation.mustache [new file with mode: 0644]
theme/boost/templates/header.mustache
theme/boost/templates/login.mustache [new file with mode: 0644]
theme/boost/templates/nav-drawer.mustache [new file with mode: 0644]
theme/boost/templates/secure.mustache

diff --git a/admin/admin_settings_search_form.php b/admin/admin_settings_search_form.php
new file mode 100644 (file)
index 0000000..ad42300
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+// 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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Admin settings search form
+ *
+ * @package    admin
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once $CFG->libdir.'/formslib.php';
+
+/**
+ * Admin settings search form
+ *
+ * @package    admin
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_settings_search_form extends moodleform {
+    function definition () {
+        $mform = $this->_form;
+
+        //$mform->addElement('header', 'settingsheader', get_string('search', 'admin'));
+        $elements = [];
+        $elements[] = $mform->createElement('text', 'query', get_string('query', 'admin'));
+        $elements[] = $mform->createElement('submit', 'search', get_string('search'));
+        $mform->addGroup($elements);
+        $mform->setType('query', PARAM_RAW);
+        $mform->setDefault('query', optional_param('query', '', PARAM_RAW));
+    }
+}
index 6c255c6..0e9a531 100644 (file)
@@ -36,6 +36,8 @@ if ($data = data_submitted() and confirm_sesskey()) {
 // to modify them
 echo $OUTPUT->header($focus);
 
+echo $OUTPUT->heading(get_string('administrationsite'));
+
 if ($errormsg !== '') {
     echo $OUTPUT->notification($errormsg);
 
@@ -43,6 +45,18 @@ if ($errormsg !== '') {
     echo $OUTPUT->notification($statusmsg, 'notifysuccess');
 }
 
-echo admin_search_settings_html($query);
+require_once("admin_settings_search_form.php");
+$form = new admin_settings_search_form();
+$form->display();
+echo '<hr>';
+
+if ($query) {
+    echo admin_search_settings_html($query);
+} else {
+    $node = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN);
+    if ($node) {
+        echo $OUTPUT->render_from_template('core/settings_link_page', ['node' => $node]);
+    }
+}
 
 echo $OUTPUT->footer();
index 223af0c..59a6102 100644 (file)
@@ -47,4 +47,4 @@ $ADMIN->add('root', new admin_category('development', new lang_string('developme
 $ADMIN->add('root', new admin_category('unsupported', new lang_string('unsupported', 'admin'), true));
 
 // hidden search script
-$ADMIN->add('root', new admin_externalpage('search', new lang_string('searchresults'), "$CFG->wwwroot/$CFG->admin/search.php", 'moodle/site:config', true));
+$ADMIN->add('root', new admin_externalpage('search', new lang_string('search', 'admin'), "$CFG->wwwroot/$CFG->admin/search.php", 'moodle/site:config', true));
diff --git a/course/admin.php b/course/admin.php
new file mode 100644 (file)
index 0000000..b89b382
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+// 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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Listing of the course administration pages for this course.
+ *
+ * @copyright 2016 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once("../config.php");
+
+$courseid = required_param('courseid', PARAM_INT);
+
+$PAGE->set_url('/course/admin.php', array('courseid'=>$courseid));
+
+$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+
+require_login($course);
+$context = context_course::instance($course->id);
+
+$PAGE->set_pagelayout('incourse');
+$PAGE->set_title(get_string('courseadministration'));
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add(get_string('courseadministration'));
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('courseadministration'));
+
+$node = $PAGE->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE);
+if ($node) {
+    echo $OUTPUT->render_from_template('core/settings_link_page', ['node' => $node]);
+}
+
+echo $OUTPUT->footer();
index da48e81..4f01d7d 100644 (file)
@@ -121,9 +121,6 @@ class format_topics extends format_base {
             if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
                 $url->param('section', $sectionno);
             } else {
-                if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) {
-                    return null;
-                }
                 $url->set_anchor('section-'.$sectionno);
             }
         }
index d32664c..db09678 100644 (file)
@@ -124,9 +124,6 @@ class format_weeks extends format_base {
             if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
                 $url->param('section', $sectionno);
             } else {
-                if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) {
-                    return null;
-                }
                 $url->set_anchor('section-'.$sectionno);
             }
         }
index e731709..8d89b8d 100644 (file)
@@ -3510,6 +3510,29 @@ function course_get_user_navigation_options($context, $course = null) {
         $options->participants = has_capability('moodle/course:viewparticipants', $context);
         $options->badges = !empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) &&
                             has_capability('moodle/badges:viewbadges', $context);
+        // Add view grade report is permitted.
+        $grades = false;
+        if (has_capability('moodle/grade:viewall', $context)) {
+            $grades = true;
+        } else if (!empty($course->showgrades)) {
+            $reports = core_component::get_plugin_list('gradereport');
+            if (is_array($reports) && count($reports) > 0) {  // Get all installed reports.
+                arsort($reports);   // User is last, we want to test it first.
+                foreach ($reports as $plugin => $plugindir) {
+                    if (has_capability('gradereport/'.$plugin.':view', $context)) {
+                        // Stop when the first visible plugin is found.
+                        $grades = true;
+                        break;
+                    }
+                }
+            }
+        }
+        $options->grades = $grades;
+    }
+
+    if (\core_competency\api::is_enabled()) {
+        $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
+        $options->competencies = has_any_capability($capabilities, $context);
     }
     return $options;
 }
index a682c37..4e7c237 100644 (file)
@@ -46,9 +46,11 @@ if (strpos($returnurl, '?') === false) {
     $returnurl  = clean_param($returnurl, PARAM_URL);
 }
 
-$PAGE->set_url('/course/switchrole.php', array('id'=>$id));
+$PAGE->set_url('/course/switchrole.php', array('id'=>$id, 'switchrole'=>$switchrole));
 
-require_sesskey();
+if ($switchrole >= 0) {
+    require_sesskey();
+}
 
 if (!$course = $DB->get_record('course', array('id'=>$id))) {
     redirect(new moodle_url('/'));
@@ -70,6 +72,21 @@ if ($switchrole > 0 && has_capability('moodle/role:switchroles', $context)) {
     if (is_array($aroles) && isset($aroles[$switchrole])) {
         role_switch($switchrole, $context);
     }
+} else if ($switchrole < 0) {
+
+    $PAGE->set_title(get_string('switchroleto'));
+    $PAGE->set_heading($course->fullname);
+    $PAGE->set_pagelayout('incourse');
+
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('switchroleto'));
+
+    require_once($CFG->dirroot.'/course/switchrole_form.php');
+    $form = new switchrole_form(null, ['course' => $course]);
+    $form->display();
+
+    echo $OUTPUT->footer();
+    exit;
 }
 
 redirect($returnurl);
diff --git a/course/switchrole_form.php b/course/switchrole_form.php
new file mode 100644 (file)
index 0000000..5788656
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+// 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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Switch roles form.
+ *
+ * @package     core_course
+ * @copyright   2016 Damyon Wiese
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Defines the course completion settings form.
+ */
+class switchrole_form extends moodleform {
+
+    /**
+     * Determine whether the user is assuming another role
+     *
+     * This function checks to see if the user is assuming another role by means of
+     * role switching. In doing this we compare each RSW key (context path) against
+     * the current context path. This ensures that we can provide the switching
+     * options against both the course and any page shown under the course.
+     *
+     * @param context $context
+     * @return bool|int The role(int) if the user is in another role, false otherwise
+     */
+    protected function in_alternative_role($context) {
+        global $USER, $PAGE;
+        if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) {
+            if (!empty($PAGE->context) && !empty($USER->access['rsw'][$PAGE->context->path])) {
+                return $USER->access['rsw'][$PAGE->context->path];
+            }
+            foreach ($USER->access['rsw'] as $key=>$role) {
+                if (strpos($context->path, $key)===0) {
+                    return $role;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Defines the form fields.
+     */
+    public function definition() {
+        global $USER, $CFG, $DB;
+
+        $mform = $this->_form;
+        $course = $this->_customdata['course'];
+
+        // Overall criteria aggregation.
+        $context = context_course::instance($course->id);
+        $roles = array();
+        $assumedrole = -1;
+        if (is_role_switched($course->id)) {
+            $roles[0] = get_string('switchrolereturn');
+            $assumedrole = $USER->access['rsw'][$context->path];
+        }
+        $availableroles = get_switchable_roles($context);
+        if (is_array($availableroles)) {
+            foreach ($availableroles as $key=>$role) {
+                if ($assumedrole == (int)$key) {
+                    continue;
+                }
+                $roles[$key] = $role;
+            }
+        }
+        $mform->addElement('select', 'switchrole', get_string('role'), $roles);
+
+        // Add common action buttons.
+        $this->add_action_buttons();
+
+        // Add hidden fields.
+        $mform->addElement('hidden', 'id', $course->id);
+        $mform->setType('id', PARAM_INT);
+    }
+}
index 9015dbe..0e07208 100644 (file)
@@ -1796,7 +1796,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
                 $navoptions->{$option['name']} = $option['available'];
             }
             if ($course['id'] == SITEID) {
-                $this->assertCount(7, $course['options']);
+                $this->assertCount(8, $course['options']);
                 $this->assertTrue($navoptions->blogs);
                 $this->assertFalse($navoptions->notes);
                 $this->assertFalse($navoptions->participants);
@@ -1804,12 +1804,15 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
                 $this->assertTrue($navoptions->tags);
                 $this->assertFalse($navoptions->search);
                 $this->assertTrue($navoptions->calendar);
+                $this->assertTrue($navoptions->competencies);
             } else {
-                $this->assertCount(4, $course['options']);
+                $this->assertCount(6, $course['options']);
                 $this->assertTrue($navoptions->blogs);
                 $this->assertFalse($navoptions->notes);
                 $this->assertTrue($navoptions->participants);
                 $this->assertTrue($navoptions->badges);
+                $this->assertTrue($navoptions->grades);
+                $this->assertTrue($navoptions->competencies);
             }
         }
     }
index 0f833ac..50cae40 100644 (file)
@@ -889,6 +889,7 @@ $string['proxypassword'] = 'Proxy password';
 $string['proxyport'] = 'Proxy port';
 $string['proxytype'] = 'Proxy type';
 $string['proxyuser'] = 'Proxy username';
+$string['query'] = 'Query';
 $string['question'] = 'Question';
 $string['questionbehaviours'] = 'Question behaviours';
 $string['questioncwqpfscheck'] = 'One or more \'random\' questions in a quiz are set up to select questions from a mixture of shared and unshared question categories. There is a more detailed report <a href="{$a->reporturl}">here</a> and see Moodle Docs page <a href="{$a->docsurl}">here</a>.';
index 7b31859..ad04aef 100644 (file)
@@ -26,6 +26,7 @@ $string['addtodock'] = 'Move this to the dock';
 $string['anypagematchingtheabove'] = 'Any page matching the above';
 $string['appearsinsubcontexts'] = 'Appears in sub-contexts';
 $string['assignrolesinblock'] = 'Assign roles in {$a} block';
+$string['blocksdrawertoggle'] = 'Hide/show blocks drawer';
 $string['blocksettings'] = 'Block settings';
 $string['bracketfirst'] = '{$a} (first)';
 $string['bracketlast'] = '{$a} (last)';
@@ -51,6 +52,7 @@ $string['moveblockafter'] = 'Move block to after {$a} block';
 $string['moveblockbefore'] = 'Move block to before {$a} block';
 $string['moveblockinregion'] = 'Move block to {$a} region';
 $string['movingthisblockcancel'] = 'Moving this block ({$a})';
+$string['myblocks'] = 'My blocks';
 $string['onthispage'] = 'On this page';
 $string['pagetypes'] = 'Page types';
 $string['pagetypewarning'] = 'The previously specified page type is no longer selectable. Please choose the most appropriate page type below.';
index b6ebf72..c12cf63 100644 (file)
@@ -143,6 +143,8 @@ class navigation_node implements renderable {
     public $includesectionnum = false;
     /** @var bool does the node need to be loaded via ajax */
     public $requiresajaxloading = false;
+    /** @var bool If set to true this node will be added to the "flat" navigation */
+    public $showinflatnavigation = false;
 
     /**
      * Constructs a new navigation_node
@@ -236,6 +238,65 @@ class navigation_node implements renderable {
         return false;
     }
 
+    /**
+     * True if this nav node has siblings in the tree.
+     *
+     * @return bool
+     */
+    public function has_siblings() {
+        if (empty($this->parent) || empty($this->parent->children)) {
+            return false;
+        }
+        if ($this->parent->children instanceof navigation_node_collection) {
+            $count = $this->parent->children->count();
+        } else {
+            $count = count($this->parent->children);
+        }
+        return ($count > 1);
+    }
+
+    /**
+     * Recursively walk the tree looking for a node with a valid action.
+     * Depth first search.
+     *
+     * @return bool
+     */
+    public function resolve_action() {
+        if ($this->action) {
+            return $this->action;
+        }
+        if (!empty($this->children)) {
+            foreach ($this->children as $child) {
+                $action = $child->resolve_action();
+                if (!empty($action)) {
+                    return $action;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get a list of sibling navigation nodes at the same level as this one.
+     *
+     * @return bool|array of navigation_node
+     */
+    public function get_siblings() {
+        // Returns a list of the siblings of the current node for display in a flat navigation element. Either
+        // the in-page links or the breadcrumb links.
+        $siblings = false;
+
+        if ($this->has_siblings()) {
+            $siblings = [];
+            foreach ($this->parent->children as $child) {
+                if ($child->display) {
+                    $siblings[] = $child;
+                }
+            }
+        }
+        return $siblings;
+    }
+
     /**
      * This sets the URL that the URL of new nodes get compared to when locating
      * the active node.
@@ -381,6 +442,24 @@ class navigation_node implements renderable {
         return $this->children->find($key, $type);
     }
 
+    /**
+     * Walk the tree building up a list of all the flat navigation nodes.
+     *
+     * @param flat_navigation $nodes List of the found flat navigation nodes.
+     */
+    public function build_flat_navigation_list(flat_navigation $nodes) {
+        if ($this->showinflatnavigation) {
+            $indent = 0;
+            if ($this->type == self::TYPE_COURSE) {
+                $indent = 1;
+            }
+            $nodes->add(new flat_navigation_node($this, $indent));
+        }
+        foreach ($this->children as $child) {
+            $child->build_flat_navigation_list($nodes);
+        }
+    }
+
     /**
      * Get the child of this node that has the given key + (optional) type.
      *
@@ -1053,6 +1132,7 @@ class global_navigation extends navigation_node {
 
         // Use the parents constructor.... good good reuse
         parent::__construct($properties);
+        $this->showinflatnavigation = true;
 
         // Initalise and set defaults
         $this->page = $page;
@@ -1128,15 +1208,18 @@ class global_navigation extends navigation_node {
         $this->load_course_sections($SITE, $this->rootnodes['site']);
 
         $course = $this->page->course;
+        $this->load_courses_enrolled();
 
         // $issite gets set to true if the current pages course is the sites frontpage course
         $issite = ($this->page->course->id == $SITE->id);
+
         // Determine if the user is enrolled in any course.
         $enrolledinanycourse = enrol_user_sees_own_courses();
 
         $this->rootnodes['currentcourse']->mainnavonly = true;
         if ($enrolledinanycourse) {
             $this->rootnodes['mycourses']->isexpandable = true;
+            $this->rootnodes['mycourses']->showinflatnavigation = true;
             if ($CFG->navshowallcourses) {
                 // When we show all courses we need to show both the my courses and the regular courses branch.
                 $this->rootnodes['courses']->isexpandable = true;
@@ -2428,6 +2511,14 @@ class global_navigation extends navigation_node {
         // This is the name that will be shown for the course.
         $coursename = empty($CFG->navshowfullcoursenames) ? $shortname : $fullname;
 
+        if ($coursetype == self::COURSE_CURRENT) {
+            if ($coursenode = $this->rootnodes['mycourses']->find($course->id, self::TYPE_COURSE)) {
+                return $coursenode;
+            } else {
+                $coursetype = self::COURSE_OTHER;
+            }
+        }
+
         // Can the user expand the course to see its content.
         $canexpandcourse = true;
         if ($issite) {
@@ -2468,6 +2559,7 @@ class global_navigation extends navigation_node {
         }
 
         $coursenode = $parent->add($coursename, $url, self::TYPE_COURSE, $shortname, $course->id);
+        $coursenode->showinflatnavigation = $coursetype == self::COURSE_MY;
         $coursenode->hidden = (!$course->visible);
         $coursenode->title(format_string($course->fullname, true, array('context' => $coursecontext, 'escape' => false)));
         if ($canexpandcourse) {
@@ -2582,11 +2674,21 @@ class global_navigation extends navigation_node {
         if ($navoptions->badges) {
             $url = new moodle_url('/badges/view.php', array('type' => 2, 'id' => $course->id));
 
-            $coursenode->add(get_string('coursebadges', 'badges'), null,
-                    navigation_node::TYPE_CONTAINER, null, 'coursebadges');
-            $coursenode->get('coursebadges')->add(get_string('badgesview', 'badges'), $url,
+            $coursenode->add(get_string('coursebadges', 'badges'), $url,
                     navigation_node::TYPE_SETTING, null, 'badgesview',
-                    new pix_icon('i/badge', get_string('badgesview', 'badges')));
+                    new pix_icon('i/badge', get_string('coursebadges', 'badges')));
+        }
+
+        // Check access to the course and competencies page.
+        if ($navoptions->competencies) {
+            // Just a link to course competency.
+            $title = get_string('competencies', 'core_competency');
+            $path = new moodle_url("/admin/tool/lp/coursecompetencies.php", array('courseid' => $course->id));
+            $coursenode->add($title, $path, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/competencies', ''));
+        }
+        if ($navoptions->grades) {
+            $url = new moodle_url('/grade/report/index.php', array('id'=>$course->id));
+            $gradenode = $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, 'grades', new pix_icon('i/grades', ''));
         }
 
         return true;
@@ -2603,7 +2705,7 @@ class global_navigation extends navigation_node {
      * @return bool True for successfull generation
      */
     public function add_front_page_course_essentials(navigation_node $coursenode, stdClass $course) {
-        global $CFG;
+        global $CFG, $USER;
         require_once($CFG->dirroot . '/course/lib.php');
 
         if ($coursenode == false || $coursenode->get('frontpageloaded', navigation_node::TYPE_CUSTOM)) {
@@ -2657,7 +2759,18 @@ class global_navigation extends navigation_node {
         if ($navoptions->calendar) {
             // Calendar
             $calendarurl = new moodle_url('/calendar/view.php', array('view' => 'month'));
-            $coursenode->add(get_string('calendar', 'calendar'), $calendarurl, self::TYPE_CUSTOM, null, 'calendar');
+            $node = $coursenode->add(get_string('calendar', 'calendar'), $calendarurl, self::TYPE_CUSTOM, null, 'calendar');
+            $node->showinflatnavigation = true;
+        }
+
+        if (isloggedin()) {
+            $usercontext = context_user::instance($USER->id);
+            if (has_capability('moodle/user:manageownfiles', $usercontext)) {
+                $url = new moodle_url('/user/files.php');
+                $node = $coursenode->add(get_string('privatefiles'), $url, self::TYPE_SETTING);
+                $node->display = false;
+                $node->showinflatnavigation = true;
+            }
         }
 
         return true;
@@ -3090,13 +3203,12 @@ class navbar extends navigation_node {
     public $includesettingsbase = false;
     /** @var breadcrumb_navigation_node[] $prependchildren */
     protected $prependchildren = array();
+
     /**
      * The almighty constructor
      *
      * @param moodle_page $page
      */
-
-
     public function __construct(moodle_page $page) {
         global $CFG;
         if (during_initial_install()) {
@@ -3255,6 +3367,10 @@ class navbar extends navigation_node {
             $items = array_merge($items, array_reverse($this->prependchildren));
         }
 
+        $last = reset($items);
+        if ($last) {
+            $last->set_last(true);
+        }
         $this->items = array_reverse($items);
         return $this->items;
     }
@@ -3416,6 +3532,9 @@ class navbar extends navigation_node {
  */
 class breadcrumb_navigation_node extends navigation_node {
 
+    /** @var $last boolean A flag indicating this is the last item in the list of breadcrumbs. */
+    private $last = false;
+
     /**
      * A proxy constructor
      *
@@ -3436,6 +3555,251 @@ class breadcrumb_navigation_node extends navigation_node {
         }
     }
 
+    /**
+     * Getter for "last"
+     * @return boolean
+     */
+    public function is_last() {
+        return $this->last;
+    }
+
+    /**
+     * Setter for "last"
+     * @param $val boolean
+     */
+    public function set_last($val) {
+        $this->last = $val;
+    }
+}
+
+/**
+ * Subclass of navigation_node allowing different rendering for the flat navigation
+ * in particular allowing dividers and indents.
+ *
+ * @package   core
+ * @category  navigation
+ * @copyright 2016 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class flat_navigation_node extends navigation_node {
+
+    /** @var $indent integer The indent level */
+    private $indent = 0;
+
+    /** @var $showdivider bool Show a divider before this element */
+    private $showdivider = false;
+
+    /** @var $smscreenonly bool Only show this menu item on xs and sm screen sizes */
+    private $smscreenonly = false;
+
+    /**
+     * A proxy constructor
+     *
+     * @param mixed $navnode A navigation_node or an array
+     */
+    public function __construct($navnode, $indent) {
+        if (is_array($navnode)) {
+            parent::__construct($navnode);
+        } else if ($navnode instanceof navigation_node) {
+
+            // Just clone everything.
+            $objvalues = get_object_vars($navnode);
+            foreach ($objvalues as $key => $value) {
+                 $this->$key = $value;
+            }
+        } else {
+            throw coding_exception('Not a valid flat_navigation_node');
+        }
+        $this->indent = $indent;
+    }
+
+    /**
+     * Getter for "showdivider"
+     * @return boolean
+     */
+    public function showdivider() {
+        return $this->showdivider;
+    }
+
+    /**
+     * Setter for "showdivider"
+     * @param $val boolean
+     */
+    public function set_showdivider($val) {
+        $this->showdivider = $val;
+    }
+
+    /**
+     * Getter for "indent"
+     * @return boolean
+     */
+    public function get_indent() {
+        return $this->indent;
+    }
+
+    /**
+     * Setter for "indent"
+     * @param $val boolean
+     */
+    public function set_indent($val) {
+        $this->indent = $val;
+    }
+
+    /**
+     * Getter for "smscreenonly"
+     * @return boolean
+     */
+    public function get_smscreenonly() {
+        return $this->smscreenonly;
+    }
+
+    /**
+     * Setter for "smscreenonly"
+     * @param $val boolean
+     */
+    public function set_smscreenonly($val) {
+        $this->smscreenonly = $val;
+    }
+}
+
+/**
+ * Class used to generate a collection of navigation nodes most closely related
+ * to the current page.
+ *
+ * @package core
+ * @copyright 2016 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class flat_navigation extends navigation_node_collection {
+    /** @var moodle_page the moodle page that the navigation belongs to */
+    protected $page;
+
+    /**
+     * Constructor.
+     *
+     * @param moodle_page $page
+     */
+    public function __construct(moodle_page &$page) {
+        if (during_initial_install()) {
+            return false;
+        }
+        $this->page = $page;
+    }
+
+    /**
+     * Build the list of navigation nodes based on the current navigation and settings trees.
+     *
+     */
+    public function initialise() {
+        global $PAGE, $USER, $OUTPUT, $CFG;
+        if (during_initial_install()) {
+            return;
+        }
+
+        $current = false;
+
+        $course = $PAGE->course;
+
+        $PAGE->navigation->build_flat_navigation_list($this);
+
+        // First walk the nav tree looking for "flat_navigation" nodes.
+        if ($course->id > 1) {
+            // It's a real course.
+            // 'dh' is an unused param used to give this node a different url to the default.
+            // This is so we don't have 2 nodes in the flat have with the same url (both would be highlighted).
+            // 'dh' means "don't highlight".
+            $url = new moodle_url('/course/view.php', array('id' => $course->id, 'dh' => 1));
+            $flat = new flat_navigation_node(navigation_node::create($course->shortname, $url), 0);
+            $flat->key = 'coursehome';
+            $flat->set_showdivider(true);
+            $this->add($flat);
+
+            $coursenode = $PAGE->navigation->find_active_node();
+            if ($coursenode) {
+                foreach ($coursenode->children as $child) {
+                    if ($child->action) {
+                        $flat = new flat_navigation_node($child, 1);
+                        $this->add($flat);
+                    }
+                }
+            }
+
+        }
+        $admin = $PAGE->settingsnav->find('siteadministration', navigation_node::TYPE_SITE_ADMIN);
+        if (!$admin) {
+            // Try again - crazy nav tree!
+            $admin = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN);
+        }
+        if ($admin) {
+            $flat = new flat_navigation_node($admin, 0);
+            $flat->set_showdivider(true);
+            $flat->key = 'sitesettings';
+            $this->add($flat);
+        }
+
+        // Add the lang menu for small screens (it doesn't fit in the header).
+        if (!empty($CFG->langmenu)) {
+            if ($PAGE->course == SITEID || empty($PAGE->course->lang)) {
+                $langs = get_string_manager()->get_list_of_translations();
+                if (count($langs) >= 2) {
+                    $first = true;
+                    foreach ($langs as $lang => $name) {
+                        $url = clone($PAGE->url);
+                        $url->params(['lang' => $lang]);
+                        $flat = new flat_navigation_node(navigation_node::create($name, $url), 0);
+                        if ($first) {
+                            $flat->set_showdivider(true);
+                            $first = false;
+                        }
+                        $flat->set_smscreenonly(true);
+                        $flat->key = $lang;
+                        $this->add($flat);
+                    }
+                }
+            }
+        }
+
+        // Add the custom menu for small screens (it doesn't fit in the header).
+        $customcount = 0;
+        if (!empty($CFG->custommenuitems)) {
+            $custommenus = custom_menu::convert_text_to_menu_nodes($CFG->custommenuitems, current_language());
+            $first = true;
+            foreach ($custommenus as $menu) {
+                if (preg_match("/^#+$/", $menu->get_text())) {
+                    $first = true;
+                } else {
+                    $url = $menu->get_url();
+                    if ($url) {
+                        $url = $url->out(false);
+                    }
+                    $flat = new flat_navigation_node(navigation_node::create($menu->get_text(), $url), 0);
+                    if ($first) {
+                        $flat->set_showdivider(true);
+                        $first = false;
+                    }
+                    $flat->set_smscreenonly(true);
+                    $flat->key = 'c-' . $customcount++;
+                    $this->add($flat);
+                }
+                foreach ($menu->get_children() as $child) {
+                    if (preg_match("/^#+$/", $child->get_text())) {
+                        $first = true;
+                    } else {
+                        $url = $child->get_url();
+                        if ($url) {
+                            $url = $url->out(false);
+                        }
+                        $flat = new flat_navigation_node(navigation_node::create($child->get_text(), $url), 1);
+                        $flat->set_smscreenonly(true);
+                        $flat->key = 'c-' . $customcount++;
+                        $this->add($flat);
+                    }
+                }
+            }
+
+        }
+    }
+
 }
 
 /**
@@ -3480,6 +3844,7 @@ class settings_navigation extends navigation_node {
         $this->cache = new navigation_cache(NAVIGATION_CACHE_NAME);
         $this->children = new navigation_node_collection();
     }
+
     /**
      * Initialise the settings navigation based on the current context
      *
@@ -3561,7 +3926,7 @@ class settings_navigation extends navigation_node {
                     $adminsettings->remove();
                     $adminsettings = false;
                 }
-                $siteadminnode = $this->add(get_string('administrationsite'), new moodle_url('/admin'), self::TYPE_SITE_ADMIN, null, 'siteadministration');
+                $siteadminnode = $this->add(get_string('administrationsite'), new moodle_url('/admin/search.php'), self::TYPE_SITE_ADMIN, null, 'siteadministration');
                 $siteadminnode->id = 'expandable_branch_'.$siteadminnode->type.'_'.clean_param($siteadminnode->key, PARAM_ALPHANUMEXT);
                 $siteadminnode->requiresajaxloading = 'true';
             }
@@ -3681,7 +4046,7 @@ class settings_navigation extends navigation_node {
 
             // Disable the navigation from automatically finding the active node
             navigation_node::$autofindactive = false;
-            $referencebranch = $this->add(get_string('administrationsite'), null, self::TYPE_SITE_ADMIN, null, 'root');
+            $referencebranch = $this->add(get_string('administrationsite'), '/admin/search.php', self::TYPE_SITE_ADMIN, null, 'root');
             foreach ($adminroot->children as $adminbranch) {
                 $this->load_administration_settings($referencebranch, $adminbranch);
             }
@@ -3789,29 +4154,6 @@ class settings_navigation extends navigation_node {
             $coursenode->force_open();
         }
 
-        if ($this->page->user_allowed_editing()) {
-            // Add the turn on/off settings
-
-            if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
-                // We are on the course page, retain the current page params e.g. section.
-                $baseurl = clone($this->page->url);
-                $baseurl->param('sesskey', sesskey());
-            } else {
-                // Edit on the main course page.
-                $baseurl = new moodle_url('/course/view.php', array('id'=>$course->id, 'return'=>$this->page->url->out_as_local_url(false), 'sesskey'=>sesskey()));
-            }
-
-            $editurl = clone($baseurl);
-            if ($this->page->user_is_editing()) {
-                $editurl->param('edit', 'off');
-                $editstring = get_string('turneditingoff');
-            } else {
-                $editurl->param('edit', 'on');
-                $editstring = get_string('turneditingon');
-            }
-            $coursenode->add($editstring, $editurl, self::TYPE_SETTING, null, 'turneditingonoff', new pix_icon('i/edit', ''));
-        }
-
         if ($adminoptions->update) {
             // Add the course settings link
             $url = new moodle_url('/course/edit.php', array('id'=>$course->id));
@@ -3959,7 +4301,8 @@ class settings_navigation extends navigation_node {
             }
         }
         if (is_array($roles) && count($roles)>0) {
-            $switchroles = $this->add(get_string('switchroleto'), null, self::TYPE_CONTAINER, null, 'switchroleto');
+            $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id, 'switchrole'=>'-1', 'returnurl'=>$this->page->url->out_as_local_url(false)));
+            $switchroles = $coursenode->add(get_string('switchroleto'), $url, self::TYPE_CONTAINER, null, 'switchroleto');
             if ((count($roles)==1 && array_key_exists(0, $roles))|| $assumedrole!==false) {
                 $switchroles->force_open();
             }
@@ -4393,7 +4736,7 @@ class settings_navigation extends navigation_node {
             if (($currentuser || is_siteadmin($USER) || !is_siteadmin($user)) &&
                     has_capability('moodle/user:update', $systemcontext)) {
                 $url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
-                $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
+                $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING, null, 'editprofile');
             } else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_siteadmin($user)) ||
                     ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
                 if ($userauthplugin && $userauthplugin->can_edit_profile()) {
@@ -4401,7 +4744,7 @@ class settings_navigation extends navigation_node {
                     if (empty($url)) {
                         $url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
                     }
-                    $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
+                    $useraccount->add(get_string('editmyprofile'), $url, self::TYPE_SETTING, null, 'editprofile');
                 }
             }
         }
index edea6df..157bb15 100644 (file)
@@ -3905,6 +3905,15 @@ class action_menu implements renderable, templatable {
         $this->menutrigger = $trigger;
     }
 
+    /**
+     * Return true if there is at least one visible link in the menu.
+     *
+     * @return bool
+     */
+    public function is_empty() {
+        return !count($this->primaryactions) && !count($this->secondaryactions);
+    }
+
     /**
      * Initialises JS required fore the action menu.
      * The JS is only required once as it manages all action menu's on the page.
index c5d29ca..c4d7bb8 100644 (file)
@@ -4086,18 +4086,6 @@ EOD;
         return $html;
     }
 
-    /**
-     * Returns the header bar.
-     *
-     * @since Moodle 2.9
-     * @param array $headerinfo An array of header information, dependant on what type of header is being displayed. The following
-     *                          array example is user specific.
-     *                          heading => Override the page heading.
-     *                          user => User object.
-     *                          usercontext => user context.
-     * @param int $headinglevel What level the 'h' tag will be.
-     * @return string HTML for the header bar.
-     */
     public function context_header($headerinfo = null, $headinglevel = 1) {
         global $DB, $USER, $CFG;
         $context = $this->page->context;
@@ -4390,6 +4378,18 @@ EOD;
         }
     }
 
+    /**
+     * Render the login signup form into a nice template for the theme.
+     *
+     * @param mform $form
+     * @return string
+     */
+    public function render_login_signup_form($form) {
+        $context = $form->export_for_template($this);
+
+        return $this->render_from_template('core/signup_form_layout', $context);
+    }
+
     /**
      * Renders a progress bar.
      *
index 061335d..df546f5 100644 (file)
@@ -286,6 +286,11 @@ class moodle_page {
      */
     protected $_settingsnav = null;
 
+    /**
+     * @var flat_navigation Contains a list of nav nodes, most closely related to the current page.
+     */
+    protected $_flatnav = null;
+
     /**
      * @var navbar Contains the navbar structure.
      */
@@ -726,6 +731,18 @@ class moodle_page {
         return $this->_settingsnav;
     }
 
+    /**
+     * Returns the flat navigation object
+     * @return flat_navigation
+     */
+    protected function magic_get_flatnav() {
+        if ($this->_flatnav === null) {
+            $this->_flatnav = new flat_navigation($this);
+            $this->_flatnav->initialise();
+        }
+        return $this->_flatnav;
+    }
+
     /**
      * Returns request IP address.
      *
diff --git a/lib/templates/settings_link_page.mustache b/lib/templates/settings_link_page.mustache
new file mode 100644 (file)
index 0000000..608e649
--- /dev/null
@@ -0,0 +1,43 @@
+{{!
+    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 <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core_admin/admin_settings_links
+
+    Displays the admin tree as a list of grouped links.
+
+    Example context (json):
+    {
+        "name": "test",
+        "id": "test0",
+        "checked": true,
+        "label": "Do you like crackers?"
+    }
+}}
+
+<div class="container">
+    <div class="row">
+        <div class="col-md-6 push-md-3">
+            <div class="card">
+                <div class="card-block">
+                    {{#node}}
+                        {{> core/settings_link_page_single }}
+                    {{/node}}
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/lib/templates/settings_link_page_single.mustache b/lib/templates/settings_link_page_single.mustache
new file mode 100644 (file)
index 0000000..525e7bf
--- /dev/null
@@ -0,0 +1,39 @@
+{{!
+    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 <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core/settings_link_page_single
+
+    Displays the admin tree as a list of grouped links.
+
+    Example context (json):
+    {
+    }
+}}
+
+<ul class="nav m-l-2">
+    {{#has_action}}
+    <li class="nav-item"><a class="nav-link" href="{{{action}}}">{{text}}</a></li>
+    {{/has_action}}
+    {{^has_action}}
+    <li class="nav-item">{{text}}</li>
+    {{/has_action}}
+    {{#children.count}}
+        {{#children}}
+            {{> core/settings_link_page_single }}
+        {{/children}}
+    {{/children.count}}
+</ul>
diff --git a/lib/templates/signup_form_layout.mustache b/lib/templates/signup_form_layout.mustache
new file mode 100644 (file)
index 0000000..80bd360
--- /dev/null
@@ -0,0 +1,2 @@
+<h3>{{#str}}newaccount{{/str}}</h3>
+{{{formhtml}}}
index 1e54597..abe977e 100644 (file)
@@ -87,10 +87,11 @@ $login      = get_string('login');
 $PAGE->navbar->add($login);
 $PAGE->navbar->add($newaccount);
 
+$PAGE->set_pagelayout('login');
 $PAGE->set_title($newaccount);
 $PAGE->set_heading($SITE->fullname);
 
 echo $OUTPUT->header();
-echo $OUTPUT->heading($newaccount);
-$mform_signup->display();
+
+echo $OUTPUT->render($mform_signup);
 echo $OUTPUT->footer();
index 185cb69..07980d8 100644 (file)
@@ -30,7 +30,7 @@ require_once($CFG->libdir.'/formslib.php');
 require_once($CFG->dirroot.'/user/profile/lib.php');
 require_once($CFG->dirroot . '/user/editlib.php');
 
-class login_signup_form extends moodleform {
+class login_signup_form extends moodleform implements renderable, templatable {
     function definition() {
         global $USER, $CFG;
 
@@ -142,4 +142,21 @@ class login_signup_form extends moodleform {
         return $errors;
 
     }
+
+    /**
+     * Export this data so it can be used as the context for a mustache template.
+     *
+     * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
+     * @return array
+     */
+    public function export_for_template(renderer_base $output) {
+        ob_start();
+        $this->display();
+        $formhtml = ob_get_contents();
+        ob_end_clean();
+        $context = [
+            'formhtml' => $formhtml
+        ];
+        return $context;
+    }
 }
diff --git a/theme/boost/amd/build/blocks_drawer.min.js b/theme/boost/amd/build/blocks_drawer.min.js
new file mode 100644 (file)
index 0000000..6abe858
Binary files /dev/null and b/theme/boost/amd/build/blocks_drawer.min.js differ
diff --git a/theme/boost/amd/build/drawer.min.js b/theme/boost/amd/build/drawer.min.js
new file mode 100644 (file)
index 0000000..cf9ca55
Binary files /dev/null and b/theme/boost/amd/build/drawer.min.js differ
diff --git a/theme/boost/amd/src/drawer.js b/theme/boost/amd/src/drawer.js
new file mode 100644 (file)
index 0000000..74c307c
--- /dev/null
@@ -0,0 +1,139 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Contain the logic for a drawer.
+ *
+ * @package    theme_boost
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/custom_interaction_events', 'core/notification'],
+     function($, CustomEvents, Notification) {
+
+    var SELECTORS = {
+        TOGGLE_REGION: '[data-region="drawer-toggle"]',
+        TOGGLE_ACTION: '[data-action="toggle-drawer"]',
+        TOGGLE_TARGET: 'aria-controls',
+        TOGGLE_SIDE: 'left',
+        BODY: 'body'
+    };
+
+    /**
+     * Constructor for the Drawer.
+     *
+     * @param {object} root The root jQuery element for the modal
+     */
+    var Drawer = function() {
+
+        if (!$(SELECTORS.TOGGLE_REGION).length) {
+            Notification.exception({message: 'Page is missing a drawer toggle region'});
+        }
+        if (!$(SELECTORS.TOGGLE_ACTION).length) {
+            Notification.exception({message: 'Page is missing a drawer toggle link'});
+        }
+        $(SELECTORS.TOGGLE_REGION).each(function(index, ele) {
+            var trigger = $(ele).find(SELECTORS.TOGGLE_ACTION);
+            var hidden = trigger.attr('aria-expanded') == 'false';
+            var side = trigger.attr('data-side');
+            var body = $(SELECTORS.BODY);
+
+            if (!hidden) {
+                body.addClass('drawer-open-' + side);
+                trigger.attr('aria-expanded', 'true');
+            } else {
+                trigger.attr('aria-expanded', 'false');
+            }
+        }.bind(this));
+
+        this.registerEventListeners();
+    };
+
+    Drawer.prototype.closeAll = function() {
+        $(SELECTORS.TOGGLE_REGION).each(function(index, ele) {
+            var trigger = $(ele).find(SELECTORS.TOGGLE_ACTION);
+            var side = trigger.attr('data-side');
+            var body = $(SELECTORS.BODY);
+            var drawerid = trigger.attr('aria-controls');
+            var drawer = $(document.getElementById(drawerid));
+            var preference = trigger.attr('data-preference');
+
+            trigger.attr('aria-expanded', 'false');
+            body.removeClass('drawer-open-' + side);
+            drawer.attr('aria-hidden', 'true');
+            drawer.addClass('closed');
+            M.util.set_user_preference(preference, 'false');
+        }.bind(this));
+    };
+
+    /**
+     * Open / close the blocks drawer.
+     *
+     * @method toggleDrawer
+     * @param {Event} e
+     */
+    Drawer.prototype.toggleDrawer = function(e) {
+        var trigger = $(e.target).closest('[data-action=toggle-drawer]');
+        var drawerid = trigger.attr('aria-controls');
+        var drawer = $(document.getElementById(drawerid));
+        var body = $(SELECTORS.BODY);
+        var side = trigger.attr('data-side');
+        var preference = trigger.attr('data-preference');
+
+        body.addClass('drawer-ease');
+        var open = trigger.attr('aria-expanded') == 'true';
+        if (!open) {
+            var small = $(document).width() < 512;
+            if (small) {
+                this.closeAll();
+            }
+            // Open.
+            trigger.attr('aria-expanded', 'true');
+            drawer.attr('aria-hidden', 'false');
+            body.addClass('drawer-open-' + side);
+            drawer.removeClass('closed');
+            M.util.set_user_preference(preference, 'true');
+        } else {
+            // Close.
+            body.removeClass('drawer-open-' + side);
+            trigger.attr('aria-expanded', 'false');
+            drawer.attr('aria-hidden', 'true');
+            drawer.addClass('closed');
+            M.util.set_user_preference(preference, 'false');
+        }
+    };
+
+    /**
+     * Set up all of the event handling for the modal.
+     *
+     * @method registerEventListeners
+     */
+    Drawer.prototype.registerEventListeners = function() {
+        var body = $(SELECTORS.BODY);
+
+        CustomEvents.define(body, [CustomEvents.events.activate]);
+
+        body.on(CustomEvents.events.activate, SELECTORS.TOGGLE_ACTION, function(e, data) {
+            this.toggleDrawer(data.originalEvent);
+            data.originalEvent.preventDefault();
+        }.bind(this));
+    };
+
+    return {
+        'init': function() {
+            return new Drawer();
+        }
+    };
+});
index afe5a60..ffd652e 100644 (file)
@@ -23,6 +23,8 @@ use tabtree;
 use custom_menu_item;
 use custom_menu;
 use block_contents;
+use navigation_node;
+use action_link;
 use stdClass;
 use moodle_url;
 use preferences_groups;
@@ -73,6 +75,7 @@ class core_renderer extends \core_renderer {
     public function full_header() {
         $html = html_writer::start_tag('header', array('id' => 'page-header', 'class' => 'row'));
         $html .= html_writer::start_div('col-xs-12 p-t-1 p-b-1');
+        $html .= html_writer::div($this->context_header_settings_menu(), 'pull-xs-right context-header-settings-menu');
         $html .= $this->context_header();
         $html .= html_writer::start_div('clearfix', array('id' => 'page-navbar'));
         $html .= html_writer::tag('div', $this->navbar(), array('class' => 'breadcrumb-nav'));
@@ -319,7 +322,7 @@ class core_renderer extends \core_renderer {
      * @return string
      */
     public function body_css_classes(array $additionalclasses = array()) {
-        return $this->page->bodyclasses;
+        return $this->page->bodyclasses . ' ' . implode(' ', $additionalclasses);
     }
 
     /**
@@ -347,6 +350,9 @@ class core_renderer extends \core_renderer {
             }
         }
 
+        if ($menu->is_empty()) {
+            return '';
+        }
         $context = $menu->export_for_template($this);
 
         // We do not want the icon with the caret, the caret is added by Bootstrap.
@@ -441,4 +447,152 @@ class core_renderer extends \core_renderer {
         }
         return $this->render_from_template('core/pix_icon', $data);
     }
+
+    /**
+     * Renders the login form.
+     *
+     * @param \core_auth\output\login $form The renderable.
+     * @return string
+     */
+    public function render_login(\core_auth\output\login $form) {
+        global $SITE;
+
+        $context = $form->export_for_template($this);
+
+        // Override because rendering is not supported in template yet.
+        $context->cookieshelpiconformatted = $this->help_icon('cookiesenabled');
+        $context->errorformatted = $this->error_text($context->error);
+        $url = $this->get_logo_url();
+        if ($url) {
+            $url = $url->out(false);
+        }
+        $context->logourl = $url;
+        $context->sitename = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID)));
+
+        return $this->render_from_template('core/login', $context);
+    }
+
+    /**
+     * Render the login signup form into a nice template for the theme.
+     *
+     * @param mform $form
+     * @return string
+     */
+    public function render_login_signup_form($form) {
+        global $SITE;
+
+        $context = $form->export_for_template($this);
+        $url = $this->get_logo_url();
+        if ($url) {
+            $url = $url->out(false);
+        }
+        $context['logourl'] = $url;
+        $context['sitename'] = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID)));
+
+        return $this->render_from_template('core/signup_form_layout', $context);
+    }
+
+    /**
+     * This is an optional menu that can be added to a layout by a theme. It contains the
+     * menu for the course administration, only on the course main page.
+     *
+     * @return string
+     */
+    public function context_header_settings_menu() {
+        $context = $this->page->context;
+        $menu = new action_menu();
+        if ($context->contextlevel == CONTEXT_COURSE) {
+            // Get the course admin node from the settings navigation.
+            $items = $this->page->navbar->get_items();
+            $node = end($items);
+            if (($node->type == navigation_node::TYPE_COURSE)) {
+                $node = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE);
+                if ($node) {
+                    // Build an action menu based on the visible nodes from this navigation tree.
+                    $this->build_action_menu_from_navigation($menu, $node, false, true);
+
+                    $text = get_string('courseadministration');
+                    $url = new moodle_url('/course/admin.php', array('courseid' => $this->page->course->id));
+                    $link = new action_link($url, $text, null, null, new pix_icon('t/edit', $text));
+                    $menu->add_secondary_action($link);
+                }
+            }
+        } else if ($context->contextlevel == CONTEXT_USER) {
+            $items = $this->page->navbar->get_items();
+            $node = end($items);
+            if ($node->key === 'myprofile') {
+                // Get the course admin node from the settings navigation.
+                $node = $this->page->settingsnav->find('useraccount', navigation_node::TYPE_CONTAINER);
+                if ($node) {
+                    // Build an action menu based on the visible nodes from this navigation tree.
+                    $this->build_action_menu_from_navigation($menu, $node);
+                }
+            }
+        }
+        return $this->render($menu);
+    }
+
+    /**
+     * This is an optional menu that can be added to a layout by a theme. It contains the
+     * menu for the most specific thing from the settings block. E.g. Module administration.
+     *
+     * @return string
+     */
+    public function region_main_settings_menu() {
+        $context = $this->page->context;
+        $menu = new action_menu();
+
+        if ($context->contextlevel == CONTEXT_MODULE) {
+
+            $node = $this->page->navigation->find_active_node();
+            if (($node->type == navigation_node::TYPE_ACTIVITY ||
+                    $node->type == navigation_node::TYPE_RESOURCE)) {
+                // Get the course admin node from the settings navigation.
+                $node = $this->page->settingsnav->find('modulesettings', navigation_node::TYPE_SETTING);
+                if ($node) {
+                    // Build an action menu based on the visible nodes from this navigation tree.
+                    $this->build_action_menu_from_navigation($menu, $node);
+                }
+            }
+        }
+        return $this->render($menu);
+    }
+
+    /**
+     * Take a node in the nav tree and make an action menu out of it.
+     * The links are injected in the action menu.
+     *
+     * @param action_menu $menu
+     * @param navigation_node $node
+     * @param boolean $indent
+     * @param boolean $onlytopleafnodes
+     */
+    private function build_action_menu_from_navigation(action_menu $menu,
+                                                       navigation_node $node,
+                                                       $indent = false,
+                                                       $onlytopleafnodes = false) {
+        // Build an action menu based on the visible nodes from this navigation tree.
+        foreach ($node->children as $menuitem) {
+            if ($menuitem->display) {
+                if ($onlytopleafnodes && $menuitem->children->count()) {
+                    continue;
+                }
+                if ($menuitem->action) {
+                    $text = $menuitem->text;
+                    $link = new action_link($menuitem->action, $menuitem->text, null, null, $menuitem->icon);
+                    if ($indent) {
+                        $link->add_class('m-l-1');
+                    }
+                } else {
+                    if ($onlytopleafnodes) {
+                        continue;
+                    }
+                    $link = $menuitem->text;
+                }
+                $menu->add_secondary_action($link);
+                $this->build_action_menu_from_navigation($menu, $menuitem, true);
+            }
+        }
+    }
+
 }
index f52949d..65f04bf 100644 (file)
@@ -88,7 +88,7 @@ $THEME->layouts = [
         'defaultregion' => 'side-pre',
     ),
     'login' => array(
-        'file' => 'columns1.php',
+        'file' => 'login.php',
         'regions' => array(),
         'options' => array('langmenu' => true),
     ),
index 618eb77..8b0b482 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * A one column layout for the boost theme.
+ * A two column layout for the boost theme.
  *
  * @package   theme_boost
  * @copyright 2016 Damyon Wiese
 
 defined('MOODLE_INTERNAL') || die();
 
+user_preference_allow_ajax_update('drawer-open-blocks', PARAM_ALPHA);
+user_preference_allow_ajax_update('drawer-open-nav', PARAM_ALPHA);
+
+$navdraweropen = (get_user_preferences('drawer-open-nav', 'true') == 'true');
+$blocksdraweropen = (get_user_preferences('drawer-open-blocks', 'true') == 'true');
+$extraclasses = [];
+if ($blocksdraweropen) {
+    $extraclasses[] = 'drawer-open-right';
+}
+if ($navdraweropen) {
+    $extraclasses[] = 'drawer-open-left';
+}
+$bodyattributes = $OUTPUT->body_attributes($extraclasses);
+
 $templatecontext = [
     'sitename' => format_string($SITE->shortname, true, array('context' => context_course::instance(SITEID))),
-    'output' => $OUTPUT
+    'output' => $OUTPUT,
+    'sidepreblocks' => $OUTPUT->blocks('side-pre'),
+    'bodyattributes' => $bodyattributes,
+    'blocksdraweropen' => $blocksdraweropen,
+    'navdraweropen' => $navdraweropen
 ];
 
+$templatecontext['flatnavigation'] = $PAGE->flatnav;
 echo $OUTPUT->render_from_template('theme_boost/columns1', $templatecontext);
+
index 90c4fc1..e0664b9 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+user_preference_allow_ajax_update('drawer-open-blocks', PARAM_ALPHA);
+user_preference_allow_ajax_update('drawer-open-nav', PARAM_ALPHA);
+
+$navdraweropen = (get_user_preferences('drawer-open-nav', 'true') == 'true');
+$blocksdraweropen = (get_user_preferences('drawer-open-blocks', 'true') == 'true');
+$extraclasses = [];
+if ($blocksdraweropen) {
+    $extraclasses[] = 'drawer-open-right';
+}
+if ($navdraweropen) {
+    $extraclasses[] = 'drawer-open-left';
+}
+$bodyattributes = $OUTPUT->body_attributes($extraclasses);
+
 $templatecontext = [
     'sitename' => format_string($SITE->shortname, true, array('context' => context_course::instance(SITEID))),
     'output' => $OUTPUT,
-    'sidepreblocks' => $OUTPUT->blocks('side-pre', 'col-md-4 pull-md-8'),
+    'sidepreblocks' => $OUTPUT->blocks('side-pre'),
+    'bodyattributes' => $bodyattributes,
+    'blocksdraweropen' => $blocksdraweropen,
+    'navdraweropen' => $navdraweropen
 ];
 
+$templatecontext['flatnavigation'] = $PAGE->flatnav;
 echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext);
+
diff --git a/theme/boost/layout/login.php b/theme/boost/layout/login.php
new file mode 100644 (file)
index 0000000..0ae675d
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+// 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 <http://www.gnu.org/licenses/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A two column layout for the boost theme.
+ *
+ * @package   theme_boost
+ * @copyright 2016 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$bodyattributes = $OUTPUT->body_attributes();
+
+$templatecontext = [
+    'sitename' => format_string($SITE->shortname, true, array('context' => context_course::instance(SITEID))),
+    'output' => $OUTPUT,
+    'bodyattributes' => $bodyattributes
+];
+
+echo $OUTPUT->render_from_template('theme_boost/login', $templatecontext);
+
index c121790..b0ae36a 100644 (file)
@@ -27,8 +27,7 @@ defined('MOODLE_INTERNAL') || die();
 $templatecontext = [
     'sitename' => format_string($SITE->shortname, true, array('context' => context_course::instance(SITEID))),
     'output' => $OUTPUT,
-    'sidepreblocks' => $OUTPUT->blocks('side-pre', 'col-md-4 pull-md-8 col-lg-3 pull-lg-9'),
-    'sidepostblocks' => $OUTPUT->blocks('side-post', 'col-md-3')
+    'sidepreblocks' => $OUTPUT->blocks('side-pre'),
 ];
 
 echo $OUTPUT->render_from_template('theme_boost/secure', $templatecontext);
diff --git a/theme/boost/pix/blocksdrawer.png b/theme/boost/pix/blocksdrawer.png
new file mode 100644 (file)
index 0000000..a296436
Binary files /dev/null and b/theme/boost/pix/blocksdrawer.png differ
diff --git a/theme/boost/pix/blocksdrawer.svg b/theme/boost/pix/blocksdrawer.svg
new file mode 100644 (file)
index 0000000..eac1f21
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<style type="text/css">
+       .st0{fill:#373A3C;}
+</style>
+<path class="st0" d="M40,3.9v24.3c0,1-0.3,1.8-1,2.5s-1.5,1-2.5,1H3.6c-1,0-1.8-0.3-2.5-1s-1-1.5-1-2.5V3.9c0-1,0.3-1.8,1-2.5
+       s1.5-1,2.5-1h32.9c1,0,1.8,0.4,2.5,1C39.7,2.1,40,2.9,40,3.9z M37.1,28.2V9.6c0-0.2-0.1-0.4-0.2-0.5c-0.1-0.1-0.3-0.2-0.5-0.2H3.6
+       C3.4,8.9,3.2,9,3.1,9.1C2.9,9.3,2.9,9.4,2.9,9.6v18.6c0,0.2,0.1,0.4,0.2,0.5c0.1,0.1,0.3,0.2,0.5,0.2h32.9c0.2,0,0.4-0.1,0.5-0.2
+       S37.1,28.4,37.1,28.2z M8.6,12.5v1.4c0,0.2-0.1,0.4-0.2,0.5c-0.1,0.1-0.3,0.2-0.5,0.2H6.4c-0.2,0-0.4-0.1-0.5-0.2
+       c-0.1-0.1-0.2-0.3-0.2-0.5v-1.4c0-0.2,0.1-0.4,0.2-0.5s0.3-0.2,0.5-0.2h1.4c0.2,0,0.4,0.1,0.5,0.2C8.5,12.1,8.6,12.3,8.6,12.5z
+        M8.6,18.2v1.4c0,0.2-0.1,0.4-0.2,0.5c-0.1,0.1-0.3,0.2-0.5,0.2H6.4c-0.2,0-0.4-0.1-0.5-0.2s-0.2-0.3-0.2-0.5v-1.4
+       c0-0.2,0.1-0.4,0.2-0.5s0.3-0.2,0.5-0.2h1.4c0.2,0,0.4,0.1,0.5,0.2C8.5,17.9,8.6,18,8.6,18.2z M8.6,23.9v1.4c0,0.2-0.1,0.4-0.2,0.5
+       C8.3,25.9,8.1,26,7.9,26H6.4c-0.2,0-0.4-0.1-0.5-0.2s-0.2-0.3-0.2-0.5v-1.4c0-0.2,0.1-0.4,0.2-0.5s0.3-0.2,0.5-0.2h1.4
+       c0.2,0,0.4,0.1,0.5,0.2C8.5,23.6,8.6,23.7,8.6,23.9z M34.3,12.5v1.4c0,0.2-0.1,0.4-0.2,0.5c-0.1,0.1-0.3,0.2-0.5,0.2H12.1
+       c-0.2,0-0.4-0.1-0.5-0.2c-0.1-0.1-0.2-0.3-0.2-0.5v-1.4c0-0.2,0.1-0.4,0.2-0.5c0.1-0.1,0.3-0.2,0.5-0.2h21.4c0.2,0,0.4,0.1,0.5,0.2
+       C34.2,12.1,34.3,12.3,34.3,12.5z M34.3,18.2v1.4c0,0.2-0.1,0.4-0.2,0.5c-0.1,0.1-0.3,0.2-0.5,0.2H12.1c-0.2,0-0.4-0.1-0.5-0.2
+       s-0.2-0.3-0.2-0.5v-1.4c0-0.2,0.1-0.4,0.2-0.5c0.1-0.1,0.3-0.2,0.5-0.2h21.4c0.2,0,0.4,0.1,0.5,0.2C34.2,17.9,34.3,18,34.3,18.2z
+        M34.3,23.9v1.4c0,0.2-0.1,0.4-0.2,0.5C34,25.9,33.8,26,33.6,26H12.1c-0.2,0-0.4-0.1-0.5-0.2s-0.2-0.3-0.2-0.5v-1.4
+       c0-0.2,0.1-0.4,0.2-0.5c0.1-0.1,0.3-0.2,0.5-0.2h21.4c0.2,0,0.4,0.1,0.5,0.2C34.2,23.6,34.3,23.7,34.3,23.9z"/>
+</svg>
index 9080050..c92deac 100644 (file)
@@ -17,14 +17,17 @@ $breadcrumb-divider-rtl: "◀" !default;
 @import "moodle/icons";
 @import "moodle/admin";
 @import "moodle/blocks";
+@import "moodle/bootswatch";
 @import "moodle/calendar";
 @import "moodle/course";
+@import "moodle/drawer";
 @import "moodle/filemanager";
 @import "moodle/message";
 @import "moodle/question";
 @import "moodle/user";
 @import "moodle/search";
 @import "moodle/forms";
+@import "moodle/login";
 @import "moodle/modules";
 @import "moodle/chat";
 @import "moodle/reports";
index f1504fa..9b15639 100644 (file)
         }
     }
 }
+
+#nav-drawer.closed {
+    left: -285px;
+}
+#blocks-drawer.closed {
+    right: -285px;
+}
+
+/* Use a variable for the drawer background colors. */
+$drawer-bg: $gray-lighter !default;
+
+[data-region="drawer"] {
+    position: fixed;
+    padding: 20px;
+    width: 265px;
+    top: ($navbar-padding-y * 2) + ($navbar-brand-padding-y * 2) + $font-size-lg * 1.5;
+    height: 100%;
+    overflow-y: scroll;
+    -webkit-overflow-scrolling: touch;
+    z-index: 100;
+    background-color: $drawer-bg;
+    -webkit-transition: right 0.5s ease, left 0.5s ease;
+       -moz-transition: right 0.5s ease, left 0.5s ease;
+            transition: right 0.5s ease, left 0.5s ease;
+}
+#nav-drawer {
+    right: auto;
+    left: 0px;
+}
+#blocks-drawer {
+    left: auto;
+    right: 0px;
+}
+body.drawer-ease {
+    -webkit-transition: margin-left 0.5s ease, margin-right 0.5s ease;
+       -moz-transition: margin-left 0.5s ease, margin-right 0.5s ease;
+            transition: margin-left 0.5s ease, margin-right 0.5s ease;
+}
+
+body.drawer-open-left {
+    @include media-breakpoint-up(md) {
+        margin-left: 265px;
+    }
+}
+body.drawer-open-right {
+    @include media-breakpoint-up(md) {
+        margin-right: 265px;
+    }
+}
diff --git a/theme/boost/scss/moodle/bootswatch.scss b/theme/boost/scss/moodle/bootswatch.scss
new file mode 100644 (file)
index 0000000..60afab5
--- /dev/null
@@ -0,0 +1,10 @@
+// Adjustments to make bootstrap 4 work better with some variables from bootstrap 3 bootswatches
+$navbar-height: 60px !default;
+
+.navbar {
+    max-height: $navbar-height;
+}
+
+.navbar-brand {
+    margin-top: (($font-size-base * $line-height-base) - ($font-size-lg * $line-height-base)) / 2;
+}
index ffa9548..12443b5 100644 (file)
@@ -1,5 +1,20 @@
 /* core.less */
 
+
+.context-header-settings-menu,
+.region-main-settings-menu {
+    float: right;
+    width: 4em;
+    height: 2em;
+    display: block;
+}
+
+.context-header-settings-menu .dropdown-toggle > .icon,
+.region-main-settings-menu .dropdown-toggle > .icon {
+    height: 24px;
+    width: auto;
+}
+
 /** Page layout CSS starts **/
 .layout-option-noheader #page-header,
 .layout-option-nonavbar #page-navbar,
@@ -1227,11 +1242,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
     }
 }
 
-/** Moodle modals. */
-.modal.show {
-    display: block;
-}
-
 /* Moodle Dialogue Settings (moodle-core-dialogue)  */
 .moodle-dialogue-base .moodle-dialogue-lightbox {
     background-color: $gray;
@@ -1920,38 +1930,6 @@ img#persona_signin {
 .breadcrumb-nav .breadcrumb {
     margin: 0;
 }
-/** Navbar */
-.navbar-brand {
-    .logo {
-        display: inline-block;
-        margin: -$navbar-brand-padding-y 0;
-    }
-
-    .site-name {
-        display: inline-block;
-    }
-
-    &.has-logo {
-        .site-name {
-            margin-left: $spacer / 2;
-        }
-    }
-}
-
-@include media-breakpoint-down(xs) {
-    .navbar {
-        .navbar-brand {
-            max-width: 80%;
-            margin-right: 0;
-
-            &.has-logo {
-                .site-name {
-                    display: none;
-                }
-            }
-        }
-    }
-}
 
 /** Page header */
 #page-header {
@@ -2207,3 +2185,18 @@ ul {
         }
     }
 }
+
+#region-flat-nav {
+    padding-right: 0;
+    padding-left: 0;
+    .nav {
+        margin-right: $grid-gutter-width / 2;
+        background-color: $card-bg;
+    }
+    @include media-breakpoint-down(sm) {
+        .nav {
+            margin-top: $grid-gutter-width;
+            margin-right: 0;
+        }
+    }
+}
index 3e7e55b..1e0a54a 100644 (file)
@@ -54,7 +54,9 @@
 
         &.right {
             float: right;
+            clear: right;
         }
+        margin-top: 0.5rem;
     }
 
     .spinner {
@@ -1204,3 +1206,8 @@ span.editinstructions {
         }
     }
 }
+
+.page-settings-menu .menubar > a > .icon {
+    width: auto;
+    height: 32px;
+}
diff --git a/theme/boost/scss/moodle/drawer.scss b/theme/boost/scss/moodle/drawer.scss
new file mode 100644 (file)
index 0000000..7c215e1
--- /dev/null
@@ -0,0 +1,75 @@
+/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
+$fixed-header-y: $navbar-height;
+
+$drawer-width: 285px;
+$drawer-padding-x: 20px;
+$drawer-padding-y: 20px;
+$drawer-offscreen-gutter: 20px;
+
+:target {
+  padding-top: ($fixed-header-y + 30px) ! important; /* negative fixed header height */
+  margin-top: -$fixed-header-y ! important; /* negative fixed header height */
+}
+
+#nav-drawer.closed {
+    left: - ($drawer-width + $drawer-offscreen-gutter);
+}
+#blocks-drawer.closed {
+    right: - ($drawer-width + $drawer-offscreen-gutter);
+}
+#page {
+    margin-top: $navbar-height;
+}
+
+/* Use a variable for the drawer background colors. */
+$drawer-bg: $gray-lighter !default;
+
+[data-region="drawer"] {
+    position: fixed;
+    padding: $drawer-padding-x $drawer-padding-y;
+    width: $drawer-width;
+    top: $fixed-header-y;
+    height: 100%;
+    overflow-y: scroll;
+    -webkit-overflow-scrolling: touch;
+    z-index: $zindex-dropdown - 1;
+    background-color: $drawer-bg;
+    -webkit-transition: right 0.5s ease, left 0.5s ease;
+       -moz-transition: right 0.5s ease, left 0.5s ease;
+            transition: right 0.5s ease, left 0.5s ease;
+    .dropdown-menu {
+        max-width: $drawer-width - $drawer-padding-x * 4;
+    }
+    .dropdown-item {
+        max-width: 100%;
+        overflow-x: hidden;
+        text-overflow: ellipsis;
+    }
+}
+#nav-drawer {
+    right: auto;
+    left: 0px;
+}
+#blocks-drawer {
+    left: auto;
+    right: 0px;
+}
+#page {
+    margin-top: $fixed-header-y;
+}
+body.drawer-ease {
+    -webkit-transition: margin-left 0.5s ease, margin-right 0.5s ease;
+       -moz-transition: margin-left 0.5s ease, margin-right 0.5s ease;
+            transition: margin-left 0.5s ease, margin-right 0.5s ease;
+}
+
+body.drawer-open-left {
+    @include media-breakpoint-up(lg) {
+        margin-left: $drawer-width;
+    }
+}
+body.drawer-open-right {
+    @include media-breakpoint-up(lg) {
+        margin-right: $drawer-width;
+    }
+}
index eb2f802..70887e8 100644 (file)
@@ -36,3 +36,7 @@ a:first-of-type > .icon {
         margin-left: 0;
     }
 }
+
+[data-action=toggle-drawer] .icon {
+    margin: 0px;
+}
diff --git a/theme/boost/scss/moodle/login.scss b/theme/boost/scss/moodle/login.scss
new file mode 100644 (file)
index 0000000..c96744b
--- /dev/null
@@ -0,0 +1,8 @@
+.pagelayout-login .card-img-top {
+    max-width: 100%;
+}
+
+.pagelayout-login #region-main {
+    border: 0;
+    background-color: inherit;
+}
index c61bb46..e7224c3 100644 (file)
@@ -18,6 +18,7 @@ $brand-info:                #9954bb !default;
 $brand-warning:             #ff8800 !default;
 $brand-danger:              #ff4136 !default;
 $brand-inverse:             $gray-dark !default;
+$navbar-height:             50px !default;
 
 // Spacing
 $spacer:   1rem !default;
@@ -166,26 +167,6 @@ $breadcrumb-divider-rtl: "/" !default;
     border-top: none;
 }
 
-/**
- * Login page.
- */
-.pagelayout-login {
-    &.notloggedin {
-        #region-main {
-            background-color: transparent;
-            border: 0;
-            padding: 0;
-        }
-        .login-wrapper {
-            background-color: #fff;
-            border: $border-width solid $card-border-color;
-        }
-    }
-    #notice {
-        text-align: center;
-    }
-}
-
 /**
  * Dashboard styling.
  */
@@ -239,3 +220,23 @@ $breadcrumb-divider-rtl: "/" !default;
 .form-inline .form-group {
     margin-top: 0;
 }
+
+
+// Custom transitions for open/close of menus.
+.dropdown-menu {
+    display: block;
+    max-height: 0px;
+    overflow: hidden;
+    opacity: 0;
+    -webkit-transition: max-height 1s ease, opacity 0.4s ease;
+       -moz-transition: max-height 1s ease, opacity 0.4s ease;
+            transition: max-height 1s ease, opacity 0.4s ease;
+}
+.open {
+  // Show the menu
+  > .dropdown-menu {
+    max-height: 2048px;
+    opacity: 100;
+  }
+}
+
diff --git a/theme/boost/templates/blocks-drawer.mustache b/theme/boost/templates/blocks-drawer.mustache
new file mode 100644 (file)
index 0000000..e353e03
--- /dev/null
@@ -0,0 +1,3 @@
+<div id="blocks-drawer" data-region="drawer" class="moodle-has-zindex {{^blocksdraweropen}}closed{{/blocksdraweropen}}" aria-hidden="{{#blocksdraweropen}}false{{/blocksdraweropen}}{{^blocksdraweropen}}true{{/blocksdraweropen}}">
+    {{{ sidepreblocks }}}
+</div>
index 67dbd8a..c96e422 100644 (file)
@@ -7,7 +7,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 </head>
 
-<body {{{ output.body_attributes }}}>
+<body {{{ bodyattributes }}}>
 
 <div id="page-wrapper">
 
 
     {{>theme_boost/header}}
 
-    <div id="page" class="container">
+    <div id="page" class="container-fluid">
         {{{ output.full_header }}}
 
         <div id="page-content" class="row">
-            <div id="region-main-box" class="col-xs-12">
-                <div class="row">
-                    <section id="region-main" class="col-xs-12">
-                        {{{ output.course_content_header }}}
-                        {{{ output.main_content }}}
-                        {{{ output.course_content_footer }}}
-                    </section>
-                </div>
+            <div id="region-main-box">
+                <section id="region-main" class="col-xs-12">
+                    <span class="pull-xs-right page-settings-menu">
+                        {{{ output.page_settings_menu }}}
+                    </span>
+                    {{{ output.course_content_header }}}
+                    {{{ output.main_content }}}
+                    {{{ output.course_content_footer }}}
+                </section>
             </div>
         </div>
     </div>
+    {{> theme_boost/blocks-drawer }}
+    {{> theme_boost/nav-drawer }}
 </div>
 <footer id="page-footer" class="p-y-1">
     <div class="container">
@@ -42,7 +45,6 @@
         {{{ output.login_info }}}
         {{{ output.home_link }}}
         {{{ output.standard_footer_html }}}
-
         {{{ output.standard_end_of_body_html }}}
     </div>
 </footer>
@@ -51,4 +53,5 @@
 </html>
 {{#js}}
 require(['theme_boost/loader'], function() {});
+require(['theme_boost/drawer'], function(mod) {mod.init();});
 {{/js}}
index 98668cc..9b0335c 100644 (file)
@@ -7,7 +7,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 </head>
 
-<body {{{ output.body_attributes }}}>
+<body {{{ bodyattributes }}}>
 
 <div id="page-wrapper">
 
 
     {{>theme_boost/header}}
 
-    <div id="page" class="container">
+    <div id="page" class="container-fluid">
         {{{ output.full_header }}}
 
         <div id="page-content" class="row">
-            <div id="region-main-box" class="col-md-8 push-md-4">
-                <div class="row">
-                    <section id="region-main" class="col-xs-12">
-                        {{{ output.course_content_header }}}
-                        {{{ output.main_content }}}
-                        {{{ output.course_content_footer }}}
-                    </section>
-                </div>
+            <div id="region-main-box">
+                <section id="region-main" class="col-xs-12">
+                    <span class="pull-xs-right region-main-settings-menu">
+                        {{{ output.region_main_settings_menu }}}
+                    </span>
+                    {{{ output.course_content_header }}}
+                    {{{ output.main_content }}}
+                    {{{ output.course_content_footer }}}
+                </section>
             </div>
-            {{{ sidepreblocks }}}
         </div>
-
     </div>
+    {{> theme_boost/blocks-drawer }}
+    {{> theme_boost/nav-drawer }}
 </div>
 <footer id="page-footer" class="p-y-1">
     <div class="container">
@@ -52,4 +53,5 @@
 </html>
 {{#js}}
 require(['theme_boost/loader'], function() {});
+require(['theme_boost/drawer'], function(mod) {mod.init();});
 {{/js}}
index 77a7a7c..eb50c46 100644 (file)
     Moodle template for the login page.
 }}
 
-<div class="container-fluid login-page">
+<div class="m-y-3 hidden-sm-down"></div>
 <div class="row">
-    <div class="col-sm-12 col-md-12 col-lg-10 offset-lg-1 col-xl-8 offset-xl-2 login-wrapper">
-        <div class="row">
-            {{#hasinstructions}}
-            <div class="col-sm-6 p-b-1 login-form">
-            {{/hasinstructions}}
-            {{^hasinstructions}}
-            <div class="col-sm-6 offset-sm-3 col-md-4 offset-md-4 p-b-1 login-form">
-            {{/hasinstructions}}
-
-                <h2 class="m-t-1">{{#str}} login {{/str}}</h2>
-
-                {{#cansignup}}
-                    <div class="sr-only">
-                        <a href="{{signupurl}}">{{#str}} tocreatenewaccount {{/str}}</a>
-                    </div>
-                {{/cansignup}}
-
-                {{#error}}
-                    <div class="loginerrors m-t-1">
-                        <a href="#" id="loginerrormessage" class="accesshide">{{error}}</a>
-                        <div class="alert alert-danger" role="alert">{{error}}</div>
-                    </div>
-                {{/error}}
+<div class="col-xl-6 push-xl-3 m-2-md col-sm-8 push-sm-2">
+<div class="card">
+    {{#logourl}}
+        <img class="card-img-top" src="{{logourl}}" alt="" role="presentation"/>
+    {{/logourl}}
+    <div class="card-block">
+        <div class="card-title">
+            <h2 class="m-t-1">{{sitename}}</h2>
+        </div>
+
+        {{#cansignup}}
+            <div class="sr-only">
+                <a href="{{signupurl}}">{{#str}} tocreatenewaccount {{/str}}</a>
+            </div>
+        {{/cansignup}}
 
+        {{#error}}
+            <div class="loginerrors m-t-1">
+                <a href="#" id="loginerrormessage" class="accesshide">{{error}}</a>
+                <div class="alert alert-danger" role="alert">{{error}}</div>
+            </div>
+        {{/error}}
+
+        <div class="row">
+            <div class="col-md-4 push-md-1">
                 <form class="m-t-1" action="{{loginurl}}" method="post" id="login" {{^passwordautocomplete}}autocomplete="off"{{/passwordautocomplete}}>
                     <input id="anchor" type="hidden" name="anchor" value="">
                     <script>document.getElementById('anchor').value = location.hash;</script>
                     {{/rememberusername}}
 
                     <button type="submit" class="btn btn-primary btn-block m-t-1" id="loginbtn">{{#str}}login{{/str}}</button>
-
-                    <div class="forgetpass m-t-1">
-                        <p><a href="{{forgotpasswordurl}}">{{#str}}forgotten{{/str}}</a></p>
-                    </div>
-
-                    <div class="m-t-1">
-                        {{#str}} cookiesenabled {{/str}}
-                        {{{cookieshelpiconformatted}}}
-                    </div>
-
                 </form>
+            </div>
 
-                {{#canloginasguest}}
-                    <div class="m-t-2">
-                        <p>{{#str}}someallowguest{{/str}}</p>
-                        <form action="{{loginurl}}" method="post" id="guestlogin">
-                            <input type="hidden" name="username" value="guest" />
-                            <input type="hidden" name="password" value="guest" />
-                            <button class="btn btn-secondary btn-block" type="submit">{{#str}}loginguest{{/str}}</button>
-                        </form>
-                    </div>
-                {{/canloginasguest}}
-
-                {{#hasidentityproviders}}
-                    <h6 class="m-t-2">{{#str}} potentialidps, auth {{/str}}</h6>
-                    <div class="potentialidplist" class="m-t-1">
-                        {{#identityproviders}}
-                            <div class="potentialidp">
-                                <a href="{{url}}" title={{#quote}}{{name}}{{/quote}}>{{#icon}}{{>core/pix_icon}}{{/icon}}{{name}}</a>
-                            </div>
-                        {{/identityproviders}}
-                    </div>
-                {{/hasidentityproviders}}
+            <div class="col-md-4 push-md-3">
+                <div class="forgetpass m-t-1">
+                    <p><a href="{{forgotpasswordurl}}">{{#str}}forgotten{{/str}}</a></p>
+                </div>
 
+                <div class="m-t-1">
+                    {{#str}} cookiesenabled {{/str}}
+                    {{{cookieshelpiconformatted}}}
+                </div>
+            {{#canloginasguest}}
+                <div class="m-t-2">
+                    <p>{{#str}}someallowguest{{/str}}</p>
+                    <form action="{{loginurl}}" method="post" id="guestlogin">
+                        <input type="hidden" name="username" value="guest" />
+                        <input type="hidden" name="password" value="guest" />
+                        <button class="btn btn-secondary btn-block" type="submit">{{#str}}loginguest{{/str}}</button>
+                    </form>
+                </div>
+            {{/canloginasguest}}
+
+        {{#hasidentityproviders}}
+                <h6 class="m-t-2">{{#str}} potentialidps, auth {{/str}}</h6>
+                <div class="potentialidplist" class="m-t-1">
+                    {{#identityproviders}}
+                        <div class="potentialidp">
+                            <a href="{{url}}" title={{#quote}}{{name}}{{/quote}}>{{#icon}}{{>core/pix_icon}}{{/icon}}{{name}}</a>
+                        </div>
+                    {{/identityproviders}}
+                </div>
+        {{/hasidentityproviders}}
             </div>
+        </div>
+    </div>
+</div>
+</div>
+</div>
 
-            {{#hasinstructions}}
-                <div class="col-sm-6 p-b-1 login-instructions">
-                    <h2 class="m-t-1">{{#str}}firsttime{{/str}}</h2>
-                    <div>
-                        {{{instructions}}}
-                        {{#cansignup}}
-                            <form class="m-t-1" action="{{signupurl}}" method="get" id="signup">
-                                <button type="submit" class="btn btn-secondary">{{#str}}startsignup{{/str}}</button>
-                            </form>
-                        {{/cansignup}}
-                    </div>
-                </div>
-            {{/hasinstructions}}
 
+{{#hasinstructions}}
+<div class="row">
+<div class="col-xl-6 push-xl-3 m-2-md col-sm-8 push-sm-2">
+<div class="card">
+    <div class="card-block">
+        <div class="card-title">
+            <h2>{{#str}}firsttime{{/str}}</h2>
+        </div>
+        <div>
+        {{{instructions}}}
+        {{#cansignup}}
+            <form class="m-t-1" action="{{signupurl}}" method="get" id="signup">
+                <button type="submit" class="btn btn-secondary">{{#str}}startsignup{{/str}}</button>
+            </form>
+        {{/cansignup}}
         </div>
     </div>
 </div>
 </div>
+</div>
+{{/hasinstructions}}
+
 
 {{#js}}
     require(['jquery', 'core/yui'], function($, Y) {
index a9a407d..1a424ca 100644 (file)
@@ -1,10 +1,10 @@
 <ol class="breadcrumb" role="navigation">
     {{#get_items}}
         {{#has_action}}
-        <li class="breadcrumb-item {{#is_active}}active{{/is_active}}"><a href="{{{action}}}" {{#get_title}}title="{{get_title}}"{{/get_title}}>{{text}}</a></li>
+        <li class="breadcrumb-item"><a href="{{{action}}}" {{#get_title}}title="{{get_title}}"{{/get_title}}>{{text}}</a></li>
         {{/has_action}}
         {{^has_action}}
-        <li class="breadcrumb-item {{#is_active}}active{{/is_active}}">{{text}}</li>
+        <li class="breadcrumb-item">{{text}}</li>
         {{/has_action}}
     {{/get_items}}
 </ol>
diff --git a/theme/boost/templates/core/signup_form_layout.mustache b/theme/boost/templates/core/signup_form_layout.mustache
new file mode 100644 (file)
index 0000000..0f59efe
--- /dev/null
@@ -0,0 +1,17 @@
+<div class="container-fluid">
+    <div class="row">
+        <div class="col-md-8 push-md-2 col-xl-6 push-xl-3">
+            <div class="card">
+                {{#logourl}}
+                    <img class="card-img-top" src="{{logourl}}">
+                {{/logourl}}
+                <div class="card-block">
+                    <div class="card-title">
+                        <h3>{{#str}}newaccount{{/str}}</h3>
+                    </div>
+                    {{{formhtml}}}
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/theme/boost/templates/flat_navigation.mustache b/theme/boost/templates/flat_navigation.mustache
new file mode 100644 (file)
index 0000000..93c1b92
--- /dev/null
@@ -0,0 +1,19 @@
+<nav class="nav nav-stacked nav-pills">
+{{# flatnavigation }}
+    {{#get_smscreenonly}}
+    <div class="hidden-lg-up">
+    {{/get_smscreenonly}}
+    {{#showdivider}}
+    <div class="nav-item"><hr></div>
+    {{/showdivider}}
+    {{#action}}
+    <div class="nav-item"><a class="nav-link {{#isactive}}active{{/isactive}} m-l-{{get_indent}}" href="{{{action}}}">{{text}}</a></div>
+    {{/action}}
+    {{^action}}
+    <div class="nav-item"><span class="nav-link m-l-{{get_indent}}">{{text}}</span></div>
+    {{/action}}
+    {{#get_smscreenonly}}
+    </div>
+    {{/get_smscreenonly}}
+{{/ flatnavigation }}
+</div>
index d6e50a9..2747517 100644 (file)
 {{!
     Page header.
 }}
-<header role="banner" class="navbar navbar-full navbar-light bg-faded navbar-static-top moodle-has-zindex">
+<header role="banner" class="pos-f-t navbar navbar-full navbar-light bg-faded navbar-static-top moodle-has-zindex">
 
-    <div class="container">
+    <div class="container-fluid">
 
-        <button class="navbar-toggler pull-xs-right hidden-sm-up" data-toggle="collapse" data-target="#bd-main-nav" aria-expanded="false" aria-controls="bd-main-nav" type="button">☰<span class="sr-only">{{#str}}expand{{/str}}</span></button>
+        <div data-region="drawer-toggle">
+        <button aria-expanded="{{#navdraweropen}}true{{/navdraweropen}}{{^navdraweropen}}false{{/navdraweropen}}" aria-controls="nav-drawer" type="button" class="btn pull-xs-left m-r-1 btn-secondary" data-action="toggle-drawer" data-side="left" data-preference="drawer-open-nav">&#9776;<span class="sr-only">{{#str}}expand, core{{/str}}</span></button>
+        </div>
 
         <a role="banner" href="{{{ config.wwwroot }}}" class="navbar-brand {{# output.should_display_navbar_logo }}has-logo{{/ output.should_display_navbar_logo }}">
             {{# output.should_display_navbar_logo }}
-                <div class="logo">
+                <span class="logo hidden-xs-down">
                     <img src="{{output.get_compact_logo_url}}" alt="{{sitename}}">
-                </div>
+                </span>
             {{/ output.should_display_navbar_logo }}
-            <div class="site-name">{{{ sitename }}}</div>
+            <span class="site-name hidden-sm-down">{{{ sitename }}}</span>
         </a>
 
+        <div data-region="drawer-toggle">
+        <button aria-expanded="{{#blocksdraweropen}}true{{/blocksdraweropen}}{{^blocksdraweropen}}false{{/blocksdraweropen}}" aria-controls="blocks-drawer" type="button" class="pull-xs-right m-l-1 btn btn-secondary" data-side="right" data-preference="drawer-open-blocks" data-action="toggle-drawer">{{#pix}}blocksdrawer, theme_boost{{/pix}}<span class="sr-only">{{#str}}expand, core{{/str}}</span></button>
+        </div>
+
         <!-- user_menu -->
         {{{ output.user_menu }}}
 
         {{{ output.navbar_plugin_output }}}
 
         <!-- search_box -->
+        <span class="hidden-md-down">
         {{{ output.search_box }}}
-
-        <div class="collapse navbar-toggleable-xs" id="bd-main-nav">
-            <nav class="nav navbar-nav">
-                <!-- custom_menu -->
-                {{{ output.custom_menu }}}
-                <!-- page_heading_menu -->
-                {{{ output.page_heading_menu }}}
-            </nav>
-        </div>
+        </span>
+
+        <nav class="nav navbar-nav hidden-md-down">
+            <!-- custom_menu -->
+            {{{ output.custom_menu }}}
+            <!-- page_heading_menu -->
+            {{{ output.page_heading_menu }}}
+        </nav>
     </div>
 </header>
 
diff --git a/theme/boost/templates/login.mustache b/theme/boost/templates/login.mustache
new file mode 100644 (file)
index 0000000..980b078
--- /dev/null
@@ -0,0 +1,44 @@
+{{{ output.doctype }}}
+<html {{{ output.htmlattributes }}}>
+<head>
+    <title>{{ output.page_title }}</title>
+    <link rel="shortcut icon" href="{{{ output.favicon }}}" />
+    {{{ output.standard_head_html }}}
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body {{{ bodyattributes }}}>
+
+<div id="page-wrapper">
+
+    {{{ output.standard_top_of_body_html }}}
+
+    <div id="page" class="container-fluid">
+        <div id="page-content" class="row">
+            <div id="region-main-box">
+                <section id="region-main" class="col-xs-12">
+                    {{{ output.course_content_header }}}
+                    {{{ output.main_content }}}
+                    {{{ output.course_content_footer }}}
+                </section>
+            </div>
+        </div>
+    </div>
+</div>
+<footer id="page-footer" class="p-y-1">
+    <div class="container">
+        <div id="course-footer">{{{ output.course_footer }}}</div>
+
+        {{# output.page_doc_link }}
+            <p class="helplink">{{{ output.page_doc_link }}}</p>
+        {{/ output.page_doc_link }}
+
+        {{{ output.login_info }}}
+        {{{ output.home_link }}}
+        {{{ output.standard_footer_html }}}
+        {{{ output.standard_end_of_body_html }}}
+    </div>
+</footer>
+
+</body>
+</html>
diff --git a/theme/boost/templates/nav-drawer.mustache b/theme/boost/templates/nav-drawer.mustache
new file mode 100644 (file)
index 0000000..3b75730
--- /dev/null
@@ -0,0 +1,3 @@
+<div id="nav-drawer" data-region="drawer" class="moodle-has-zindex {{^navdraweropen}}closed{{/navdraweropen}}" aria-hidden="{{#navdraweropen}}false{{/navdraweropen}}{{^navdraweropen}}true{{/navdraweropen}}">
+    {{> theme_boost/flat_navigation }}
+</div>
index 3c2af3c..2076bd2 100644 (file)
     {{{ output.full_header }}}
 
     <div id="page-content" class="row">
-        <div id="region-main-box" class="col-md-9">
+        <div id="region-main-box" class="col-xs-12">
             <div class="row">
-                <section id="region-main" class="col-md-8 push-md-4 col-lg-9 push-lg-3">
+                <section id="region-main" class="col-xs-12">
                     {{{ output.main_content }}}
                 </section>
-                {{{ sidepreblocks }}}
             </div>
         </div>
-        {{{ sidepostblocks }}}
     </div>
 
     {{{ output.standard_end_of_body_html }}}
@@ -64,4 +62,3 @@
 {{#js}}
 require(['theme_boost/loader'], function() {});
 {{/js}}
-