Merge branch 'MDL-51709-39' of git://github.com/lucaboesch/moodle into MOODLE_39_STABLE
authorSara Arjona <sara@moodle.com>
Tue, 1 Sep 2020 07:04:24 +0000 (09:04 +0200)
committerSara Arjona <sara@moodle.com>
Tue, 1 Sep 2020 07:04:24 +0000 (09:04 +0200)
32 files changed:
admin/tool/usertours/classes/manager.php
lib/form/duration.php
lib/form/tests/duration_test.php
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js
lib/form/yui/src/dateselector/js/calendar.js
mod/assign/feedback/file/importziplib.php
mod/assign/feedback/file/tests/importziplib_test.php [new file with mode: 0644]
pix/i/completion-auto-enabled.png
pix/i/completion-auto-enabled.svg
pix/i/completion-auto-fail.png
pix/i/completion-auto-fail.svg
pix/i/completion-auto-n.png
pix/i/completion-auto-n.svg
pix/i/completion-auto-pass.png
pix/i/completion-auto-pass.svg
pix/i/completion-auto-y-override.png
pix/i/completion-auto-y-override.svg
pix/i/completion-auto-y.png
pix/i/completion-auto-y.svg
pix/i/completion-manual-enabled.png
pix/i/completion-manual-enabled.svg
pix/i/completion-manual-n.png
pix/i/completion-manual-n.svg
pix/i/completion-manual-y-override.png
pix/i/completion-manual-y-override.svg
pix/i/completion-manual-y.png
pix/i/completion-manual-y.svg
theme/boost/scss/moodle/calendar.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css

index 4a9f5cf..6741568 100644 (file)
@@ -138,13 +138,28 @@ class manager {
      */
     const CONFIG_SHIPPED_VERSION = 'shipped_version';
 
+    /**
+     * Helper method to initialize admin page, setting appropriate extra URL parameters
+     *
+     * @param string $action
+     */
+    protected function setup_admin_externalpage(string $action): void {
+        admin_externalpage_setup('tool_usertours/tours', '', array_filter([
+            'action' => $action,
+            'id' => optional_param('id', 0, PARAM_INT),
+            'tourid' => optional_param('tourid', 0, PARAM_INT),
+            'direction' => optional_param('direction', 0, PARAM_INT),
+        ]));
+    }
+
     /**
      * This is the entry point for this controller class.
      *
      * @param   string  $action     The action to perform.
      */
     public function execute($action) {
-        admin_externalpage_setup('tool_usertours/tours');
+        $this->setup_admin_externalpage($action);
+
         // Add the main content.
         switch($action) {
             case self::ACTION_NEWTOUR:
index 20c0d05..58e7c6c 100644 (file)
@@ -34,7 +34,7 @@ require_once($CFG->libdir . '/form/text.php');
  * Duration element
  *
  * HTML class for a length of time. For example, 30 minutes of 4 days. The
- * values returned to PHP is the duration in seconds.
+ * values returned to PHP is the duration in seconds (an int rounded to the nearest second).
  *
  * @package   core_form
  * @category  form
@@ -301,6 +301,7 @@ class MoodleQuickForm_duration extends MoodleQuickForm_group {
         if ($this->_options['optional'] && empty($valuearray['enabled'])) {
             return $this->_prepareValue(0, $assoc);
         }
-        return $this->_prepareValue($valuearray['number'] * $valuearray['timeunit'], $assoc);
+        return $this->_prepareValue(
+                (int) round($valuearray['number'] * $valuearray['timeunit']), $assoc);
     }
 }
index 4ce9280..629ac4d 100644 (file)
@@ -47,7 +47,7 @@ class core_form_duration_testcase extends basic_testcase {
      *
      * @return MoodleQuickForm
      */
-    protected function get_test_form() {
+    protected function get_test_form(): MoodleQuickForm {
         $form = new temp_form_duration();
         return $form->getform();
     }
@@ -57,27 +57,26 @@ class core_form_duration_testcase extends basic_testcase {
      *
      * @return array with two elements, a MoodleQuickForm and a MoodleQuickForm_duration.
      */
-    protected function get_test_form_and_element() {
+    protected function get_test_form_and_element(): array {
         $mform = $this->get_test_form();
         $element = $mform->addElement('duration', 'duration');
         return [$mform, $element];
     }
 
     /**
-     * Testcase for testing contructor.
-     *
-     * @expectedException coding_exception
+     * Test the constructor error handling.
      */
-    public function test_constructor() {
+    public function test_constructor_rejects_invalid_unit(): void {
         // Test trying to create with an invalid unit.
         $mform = $this->get_test_form();
+        $this->expectException('coding_exception');
         $mform->addElement('duration', 'testel', null, ['defaultunit' => 123, 'optional' => false]);
     }
 
     /**
-     * Test contructor only some units.
+     * Test constructor only some units.
      */
-    public function test_constructor_limited_units() {
+    public function test_constructor_limited_units(): void {
         $mform = $this->get_test_form();
         $mform->addElement('duration', 'testel', null, ['units' => [MINSECS, 1], 'optional' => false]);
         $html = $mform->toHtml();
@@ -90,7 +89,7 @@ class core_form_duration_testcase extends basic_testcase {
     /**
      * Testcase for testing units (seconds, minutes, hours and days)
      */
-    public function test_get_units() {
+    public function test_get_units(): void {
         [$mform, $element] = $this->get_test_form_and_element();
         $units = $element->get_units();
         $this->assertEquals($units, [1 => get_string('seconds'), 60 => get_string('minutes'),
@@ -98,66 +97,95 @@ class core_form_duration_testcase extends basic_testcase {
     }
 
     /**
-     * Testcase for testing conversion of seconds to the best possible unit
+     * Data provider for {@see test_seconds_to_unit()}.
+     *
+     * @return array test cases.
      */
-    public function test_seconds_to_unit() {
-        [$mform, $element] = $this->get_test_form_and_element();
-        $this->assertEquals([0, MINSECS], $element->seconds_to_unit(0)); // Zero minutes, for a nice default unit.
-        $this->assertEquals([1, 1], $element->seconds_to_unit(1));
-        $this->assertEquals([3601, 1], $element->seconds_to_unit(3601));
-        $this->assertEquals([1, MINSECS], $element->seconds_to_unit(60));
-        $this->assertEquals([3, MINSECS], $element->seconds_to_unit(180));
-        $this->assertEquals([1, HOURSECS], $element->seconds_to_unit(3600));
-        $this->assertEquals([2, HOURSECS], $element->seconds_to_unit(7200));
-        $this->assertEquals([1, DAYSECS], $element->seconds_to_unit(86400));
-        $this->assertEquals([25, HOURSECS], $element->seconds_to_unit(90000));
+    public function seconds_to_unit_cases(): array {
+        return [
+            [[0, MINSECS], 0], // Zero minutes, for a nice default unit.
+            [[1, 1], 1],
+            [[3601, 1], 3601],
+            [[1, MINSECS], 60],
+            [[3, MINSECS], 180],
+            [[1, HOURSECS], 3600],
+            [[2, HOURSECS], 7200],
+            [[1, DAYSECS], 86400],
+            [[25, HOURSECS], 90000],
+        ];
+    }
 
+    /**
+     * Testcase for testing conversion of seconds to the best possible unit.
+     *
+     * @dataProvider seconds_to_unit_cases
+     * @param array $expected expected return value from seconds_to_unit
+     * @param int $seconds value to pass to seconds_to_unit
+     */
+    public function test_seconds_to_unit(array $expected, int $seconds): void {
+        [, $element] = $this->get_test_form_and_element();
+        $this->assertEquals($expected, $element->seconds_to_unit($seconds));
+    }
+
+    /**
+     * Testcase for testing conversion of seconds to the best possible unit with a non-default default unit.
+     */
+    public function test_seconds_to_unit_different_default_unit() {
+        $mform = $this->get_test_form();
         $element = $mform->addElement('duration', 'testel', null,
                 ['defaultunit' => DAYSECS, 'optional' => false]);
-        $this->assertEquals([0, DAYSECS], $element->seconds_to_unit(0)); // Zero minutes, for a nice default unit.
+        $this->assertEquals([0, DAYSECS], $element->seconds_to_unit(0));
+    }
+
+    /**
+     * Data provider for {@see test_export_value()}.
+     *
+     * @return array test cases.
+     */
+    public function export_value_cases(): array {
+        return [
+            [10, '10', 1],
+            [9, '9.3', 1],
+            [10, '9.5', 1],
+            [180, '3', MINSECS],
+            [90, '1.5', MINSECS],
+            [7200, '2', HOURSECS],
+            [86400, '1', DAYSECS],
+            [0, '0', HOURSECS],
+            [0, '10', 1, 0, true],
+            [20, '20', 1, 1, true],
+            [0, '10', 1, 0, true, ''],
+            [20, '20', 1, 1, true, ''],
+        ];
     }
 
     /**
      * Testcase to check generated timestamp
+     *
+     * @dataProvider export_value_cases
+     * @param int $expected Expected value returned by the element.
+     * @param string $number Number entered into the element.
+     * @param int $unit Unit selected in the element.
+     * @param int $enabled Whether the enabled checkbox on the form was selected. (Only used if $optional is true.)
+     * @param bool $optional Whether the element has the optional option on.
+     * @param string|null $label The element's label.
      */
-    public function test_exportValue() {
+    public function test_export_value(int $expected, string $number, int $unit, int $enabled = 0,
+            bool $optional = false, ?string $label = null): void {
+
+        // Create the test element.
         $mform = $this->get_test_form();
-        $el = $mform->addElement('duration', 'testel');
-        $values = ['testel' => ['number' => 10, 'timeunit' => 1]];
-        $this->assertEquals(['testel' => 10], $el->exportValue($values, true));
-        $this->assertEquals(10, $el->exportValue($values));
-        $values = ['testel' => ['number' => 3, 'timeunit' => MINSECS]];
-        $this->assertEquals(['testel' => 180], $el->exportValue($values, true));
-        $this->assertEquals(180, $el->exportValue($values));
-        $values = ['testel' => ['number' => 1.5, 'timeunit' => MINSECS]];
-        $this->assertEquals(['testel' => 90], $el->exportValue($values, true));
-        $this->assertEquals(90, $el->exportValue($values));
-        $values = ['testel' => ['number' => 2, 'timeunit' => HOURSECS]];
-        $this->assertEquals(['testel' => 7200], $el->exportValue($values, true));
-        $this->assertEquals(7200, $el->exportValue($values));
-        $values = ['testel' => ['number' => 1, 'timeunit' => DAYSECS]];
-        $this->assertEquals(['testel' => 86400], $el->exportValue($values, true));
-        $this->assertEquals(86400, $el->exportValue($values));
-        $values = ['testel' => ['number' => 0, 'timeunit' => HOURSECS]];
-        $this->assertEquals(['testel' => 0], $el->exportValue($values, true));
-        $this->assertEquals(0, $el->exportValue($values));
-
-        $el = $mform->addElement('duration', 'testel', null, ['optional' => true]);
-        $values = ['testel' => ['number' => 10, 'timeunit' => 1]];
-        $this->assertEquals(['testel' => 0], $el->exportValue($values, true));
-        $this->assertEquals(0, $el->exportValue($values));
-        $values = ['testel' => ['number' => 20, 'timeunit' => 1, 'enabled' => 1]];
-        $this->assertEquals(['testel' => 20], $el->exportValue($values, true));
-        $this->assertEquals(20, $el->exportValue($values));
-
-        // Optional element.
-        $el2 = $mform->addElement('duration', 'testel', '', ['optional' => true]);
-        $values = ['testel' => ['number' => 10, 'timeunit' => 1, 'enabled' => 1]];
-        $this->assertEquals(['testel' => 10], $el2->exportValue($values, true));
-        $this->assertEquals(10, $el2->exportValue($values));
-        $values = ['testel' => ['number' => 10, 'timeunit' => 1, 'enabled' => 0]];
-        $this->assertEquals(['testel' => 0], $el2->exportValue($values, true));
-        $this->assertEquals(null, $el2->exportValue($values));
+        $el = $mform->addElement('duration', 'testel', $label, $optional ? ['optional' => true] : []);
+
+        // Prepare the submitted values.
+        $values = ['testel' => ['number' => $number, 'timeunit' => $unit]];
+        if ($optional) {
+            $values['testel']['enabled'] = $enabled;
+        }
+
+        // Test.
+        $this->assertEquals(['testel' => $expected], $el->exportValue($values, true));
+        $this->assertEquals($expected, $el->exportValue($values));
     }
 }
 
index ab07f4e..d4cb9ed 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js and b/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js differ
index 8835642..d7fc49d 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js and b/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js differ
index ab07f4e..d4cb9ed 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js and b/lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js differ
index b3ba4b1..42b3967 100644 (file)
@@ -86,8 +86,8 @@ CALENDAR.prototype = {
             this.set_date_from_selects();
         }
         M.form.dateselector.currentowner = this;
-        M.form.dateselector.calendar.set('mindate', new Date(this.yearselect.firstOptionValue(), 0, 1));
-        M.form.dateselector.calendar.set('maxdate', new Date(this.yearselect.lastOptionValue(), 11, 31));
+        M.form.dateselector.calendar.set('minimumDate', new Date(this.yearselect.firstOptionValue(), 0, 1));
+        M.form.dateselector.calendar.set('maximumDate', new Date(this.yearselect.lastOptionValue(), 11, 31));
         M.form.dateselector.panel.show();
         M.form.dateselector.calendar.show();
         M.form.dateselector.fix_position();
index 01d9a33..3cbe9f7 100644 (file)
@@ -59,33 +59,53 @@ class assignfeedback_file_zip_importer {
             return false;
         }
 
-        $info = explode('_', $fileinfo->get_filepath() . $fileinfo->get_filename(), 5);
+        // Break the full path-name into path parts.
+        $pathparts = explode('/', $fileinfo->get_filepath() . $fileinfo->get_filename());
 
-        if (count($info) < 5) {
-            return false;
-        }
+        while (!empty($pathparts)) {
+            // Get the next path part and break it up by underscores.
+            $pathpart = array_shift($pathparts);
+            $info = explode('_', $pathpart, 5);
 
-        $participantid = $info[1];
-        $filename = $info[4];
-        $plugin = $assignment->get_plugin_by_type($info[2], $info[3]);
+            if (count($info) < 5) {
+                continue;
+            }
 
-        if (!is_numeric($participantid)) {
-            return false;
-        }
+            // Check the participant id.
+            $participantid = $info[1];
 
-        if (!$plugin) {
-            return false;
-        }
+            if (!is_numeric($participantid)) {
+                continue;
+            }
 
-        // Convert to int.
-        $participantid += 0;
+            // Convert to int.
+            $participantid += 0;
 
-        if (empty($participants[$participantid])) {
-            return false;
+            if (empty($participants[$participantid])) {
+                continue;
+            }
+
+            // Set user, which is by reference, so is used by the calling script.
+            $user = $participants[$participantid];
+
+            // Set the plugin. This by reference, and is used by the calling script.
+            $plugin = $assignment->get_plugin_by_type($info[2], $info[3]);
+
+            if (!$plugin) {
+                continue;
+            }
+
+            // Take any remaining text in this part and put it back in the path parts array.
+            array_unshift($pathparts, $info[4]);
+
+            // Combine the remaining parts and set it as the filename.
+            // Note that filename is a 'by reference' variable, so we need to set it before returning.
+            $filename = implode('/', $pathparts);
+
+            return true;
         }
 
-        $user = $participants[$participantid];
-        return true;
+        return false;
     }
 
     /**
diff --git a/mod/assign/feedback/file/tests/importziplib_test.php b/mod/assign/feedback/file/tests/importziplib_test.php
new file mode 100644 (file)
index 0000000..009579b
--- /dev/null
@@ -0,0 +1,148 @@
+<?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/>.
+
+/**
+ * Unit tests for importziplib.
+ *
+ * @package    assignfeedback_file
+ * @copyright  2020 Eric Merrill <merrill@oakland.edu>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
+require_once($CFG->dirroot . '/mod/assign/feedback/file/importziplib.php');
+
+/**
+ * Unit tests for importziplib.
+ *
+ * @copyright  2020 Eric Merrill <merrill@oakland.edu>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_importziplib_testcase extends advanced_testcase {
+
+    // Use the generator helper.
+    use mod_assign_test_generator;
+
+    /**
+     * Test the assignfeedback_file_zip_importer->is_valid_filename_for_import() method.
+     */
+    public function test_is_valid_filename_for_import() {
+        // Do the initial assign setup.
+        $this->resetAfterTest();
+        $course = $this->getDataGenerator()->create_course();
+        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
+        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
+
+        $assign = $this->create_instance($course, [
+                'assignsubmission_onlinetext_enabled' => 1,
+                'assignfeedback_file_enabled' => 1,
+            ]);
+
+        // Create an online text submission.
+        $this->add_submission($student, $assign);
+
+        // Now onto the file work.
+        $fs = get_file_storage();
+
+        // Setup a basic file we will work with. We will keep renaming and repathing it.
+        $record = new stdClass;
+        $record->contextid = $assign->get_context()->id;
+        $record->component = 'assignfeedback_file';
+        $record->filearea  = ASSIGNFEEDBACK_FILE_FILEAREA;
+        $record->itemid    = $assign->get_user_grade($student->id, true)->id;
+        $record->filepath  = '/';
+        $record->filename  = '1.txt';
+        $record->source    = 'test';
+        $file = $fs->create_file_from_string($record, 'file content');
+
+        // The importer we will use.
+        $importer = new assignfeedback_file_zip_importer();
+
+        // Setup some variable we use.
+        $user = null;
+        $plugin = null;
+        $filename = '';
+
+        $allusers = $assign->list_participants(0, false);
+        $participants = array();
+        foreach ($allusers as $user) {
+            $participants[$assign->get_uniqueid_for_user($user->id)] = $user;
+        }
+
+        $file->rename('/import/', '.hiddenfile');
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        $file->rename('/import/', '~hiddenfile');
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        $file->rename('/import/some_path_here/', 'RandomFile.txt');
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        $file->rename('/import/', '~hiddenfile');
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        // Get the students assign id.
+        $studentid = $assign->get_uniqueid_for_user($student->id);
+
+        // Submissions are identified with the format:
+        // StudentName_StudentID_PluginType_Plugin_FilePathAndName.
+
+        // Test a string student id.
+        $badname = 'Student Name_StringID_assignsubmission_file_My_cool_filename.txt';
+        $file->rename('/import/', $badname);
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        // Test an invalid student id.
+        $badname = 'Student Name_' . ($studentid + 100) . '_assignsubmission_file_My_cool_filename.txt';
+        $file->rename('/import/', $badname);
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        // Test an invalid submission plugin.
+        $badname = 'Student Name_' . $studentid . '_assignsubmission_noplugin_My_cool_filename.txt';
+        $file->rename('/import/', $badname);
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertFalse($result);
+
+        // Test a basic, good file.
+        $goodbase = 'Student Name_' . $studentid . '_assignsubmission_file_';
+        $file->rename('/import/', $goodbase . "My_cool_filename.txt");
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertTrue($result);
+        $this->assertEquals($participants[$studentid], $user);
+        $this->assertEquals('My_cool_filename.txt', $filename);
+        $this->assertInstanceOf(assign_submission_file::class, $plugin);
+
+        // Test another good file, with some additional path and underscores.
+        $user = null;
+        $plugin = null;
+        $filename = '';
+        $file->rename('/import/some_path_here/' . $goodbase . '/some_path/', 'My File.txt');
+        $result = $importer->is_valid_filename_for_import($assign, $file, $participants, $user, $plugin, $filename);
+        $this->assertTrue($result);
+        $this->assertEquals($participants[$studentid], $user);
+        $this->assertEquals('/some_path/My File.txt', $filename);
+        $this->assertInstanceOf(assign_submission_file::class, $plugin);
+    }
+}
index c030e3d..68689f0 100644 (file)
Binary files a/pix/i/completion-auto-enabled.png and b/pix/i/completion-auto-enabled.png differ
index 43f2df4..0ee649a 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M10 0v2H6V0h4zm1 2h1c.4 0 .8.1 1.1.3.3-.3.8-.4 1.2-.4.5 0 1 .2 1.4.6l.3.3V2c0-1.1-.9-2-2-2h-3v2zm3 8h2V6.4l-2 2V10zm0 1v1c0 1.1-.9 2-2 2h-1v2h3c1.1 0 2-.9 2-2v-3h-2zm-4 5v-2H6v2h4zm-5-2H4c-1.1 0-2-.9-2-2v-1H0v3c0 1.1.9 2 2 2h3v-2zm-5-4h2V6H0v4zm2-5V4c0-1.1.9-2 2-2h1V0H2C.9 0 0 .9 0 2v3h2zm13.7-1.1l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#999"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10,0V2H6V0Zm1,2h1a2,2,0,0,1,1.1.3,1.75,1.75,0,0,1,1.2-.4,2,2,0,0,1,1.4.6l.3.3V2a2,2,0,0,0-2-2H11Zm3,8h2V6.4l-2,2Zm0,1v1a2,2,0,0,1-2,2H11v2h3a2,2,0,0,0,2-2V11Zm-4,5V14H6v2ZM5,14H4a2,2,0,0,1-2-2V11H0v3a2,2,0,0,0,2,2H5ZM0,10H2V6H0ZM2,5V4A2,2,0,0,1,4,2H5V0H2A2,2,0,0,0,0,2V5ZM15.7,3.9,15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#949494"/></svg>
\ No newline at end of file
index c2766ac..badd1f0 100644 (file)
Binary files a/pix/i/completion-auto-fail.png and b/pix/i/completion-auto-fail.png differ
index f437d97..1929dfc 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M10 0v2H6V0h4zm1 2h1c1.1 0 2 .9 2 2v1h2V2c0-1.1-.9-2-2-2h-3v2zm5 4h-2v4h2V6zm-2 5v1c0 1.1-.9 2-2 2h-1v2h3c1.1 0 2-.9 2-2v-3h-2zm-4 5v-2H6v2h4zm-5-2H4c-1.1 0-2-.9-2-2v-1H0v3c0 1.1.9 2 2 2h3v-2zm-5-4h2V6H0v4zm2-5V4c0-1.1.9-2 2-2h1V0H2C.9 0 0 .9 0 2v3h2z" fill="#999"/><path d="M10.2 8l2.6-2.6c.4-.4.4-1 0-1.4l-.8-.7c-.4-.4-1-.4-1.4 0L8 5.9 5.4 3.3c-.4-.4-1-.4-1.4 0l-.7.7c-.4.4-.4 1 0 1.4L5.9 8l-2.6 2.6c-.3.4-.3 1 0 1.4l.7.7c.4.4 1 .4 1.4 0L8 10.2l2.5 2.5c.4.4 1 .4 1.4 0l.7-.7c.4-.4.4-1 0-1.4L10.2 8z" fill="#FF403C"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10,0V2H6V0Zm1,2h1a2,2,0,0,1,2,2V5h2V2a2,2,0,0,0-2-2H11Zm5,4H14v4h2Zm-2,5v1a2,2,0,0,1-2,2H11v2h3a2,2,0,0,0,2-2V11Zm-4,5V14H6v2ZM5,14H4a2,2,0,0,1-2-2V11H0v3a2,2,0,0,0,2,2H5ZM0,10H2V6H0ZM2,5V4A2,2,0,0,1,4,2H5V0H2A2,2,0,0,0,0,2V5Z" fill="#949494"/><path d="M10.2,8l2.6-2.6a1,1,0,0,0,0-1.4L12,3.3a1,1,0,0,0-1.4,0L8,5.9,5.4,3.3A1,1,0,0,0,4,3.3L3.3,4a1,1,0,0,0,0,1.4L5.9,8,3.3,10.6a1.2,1.2,0,0,0,0,1.4l.7.7a1,1,0,0,0,1.4,0L8,10.2l2.5,2.5a1,1,0,0,0,1.4,0l.7-.7a1,1,0,0,0,0-1.4Z" fill="#ff403c"/></svg>
\ No newline at end of file
index c93c86b..01419b9 100644 (file)
Binary files a/pix/i/completion-auto-n.png and b/pix/i/completion-auto-n.png differ
index 56f0b03..e5d4138 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M10 0v2H6V0h4zm1 2h1c1.1 0 2 .9 2 2v1h2V2c0-1.1-.9-2-2-2h-3v2zm5 4h-2v4h2V6zm-2 5v1c0 1.1-.9 2-2 2h-1v2h3c1.1 0 2-.9 2-2v-3h-2zm-4 5v-2H6v2h4zm-5-2H4c-1.1 0-2-.9-2-2v-1H0v3c0 1.1.9 2 2 2h3v-2zm-5-4h2V6H0v4zm2-5V4c0-1.1.9-2 2-2h1V0H2C.9 0 0 .9 0 2v3h2z" fill="#999"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10,0V2H6V0Zm1,2h1a2,2,0,0,1,2,2V5h2V2a2,2,0,0,0-2-2H11Zm5,4H14v4h2Zm-2,5v1a2,2,0,0,1-2,2H11v2h3a2,2,0,0,0,2-2V11Zm-4,5V14H6v2ZM5,14H4a2,2,0,0,1-2-2V11H0v3a2,2,0,0,0,2,2H5ZM0,10H2V6H0ZM2,5V4A2,2,0,0,1,4,2H5V0H2A2,2,0,0,0,0,2V5Z" fill="#949494"/></svg>
\ No newline at end of file
index f7384ad..0e88881 100644 (file)
Binary files a/pix/i/completion-auto-pass.png and b/pix/i/completion-auto-pass.png differ
index f644de7..9b91a5f 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M10 0v2H6V0h4zm1 2h1c.4 0 .8.1 1.1.3.3-.3.8-.4 1.2-.4.5 0 1 .2 1.4.6l.3.3V2c0-1.1-.9-2-2-2h-3v2zm3 8h2V6.4l-2 2V10zm0 1v1c0 1.1-.9 2-2 2h-1v2h3c1.1 0 2-.9 2-2v-3h-2zm-4 5v-2H6v2h4zm-5-2H4c-1.1 0-2-.9-2-2v-1H0v3c0 1.1.9 2 2 2h3v-2zm-5-4h2V6H0v4zm2-5V4c0-1.1.9-2 2-2h1V0H2C.9 0 0 .9 0 2v3h2z" fill="#999"/><path d="M15.7 3.9l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#9C3"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10,0V2H6V0Zm1,2h1a2,2,0,0,1,1.1.3,1.75,1.75,0,0,1,1.2-.4,2,2,0,0,1,1.4.6l.3.3V2a2,2,0,0,0-2-2H11Zm3,8h2V6.4l-2,2Zm0,1v1a2,2,0,0,1-2,2H11v2h3a2,2,0,0,0,2-2V11Zm-4,5V14H6v2ZM5,14H4a2,2,0,0,1-2-2V11H0v3a2,2,0,0,0,2,2H5ZM0,10H2V6H0ZM2,5V4A2,2,0,0,1,4,2H5V0H2A2,2,0,0,0,0,2V5Z" fill="#949494"/><path d="M15.7,3.9,15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#79a128"/></svg>
\ No newline at end of file
index aeca4ed..7022a81 100644 (file)
Binary files a/pix/i/completion-auto-y-override.png and b/pix/i/completion-auto-y-override.png differ
index 13cf5d7..c849f00 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M10 0v2H6V0h4zm1 2h1c.4 0 .8.1 1.1.3.3-.3.8-.4 1.2-.4.5 0 1 .2 1.4.6l.3.3V2c0-1.1-.9-2-2-2h-3v2zm3 8h2V6.4l-2 2V10zm0 1v1c0 1.1-.9 2-2 2h-1v2h3c1.1 0 2-.9 2-2v-3h-2zm-4 5v-2H6v2h4zm-5-2H4c-1.1 0-2-.9-2-2v-1H0v3c0 1.1.9 2 2 2h3v-2zm-5-4h2V6H0v4zm2-5V4c0-1.1.9-2 2-2h1V0H2C.9 0 0 .9 0 2v3h2z" fill="#FF2727"/><path d="M15.7 3.9l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#76A1F0"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10,0V2H6V0Zm1,2h1a2,2,0,0,1,1.1.3,1.75,1.75,0,0,1,1.2-.4,2,2,0,0,1,1.4.6l.3.3V2a2,2,0,0,0-2-2H11Zm3,8h2V6.4l-2,2Zm0,1v1a2,2,0,0,1-2,2H11v2h3a2,2,0,0,0,2-2V11Zm-4,5V14H6v2ZM5,14H4a2,2,0,0,1-2-2V11H0v3a2,2,0,0,0,2,2H5ZM0,10H2V6H0ZM2,5V4A2,2,0,0,1,4,2H5V0H2A2,2,0,0,0,0,2V5Z" fill="#ff2727"/><path d="M15.7,3.9,15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#6393ee"/></svg>
\ No newline at end of file
index a5440d5..349f8bf 100644 (file)
Binary files a/pix/i/completion-auto-y.png and b/pix/i/completion-auto-y.png differ
index c734b49..9f2809f 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M10 0v2H6V0h4zm1 2h1c.4 0 .8.1 1.1.3.3-.3.8-.4 1.2-.4.5 0 1 .2 1.4.6l.3.3V2c0-1.1-.9-2-2-2h-3v2zm3 8h2V6.4l-2 2V10zm0 1v1c0 1.1-.9 2-2 2h-1v2h3c1.1 0 2-.9 2-2v-3h-2zm-4 5v-2H6v2h4zm-5-2H4c-1.1 0-2-.9-2-2v-1H0v3c0 1.1.9 2 2 2h3v-2zm-5-4h2V6H0v4zm2-5V4c0-1.1.9-2 2-2h1V0H2C.9 0 0 .9 0 2v3h2z" fill="#999"/><path d="M15.7 3.9l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#76A1F0"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M10,0V2H6V0Zm1,2h1a2,2,0,0,1,1.1.3,1.75,1.75,0,0,1,1.2-.4,2,2,0,0,1,1.4.6l.3.3V2a2,2,0,0,0-2-2H11Zm3,8h2V6.4l-2,2Zm0,1v1a2,2,0,0,1-2,2H11v2h3a2,2,0,0,0,2-2V11Zm-4,5V14H6v2ZM5,14H4a2,2,0,0,1-2-2V11H0v3a2,2,0,0,0,2,2H5ZM0,10H2V6H0ZM2,5V4A2,2,0,0,1,4,2H5V0H2A2,2,0,0,0,0,2V5Z" fill="#949494"/><path d="M15.7,3.9,15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#6393ee"/></svg>
\ No newline at end of file
index 1850d22..2b8389f 100644 (file)
Binary files a/pix/i/completion-manual-enabled.png and b/pix/i/completion-manual-enabled.png differ
index c415b90..dd45c3f 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M16 6.4V14c0 1.1-.9 2-2 2H2c-1.1 0-2-.9-2-2V2C0 .9.9 0 2 0h12c1.1 0 2 .9 2 2v.8l-.3-.3c-.4-.4-.9-.6-1.4-.6-.4 0-.9.1-1.2.4-.3-.2-.7-.3-1.1-.3H4c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V8.4l2-2zm-.3-2.5l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#999"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M16,6.4V14a2,2,0,0,1-2,2H2a2,2,0,0,1-2-2V2A2,2,0,0,1,2,0H14a2,2,0,0,1,2,2v.8l-.3-.3a2,2,0,0,0-1.4-.6,1.75,1.75,0,0,0-1.2.4A2,2,0,0,0,12,2H4A2,2,0,0,0,2,4v8a2,2,0,0,0,2,2h8a2,2,0,0,0,2-2V8.4Zm-.3-2.5L15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#949494"/></svg>
\ No newline at end of file
index 79e5c71..f783abf 100644 (file)
Binary files a/pix/i/completion-manual-n.png and b/pix/i/completion-manual-n.png differ
index 19785de..5c8d283 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M14 0H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V2c0-1.1-.9-2-2-2zm0 12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2v8z" fill="#999"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M14,0H2A2,2,0,0,0,0,2V14a2,2,0,0,0,2,2H14a2,2,0,0,0,2-2V2A2,2,0,0,0,14,0Zm0,12a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2h8a2,2,0,0,1,2,2Z" fill="#949494"/></svg>
\ No newline at end of file
index bdbc46b..78d5b86 100644 (file)
Binary files a/pix/i/completion-manual-y-override.png and b/pix/i/completion-manual-y-override.png differ
index 69270ba..6ff1757 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M14 8.4V12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h8c.4 0 .8.1 1.1.3.3-.3.8-.4 1.2-.4.5 0 1 .2 1.4.6l.3.3V2c0-1.1-.9-2-2-2H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V6.4l-2 2z" fill="#FF2727"/><path d="M15.7 3.9l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#76A1F0"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M14,8.4V12a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2h8a2,2,0,0,1,1.1.3,1.75,1.75,0,0,1,1.2-.4,2,2,0,0,1,1.4.6l.3.3V2a2,2,0,0,0-2-2H2A2,2,0,0,0,0,2V14a2,2,0,0,0,2,2H14a2,2,0,0,0,2-2V6.4Z" fill="#ff2727"/><path d="M15.7,3.9,15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#6393ee"/></svg>
\ No newline at end of file
index d1c85ee..7c79094 100644 (file)
Binary files a/pix/i/completion-manual-y.png and b/pix/i/completion-manual-y.png differ
index 8125e80..b2a39cd 100644 (file)
@@ -1,3 +1 @@
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
-       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
-]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M14 8.4V12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h8c.4 0 .8.1 1.1.3.3-.3.8-.4 1.2-.4.5 0 1 .2 1.4.6l.3.3V2c0-1.1-.9-2-2-2H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V6.4l-2 2z" fill="#999"/><path d="M15.7 3.9l-.7-.7c-.4-.4-1-.4-1.4 0l-6 6L5.4 7c-.4-.3-1-.3-1.4 0l-.7.7c-.4.4-.4 1 0 1.4l3.6 3.6c.4.4 1 .4 1.4 0l7.4-7.4c.4-.4.4-1 0-1.4z" fill="#76A1F0"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M14,8.4V12a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2h8a2,2,0,0,1,1.1.3,1.75,1.75,0,0,1,1.2-.4,2,2,0,0,1,1.4.6l.3.3V2a2,2,0,0,0-2-2H2A2,2,0,0,0,0,2V14a2,2,0,0,0,2,2H14a2,2,0,0,0,2-2V6.4Z" fill="#949494"/><path d="M15.7,3.9,15,3.2a1,1,0,0,0-1.4,0l-6,6L5.4,7A1.2,1.2,0,0,0,4,7l-.7.7a1,1,0,0,0,0,1.4l3.6,3.6a1,1,0,0,0,1.4,0l7.4-7.4A1,1,0,0,0,15.7,3.9Z" fill="#6293ee"/></svg>
\ No newline at end of file
index 53fdb52..6de983f 100644 (file)
@@ -1,35 +1,64 @@
 /* calendar.less */
 
 // Calendar colour variables defined.
-$calendarEventCategoryColor: #d8bfd8 !default; // Pale purple.
+$calendarEventCategoryColor: #e0cbe0 !default; // Pale purple.
 $calendarEventCourseColor: #ffd3bd !default; // Pale red.
 $calendarEventGlobalColor: #d6f8cd !default; // Pale green.
 $calendarEventGroupColor: #fee7ae !default; // Pale yellow.
 $calendarEventUserColor: #dce7ec !default; // Pale blue.
 $calendarEventOtherColor: #ced4da !default; // Pale gray.
 
+// Border colours for the event colour indicators.
+$calendarEventCategoryBorder: 2px solid #9e619f !default; // Purple.
+$calendarEventCourseBorder: 2px solid #d34600 !default; // Red-orange.
+$calendarEventGlobalBorder: 2px solid #2b8713 !default; // Green.
+$calendarEventGroupBorder: 2px solid #9a6e02 !default; // Dark orange.
+$calendarEventUserBorder: 2px solid #4e7c91 !default; // Blue.
+$calendarEventOtherBorder: 2px solid #687889 !default; // Gray.
+
+// This will be the colour of mini-calendar links, hide/show filter icons, edit/delete icon buttons.
+$calendarEventColor: #0d5ca1 !default;
+
 // Calendar event background colours defined.
 .calendar_event_category {
     background-color: $calendarEventCategoryColor;
+    .commands a {
+        color: $calendarEventColor;
+    }
 }
 .calendar_event_course {
     background-color: $calendarEventCourseColor;
+    .commands a {
+        color: $calendarEventColor;
+    }
 }
 
 .calendar_event_site {
     background-color: $calendarEventGlobalColor;
+    .commands a {
+        color: $calendarEventColor;
+    }
 }
 
 .calendar_event_group {
     background-color: $calendarEventGroupColor;
+    .commands a {
+        color: $calendarEventColor;
+    }
 }
 
 .calendar_event_user {
     background-color: $calendarEventUserColor;
+    .commands a {
+        color: $calendarEventColor;
+    }
 }
 
 .calendar_event_other {
     background-color: $calendarEventOtherColor;
+    .commands a {
+        color: $calendarEventColor;
+    }
 }
 
 // Calendar restyling.
@@ -139,25 +168,31 @@ $calendarEventOtherColor: #ced4da !default; // Pale gray.
 
                             &.calendar_event_category {
                                 background-color: $calendarEventCategoryColor;
+                                border: $calendarEventCategoryBorder;
                             }
                             &.calendar_event_course {
                                 background-color: $calendarEventCourseColor;
+                                border: $calendarEventCourseBorder;
                             }
 
                             &.calendar_event_site {
                                 background-color: $calendarEventGlobalColor;
+                                border: $calendarEventGlobalBorder;
                             }
 
                             &.calendar_event_group {
                                 background-color: $calendarEventGroupColor;
+                                border: $calendarEventGroupBorder;
                             }
 
                             &.calendar_event_user {
                                 background-color: $calendarEventUserColor;
+                                border: $calendarEventUserBorder;
                             }
 
                             &.calendar_event_other {
                                 background-color: $calendarEventOtherColor;
+                                border: $calendarEventOtherBorder;
                             }
                         }
                     }
@@ -289,6 +324,7 @@ $calendarEventOtherColor: #ced4da !default; // Pale gray.
                 width: 100%;
                 height: 100%;
                 display: block;
+                color: $calendarEventColor;
             }
 
             &.duration_global {
@@ -389,6 +425,36 @@ $calendarEventOtherColor: #ced4da !default; // Pale gray.
             margin-bottom: 0.2em;
 
             span {
+                &.calendar_event_category {
+                    i {
+                        color: $calendarEventColor;
+                    }
+                }
+                &.calendar_event_course {
+                    i {
+                        color: $calendarEventColor;
+                    }
+                }
+                &.calendar_event_site {
+                    i {
+                        color: $calendarEventColor;
+                    }
+                }
+                &.calendar_event_group {
+                    i {
+                        color: $calendarEventColor;
+                    }
+                }
+                &.calendar_event_user {
+                    i {
+                        color: $calendarEventColor;
+                    }
+                }
+                &.calendar_event_other {
+                    i {
+                        color: $calendarEventColor;
+                    }
+                }
                 img {
                     padding: 0 0.2em;
                     margin: 0;
index 9a1735e..5705764 100644 (file)
@@ -12721,22 +12721,34 @@ body.h5p-embed .h5pmessages {
 
 /* calendar.less */
 .calendar_event_category {
-  background-color: #d8bfd8; }
+  background-color: #e0cbe0; }
+  .calendar_event_category .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_course {
   background-color: #ffd3bd; }
+  .calendar_event_course .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_site {
   background-color: #d6f8cd; }
+  .calendar_event_site .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_group {
   background-color: #fee7ae; }
+  .calendar_event_group .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_user {
   background-color: #dce7ec; }
+  .calendar_event_user .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_other {
   background-color: #ced4da; }
+  .calendar_event_other .commands a {
+    color: #0d5ca1; }
 
 .path-calendar .calendartable {
   width: 100%;
@@ -12813,17 +12825,23 @@ body.h5p-embed .h5pmessages {
           border-radius: 6px;
           vertical-align: middle; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_category {
-            background-color: #d8bfd8; }
+            background-color: #e0cbe0;
+            border: 2px solid #9e619f; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_course {
-            background-color: #ffd3bd; }
+            background-color: #ffd3bd;
+            border: 2px solid #d34600; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_site {
-            background-color: #d6f8cd; }
+            background-color: #d6f8cd;
+            border: 2px solid #2b8713; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_group {
-            background-color: #fee7ae; }
+            background-color: #fee7ae;
+            border: 2px solid #9a6e02; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_user {
-            background-color: #dce7ec; }
+            background-color: #dce7ec;
+            border: 2px solid #4e7c91; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_other {
-            background-color: #ced4da; }
+            background-color: #ced4da;
+            border: 2px solid #687889; }
     .path-calendar .maincalendar .calendarmonth td {
       height: 5em; }
     .path-calendar .maincalendar .calendarmonth .clickable:hover {
@@ -12844,7 +12862,7 @@ body.h5p-embed .h5pmessages {
     color: #0b4f8a;
     text-decoration: underline; }
   .path-calendar .maincalendar .calendar_event_category {
-    border-color: #d8bfd8; }
+    border-color: #e0cbe0; }
   .path-calendar .maincalendar .calendar_event_course {
     border-color: #ffd3bd; }
   .path-calendar .maincalendar .calendar_event_site {
@@ -12887,17 +12905,18 @@ body.h5p-embed .h5pmessages {
   .block .minicalendar td a {
     width: 100%;
     height: 100%;
-    display: block; }
+    display: block;
+    color: #0d5ca1; }
   .block .minicalendar td.duration_global {
     border-top: 1px solid #d6f8cd;
     border-bottom: 1px solid #d6f8cd; }
     .block .minicalendar td.duration_global.duration_finish {
       background-color: #d6f8cd; }
   .block .minicalendar td.duration_category {
-    border-top: 1px solid #d8bfd8;
-    border-bottom: 1px solid #d8bfd8; }
+    border-top: 1px solid #e0cbe0;
+    border-bottom: 1px solid #e0cbe0; }
     .block .minicalendar td.duration_category.duration_finish {
-      background-color: #d8bfd8; }
+      background-color: #e0cbe0; }
   .block .minicalendar td.duration_course {
     border-top: 1px solid #ffd3bd;
     border-bottom: 1px solid #ffd3bd; }
@@ -12949,6 +12968,18 @@ body.h5p-embed .h5pmessages {
 
 .block .calendar_filters li {
   margin-bottom: 0.2em; }
+  .block .calendar_filters li span.calendar_event_category i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_course i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_site i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_group i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_user i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_other i {
+    color: #0d5ca1; }
   .block .calendar_filters li span img {
     padding: 0 0.2em;
     margin: 0; }
index de04da2..538da50 100644 (file)
@@ -12936,22 +12936,34 @@ body.h5p-embed .h5pmessages {
 
 /* calendar.less */
 .calendar_event_category {
-  background-color: #d8bfd8; }
+  background-color: #e0cbe0; }
+  .calendar_event_category .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_course {
   background-color: #ffd3bd; }
+  .calendar_event_course .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_site {
   background-color: #d6f8cd; }
+  .calendar_event_site .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_group {
   background-color: #fee7ae; }
+  .calendar_event_group .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_user {
   background-color: #dce7ec; }
+  .calendar_event_user .commands a {
+    color: #0d5ca1; }
 
 .calendar_event_other {
   background-color: #ced4da; }
+  .calendar_event_other .commands a {
+    color: #0d5ca1; }
 
 .path-calendar .calendartable {
   width: 100%;
@@ -13028,17 +13040,23 @@ body.h5p-embed .h5pmessages {
           border-radius: 6px;
           vertical-align: middle; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_category {
-            background-color: #d8bfd8; }
+            background-color: #e0cbe0;
+            border: 2px solid #9e619f; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_course {
-            background-color: #ffd3bd; }
+            background-color: #ffd3bd;
+            border: 2px solid #d34600; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_site {
-            background-color: #d6f8cd; }
+            background-color: #d6f8cd;
+            border: 2px solid #2b8713; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_group {
-            background-color: #fee7ae; }
+            background-color: #fee7ae;
+            border: 2px solid #9a6e02; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_user {
-            background-color: #dce7ec; }
+            background-color: #dce7ec;
+            border: 2px solid #4e7c91; }
           .path-calendar .maincalendar .calendarmonth ul li .badge.badge-circle.calendar_event_other {
-            background-color: #ced4da; }
+            background-color: #ced4da;
+            border: 2px solid #687889; }
     .path-calendar .maincalendar .calendarmonth td {
       height: 5em; }
     .path-calendar .maincalendar .calendarmonth .clickable:hover {
@@ -13059,7 +13077,7 @@ body.h5p-embed .h5pmessages {
     color: #0b4f8a;
     text-decoration: underline; }
   .path-calendar .maincalendar .calendar_event_category {
-    border-color: #d8bfd8; }
+    border-color: #e0cbe0; }
   .path-calendar .maincalendar .calendar_event_course {
     border-color: #ffd3bd; }
   .path-calendar .maincalendar .calendar_event_site {
@@ -13102,17 +13120,18 @@ body.h5p-embed .h5pmessages {
   .block .minicalendar td a {
     width: 100%;
     height: 100%;
-    display: block; }
+    display: block;
+    color: #0d5ca1; }
   .block .minicalendar td.duration_global {
     border-top: 1px solid #d6f8cd;
     border-bottom: 1px solid #d6f8cd; }
     .block .minicalendar td.duration_global.duration_finish {
       background-color: #d6f8cd; }
   .block .minicalendar td.duration_category {
-    border-top: 1px solid #d8bfd8;
-    border-bottom: 1px solid #d8bfd8; }
+    border-top: 1px solid #e0cbe0;
+    border-bottom: 1px solid #e0cbe0; }
     .block .minicalendar td.duration_category.duration_finish {
-      background-color: #d8bfd8; }
+      background-color: #e0cbe0; }
   .block .minicalendar td.duration_course {
     border-top: 1px solid #ffd3bd;
     border-bottom: 1px solid #ffd3bd; }
@@ -13164,6 +13183,18 @@ body.h5p-embed .h5pmessages {
 
 .block .calendar_filters li {
   margin-bottom: 0.2em; }
+  .block .calendar_filters li span.calendar_event_category i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_course i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_site i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_group i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_user i {
+    color: #0d5ca1; }
+  .block .calendar_filters li span.calendar_event_other i {
+    color: #0d5ca1; }
   .block .calendar_filters li span img {
     padding: 0 0.2em;
     margin: 0; }