Commit ID: 86618aaf0406351f1d249e5f328b305e36ffa89e
Change ID: uuqkpqyoxozwxqyptqkxnqpqklnymnmr
Author   : Gabriel Fontes <hi@m7.rs> (2025-12-18 09:23:01)
Committer: Gabriel Fontes <hi@m7.rs> (2025-12-18 16:47:14)
Signature: (no signature)

    feat(tasklist): use none/low/medium/high priority instead of true/false flag

diff --git a/plugins/libkolab/lib/kolab_format_task.php b/plugins/libkolab/lib/kolab_format_task.php
index 5f96f0cc87..57c58ca043 100644
--- a/plugins/libkolab/lib/kolab_format_task.php
+++ b/plugins/libkolab/lib/kolab_format_task.php
@@ -156,7 +156,7 @@
         }
 
         if (!empty($object['priority'])) {
-            $tags[] = 'x-flagged';
+            $tags[] = 'x-highpriority';
         }
 
         if (!empty($object['parent_id'])) {
diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache_task.php b/plugins/libkolab/lib/kolab_storage_dav_cache_task.php
index b20c9963e5..9f9e16bd7b 100644
--- a/plugins/libkolab/lib/kolab_storage_dav_cache_task.php
+++ b/plugins/libkolab/lib/kolab_storage_dav_cache_task.php
@@ -98,8 +98,8 @@
             $tags[] = 'x-complete';
         }
 
-        if (!empty($object['priority']) && $object['priority'] == 1) {
-            $tags[] = 'x-flagged';
+        if (!empty($object['priority']) && $object['priority'] >= 1 && $object['priority'] <= 4) {
+            $tags[] = 'x-highpriority';
         }
 
         if (!empty($object['parent_id'])) {
diff --git a/plugins/libkolab/skins/elastic/include/darkmode.less b/plugins/libkolab/skins/elastic/include/darkmode.less
index febed080ae..21cbcfc95c 100644
--- a/plugins/libkolab/skins/elastic/include/darkmode.less
+++ b/plugins/libkolab/skins/elastic/include/darkmode.less
@@ -107,10 +107,32 @@
     body.task-calendar .ui-datepicker-inline .ui-datepicker-activerange,
     .listing li.selected > div > *:not(.count),
     #tasklist li.taskitem div.taskhead.selected {
-        color: @color-dark-list-selected;
         background-color: @color-dark-list-selected-background;
     }
 
+    #tasklist li.taskitem div.taskhead.priority-low {
+        color: @color-main;
+    }
+    #tasklist li.taskitem div.taskhead.priority-medium {
+        color: @color-warning;
+    }
+    #tasklist li.taskitem div.taskhead.priority-high {
+        color: @color-error;
+    }
+
+    #taskshow.status-priority-none h2:before {
+        color: @color-dark-font;
+    }
+    #taskshow.status-priority-low h2:before {
+        color: @color-main;
+    }
+    #taskshow.status-priority-medium h2:before {
+        color: @color-warning;
+    }
+    #taskshow.status-priority-high h2:before {
+        color: @color-error;
+    }
+
     #folder-mount-form td.source.selected {
         background-color: @color-dark-list-selected-background;
     }
diff --git a/plugins/libkolab/skins/elastic/include/tasklist.less b/plugins/libkolab/skins/elastic/include/tasklist.less
index 8cc8196d8d..94ffbc3122 100644
--- a/plugins/libkolab/skins/elastic/include/tasklist.less
+++ b/plugins/libkolab/skins/elastic/include/tasklist.less
@@ -60,8 +60,14 @@
             padding-left: (1 * @listing-treetoggle-width);
             outline: 0;
 
-            &.flagged {
-                color: @color-list-flagged !important;
+            &.priority-high {
+                color: @color-dark-error;
+            }
+            &.priority-medium {
+                color: @color-dark-warning;
+            }
+            &.priority-low {
+                color: @color-dark-main;
             }
 
             &.selected {
@@ -186,7 +192,7 @@
         }
     }
 
-    span.flagged {
+    span.priority {
         position: absolute;
         right: 0;
         top: 0;
@@ -194,14 +200,22 @@
         cursor: pointer;
     }
 
-    .taskhead:not(.flagged):hover span.flagged:before {
+    .taskhead.priority-none span.priority:before {
         &:extend(.font-icon-class);
-        .font-icon-regular(@fa-var-flag);
+        content: @fa-var-angle-double-down;
     }
 
-    .taskhead.flagged span.flagged:before {
-        &:extend(.font-icon-class);
-        content: @fa-var-flag;
+    .taskhead.priority-low span.priority:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-down;
+    }
+    .taskhead.priority-medium span.priority:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-up;
+    }
+    .taskhead.priority-high span.priority:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-double-up;
     }
 
     .progressbar {
@@ -244,7 +258,7 @@
         font-size: 1rem;
     }
 
-    span.flagged,
+    span.priority,
     input[type=checkbox],
     .custom-switch {
         display: none;
@@ -285,10 +299,28 @@
         color: @color-list-deleted;
     }
 
-    &.status-flagged h2:before {
-        &:extend(.font-icon-class);
-        content: @fa-var-flag;
-        color: @color-error;
+    &.status-priority-none h2:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-double-down;
+        color: @color-font;
+        font-size: .9em;
+    }
+    &.status-priority-low h2:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-down;
+        color: @color-dark-main;
+        font-size: .9em;
+    }
+    &.status-priority-medium h2:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-up;
+        color: @color-dark-warning;
+        font-size: .9em;
+    }
+    &.status-priority-high h2:before {
+        &:extend(.font-icon-class);
+        content: @fa-var-angle-double-up;
+        color: @color-dark-error;
         font-size: .9em;
     }
 
diff --git a/plugins/libkolab/skins/elastic/libkolab.less b/plugins/libkolab/skins/elastic/libkolab.less
index 6418575d31..f233282aa1 100644
--- a/plugins/libkolab/skins/elastic/libkolab.less
+++ b/plugins/libkolab/skins/elastic/libkolab.less
@@ -148,7 +148,6 @@
         }
 
         &.selected > div > *:not(.count) {
-            color: @color-list-selected;
             background-color: @color-list-selected-background;
         }
 
diff --git a/plugins/tasklist/config.inc.php.dist b/plugins/tasklist/config.inc.php.dist
index 845a3ebc4c..d244b45f6b 100644
--- a/plugins/tasklist/config.inc.php.dist
+++ b/plugins/tasklist/config.inc.php.dist
@@ -6,7 +6,7 @@
 // CalDAV server location (required when tasklist_driver = caldav)
 $config['tasklist_caldav_server'] = "http://localhost";
 
-// default sorting order of tasks listing (auto, datetime, startdatetime, flagged, complete, changed)
+// default sorting order of tasks listing (auto, datetime, startdatetime, priority, complete, changed)
 $config['tasklist_sort_col'] = '';
 
 // default sorting order for tasks listing (asc or desc)
diff --git a/plugins/tasklist/drivers/caldav/tasklist_caldav_driver.php b/plugins/tasklist/drivers/caldav/tasklist_caldav_driver.php
index aba2d76639..8da1983c93 100644
--- a/plugins/tasklist/drivers/caldav/tasklist_caldav_driver.php
+++ b/plugins/tasklist/drivers/caldav/tasklist_caldav_driver.php
@@ -460,7 +460,7 @@
      *
      * @param array $lists List of lists to count tasks of
      *
-     * @return array Hash array with counts grouped by status (all|flagged|completed|today|tomorrow|nodate)
+     * @return array Hash array with counts grouped by status (all|highpriority|completed|today|tomorrow|nodate)
      */
     public function count_tasks($lists = null)
     {
@@ -807,11 +807,6 @@
                         if (array_key_exists($change['property'], $keymap)) {
                             $change['property'] = $keymap[$change['property']];
                         }
-                        if ($change['property'] == 'priority') {
-                            $change['property'] = 'flagged';
-                            $change['old'] = $change['old'] == 1 ? $this->plugin->gettext('yes') : null;
-                            $change['new'] = $change['new'] == 1 ? $this->plugin->gettext('yes') : null;
-                        }
                         // map alarms trigger value
                         if ($change['property'] == 'alarms') {
                             if (is_array($change['old']) && is_array($change['old']['trigger']))
@@ -1121,7 +1116,7 @@
             'title' => $record['title'] ?? '',
 //            'location' => $record['location'],
             'description' => $record['description'] ?? '',
-            'flagged' => !empty($record['priority']) && $record['priority'] == 1,
+            'priority' => intval($record['priority'] ?? 0),
             'complete' => floatval(($record['complete'] ?? 0) / 100),
             'status' => $record['status'] ?? null,
             'parent_id' => !empty($record['parent_id']) ? $id_prefix . $record['parent_id'] : null,
@@ -1241,12 +1236,6 @@
             $object['status'] = 'COMPLETED';
         }
 
-        if (!empty($task['flagged'])) {
-            $object['priority'] = 1;
-        } else {
-            $object['priority'] = isset($old['priority']) && $old['priority'] > 1 ? $old['priority'] : 0;
-        }
-
         // remove list: prefix from parent_id
         if (!empty($task['parent_id']) && strpos($task['parent_id'], $id_prefix) === 0) {
             $object['parent_id'] = substr($task['parent_id'], strlen($id_prefix));
@@ -1276,7 +1265,7 @@
 
         $object['categories'] = (array) ($task['tags'] ?? []);
 
-        unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['created']);
+        unset($object['tempid'], $object['raw'], $object['list'], $object['created']);
 
         return $object;
     }
diff --git a/plugins/tasklist/drivers/database/SQL/mysql.initial.sql b/plugins/tasklist/drivers/database/SQL/mysql.initial.sql
index 6751122f27..7a913b9628 100644
--- a/plugins/tasklist/drivers/database/SQL/mysql.initial.sql
+++ b/plugins/tasklist/drivers/database/SQL/mysql.initial.sql
@@ -33,7 +33,7 @@
   `time` varchar(5) DEFAULT NULL,
   `startdate` varchar(10) DEFAULT NULL,
   `starttime` varchar(5) DEFAULT NULL,
-  `flagged` tinyint(4) NOT NULL DEFAULT '0',
+  `priority` tinyint(4) NOT NULL DEFAULT '0',
   `complete` float NOT NULL DEFAULT '0',
   `status` enum('','NEEDS-ACTION','IN-PROCESS','COMPLETED','CANCELLED') NOT NULL DEFAULT '',
   `alarms` varchar(255) DEFAULT NULL,
diff --git a/plugins/tasklist/drivers/database/SQL/postgres.initial.sql b/plugins/tasklist/drivers/database/SQL/postgres.initial.sql
index d6b536a6e1..d0a311f1f3 100644
--- a/plugins/tasklist/drivers/database/SQL/postgres.initial.sql
+++ b/plugins/tasklist/drivers/database/SQL/postgres.initial.sql
@@ -46,7 +46,7 @@
     time varchar(5) DEFAULT NULL,
     startdate varchar(10) DEFAULT NULL,
     starttime varchar(5) DEFAULT NULL,
-    flagged smallint NOT NULL DEFAULT 0,
+    priority smallint NOT NULL DEFAULT 0,
     complete float NOT NULL DEFAULT 0,
     status varchar(16) NOT NULL DEFAULT '',
     alarms varchar(255) DEFAULT NULL,
diff --git a/plugins/tasklist/drivers/database/SQL/sqlite.initial.sql b/plugins/tasklist/drivers/database/SQL/sqlite.initial.sql
index b39a8bc0da..6262f6ea94 100644
--- a/plugins/tasklist/drivers/database/SQL/sqlite.initial.sql
+++ b/plugins/tasklist/drivers/database/SQL/sqlite.initial.sql
@@ -33,7 +33,7 @@
     "time" varchar(5) DEFAULT NULL,
     startdate varchar(10) DEFAULT NULL,
     starttime varchar(5) DEFAULT NULL,
-    flagged tinyint NOT NULL DEFAULT '0',
+    priority tinyint NOT NULL DEFAULT '0',
     complete real NOT NULL DEFAULT '0',
     status varchar(16) NOT NULL DEFAULT '',
     alarms varchar(255) DEFAULT NULL,
diff --git a/plugins/tasklist/drivers/database/tasklist_database_driver.php b/plugins/tasklist/drivers/database/tasklist_database_driver.php
index 8d244636ae..7f232ad297 100644
--- a/plugins/tasklist/drivers/database/tasklist_database_driver.php
+++ b/plugins/tasklist/drivers/database/tasklist_database_driver.php
@@ -229,7 +229,7 @@
      *
      * @param array $lists List of lists to count tasks of
      *
-     * @return array Hash array with counts grouped by status (all|flagged|today|tomorrow|overdue|nodate)
+     * @return array Hash array with counts grouped by status (all|highpriority|today|tomorrow|overdue|nodate)
      * @see tasklist_driver::count_tasks()
      */
     public function count_tasks($lists = null)
@@ -249,7 +249,7 @@
         $tomorrow      = $tomorrow_date->format('Y-m-d');
 
         $result = $this->db->query(sprintf(
-            "SELECT `task_id`, `flagged`, `date` FROM " . $this->db_tasks
+            "SELECT `task_id`, `priority`, `date` FROM " . $this->db_tasks
              . " WHERE `tasklist_id` IN (%s) AND `del` = 0 AND NOT " . self::IS_COMPLETE_SQL,
             implode(',', $list_ids)
         ));
@@ -319,8 +319,8 @@
                 $sql_add .= " AND NOT " . self::IS_COMPLETE_SQL;
             }
 
-            if ($filter['mask'] & tasklist::FILTER_MASK_FLAGGED) {
-                $sql_add .= " AND `flagged` = 1";
+            if ($filter['mask'] & tasklist::FILTER_MASK_HIGHPRIORITY) {
+                $sql_add .= " AND `priority` >= 1 AND `priority` <= 4";
             }
 
             // compose (slow) SQL query for searching
@@ -587,7 +587,7 @@
         $result = $this->db->query(
             "INSERT INTO " . $this->db_tasks
             . " (`tasklist_id`, `uid`, `parent_id`, `created`, `changed`, `title`, `date`, `time`,"
-                . " `startdate`, `starttime`, `description`, `tags`, `flagged`, `complete`, `status`,"
+                . " `startdate`, `starttime`, `description`, `tags`, `priority`, `complete`, `status`,"
                 . " `alarms`, `recurrence`, `notify`)"
             . " VALUES (?, ?, ?, $now, $now, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
             $list_id,
@@ -600,7 +600,7 @@
             $prop['starttime'],
             isset($prop['description']) ? strval($prop['description']) : '',
             !empty($prop['tags']) ? implode(',', (array)$prop['tags']) : '',
-            !empty($prop['flagged']) ? 1 : 0,
+            $prop['priority']) ? 0,
             $prop['complete'] ?: 0,
             strval($prop['status']),
             $prop['alarms'] ?? '',
@@ -636,7 +636,7 @@
         }
 
         $sql_set = [];
-        foreach (['title', 'description', 'flagged', 'complete'] as $col) {
+        foreach (['title', 'description', 'priority', 'complete'] as $col) {
             if (isset($prop[$col])) {
                 $sql_set[] = $this->db->quote_identifier($col) . '=' . $this->db->quote($prop[$col]);
             }
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index 2f7c59bdfa..d94598494d 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -579,7 +579,7 @@
      *
      * @param array $lists List of lists to count tasks of
      *
-     * @return array Hash array with counts grouped by status (all|flagged|completed|today|tomorrow|nodate)
+     * @return array Hash array with counts grouped by status (all|highpriority|completed|today|tomorrow|nodate)
      */
     public function count_tasks($lists = null)
     {
@@ -934,11 +934,6 @@
                 if (array_key_exists($change['property'], $keymap)) {
                     $change['property'] = $keymap[$change['property']];
                 }
-                if ($change['property'] == 'priority') {
-                    $change['property'] = 'flagged';
-                    $change['old'] = $change['old'] == 1 ? $this->plugin->gettext('yes') : null;
-                    $change['new'] = $change['new'] == 1 ? $this->plugin->gettext('yes') : null;
-                }
                 // map alarms trigger value
                 if ($change['property'] == 'alarms') {
                     if (is_array($change['old']) && is_array($change['old']['trigger'])) {
@@ -1288,7 +1283,7 @@
             'title' => $record['title'] ?? null,
 //            'location' => $record['location'],
             'description' => $record['description'] ?? null,
-            'flagged' => ($record['priority'] ?? null) == 1,
+            'priority' => $record['priority'] ?? 0,
             'complete' => floatval(($record['complete'] ?? null) / 100),
             'status' => $record['status'] ?? null,
             'parent_id' => ($record['parent_id'] ?? null) ? $id_prefix . $record['parent_id'] : null,
@@ -1409,12 +1404,6 @@
             $object['status'] = 'COMPLETED';
         }
 
-        if (!empty($task['flagged'])) {
-            $object['priority'] = 1;
-        } else {
-            $object['priority'] = ($old['priority'] ?? 0) > 1 ? $old['priority'] : 0;
-        }
-
         // remove list: prefix from parent_id
         if (!empty($task['parent_id']) && strpos($task['parent_id'], $id_prefix) === 0) {
             $object['parent_id'] = substr($task['parent_id'], strlen($id_prefix));
@@ -1442,7 +1431,7 @@
             $object['sequence'] = $old['sequence'];
         }
 
-        unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags'], $object['created']);
+        unset($object['tempid'], $object['raw'], $object['list'], $object['tags'], $object['created']);
         return $object;
     }
 
diff --git a/plugins/tasklist/drivers/tasklist_driver.php b/plugins/tasklist/drivers/tasklist_driver.php
index 35552864c9..5e33dbeb93 100644
--- a/plugins/tasklist/drivers/tasklist_driver.php
+++ b/plugins/tasklist/drivers/tasklist_driver.php
@@ -39,7 +39,7 @@
  *     'startdate' => 'Start date'  // Delay start of the task until that date
  *     'starttime' => 'Start time'  // ...and time
  *    'categories' => 'Task category',
- *       'flagged' => 'Boolean value whether this record is flagged',
+ *       'priority'=> 'Int representing the task priority',
  *      'complete' => 'Float value representing the completeness state (range 0..1)',
  *      'status'   => 'Task status string according to (NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED) RFC 2445',
  *       'valarms' => array(           // List of reminders (new format), each represented as a hash array:
@@ -155,7 +155,7 @@
      *
      * @param array $lists List of lists to count tasks of
      *
-     * @return array Hash array with counts grouped by status (all|flagged|completed|today|tomorrow|nodate)
+     * @return array Hash array with counts grouped by status (all|highpriority|completed|today|tomorrow|nodate)
      */
     abstract public function count_tasks($lists = null);
 
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index d7bed7d8a8..c557e2edb8 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -58,7 +58,12 @@
 $labels['taskoptions'] = 'Options';
 
 $labels['all'] = 'All';
-$labels['flagged'] = 'Flagged';
+$labels['highpriority'] = 'High Priority';
+$labels['priority'] = 'Priority';
+$labels['priority-none'] = 'None';
+$labels['priority-low'] = 'Low';
+$labels['priority-medium'] = 'Medium';
+$labels['priority-high'] = 'High';
 $labels['complete'] = 'Complete';
 $labels['completeness'] = 'Progress';
 $labels['overdue'] = 'Overdue';
diff --git a/plugins/tasklist/localization/pt_BR.inc b/plugins/tasklist/localization/pt_BR.inc
index 2d88705774..65d40f58da 100644
--- a/plugins/tasklist/localization/pt_BR.inc
+++ b/plugins/tasklist/localization/pt_BR.inc
@@ -53,7 +53,8 @@
 $labels['changed'] = 'Última modificação';
 $labels['taskoptions'] = 'Opções';
 $labels['all'] = 'Todos';
-$labels['flagged'] = 'Marcado';
+$labels['highpriority'] = 'Alta Prioridade';
+$labels['priority'] = 'Prioridade';
 $labels['complete'] = 'Completo';
 $labels['completeness'] = 'Progresso';
 $labels['overdue'] = 'Atrasado';
diff --git a/plugins/tasklist/skins/elastic/templates/mainview.html b/plugins/tasklist/skins/elastic/templates/mainview.html
index ef944b5c7f..2079e67a00 100644
--- a/plugins/tasklist/skins/elastic/templates/mainview.html
+++ b/plugins/tasklist/skins/elastic/templates/mainview.html
@@ -50,7 +50,7 @@
 			<roundcube:endif />
 			<li class="later" role="radio" aria-checked="false" aria-labelledby="aria-radio-later"><a href="#later" id="aria-radio-later"><roundcube:label name="tasklist.later" /><span class="count"></span></a></li>
 			<li class="nodate" role="radio" aria-checked="false" aria-labelledby="aria-radio-nodate"><a href="#nodate" id="aria-radio-nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li>
-			<li class="flagged" role="radio" aria-checked="false" aria-labelledby="aria-radio-flagged"><a href="#flagged" id="aria-radio-flagged"><roundcube:label name="tasklist.flagged" /></a></li>
+			<li class="highpriority" role="radio" aria-checked="false" aria-labelledby="aria-radio-highpriority"><a href="#highpriority" id="aria-radio-highpriority"><roundcube:label name="tasklist.highpriority" /></a></li>
 			<roundcube:if condition="env:tasklist_driver == 'kolab'" />
 			<li class="mytasks" role="radio" aria-checked="false" aria-labelledby="aria-radio-mytasks"><a href="#mytasks" id="aria-radio-mytasks" title="<roundcube:label name='tasklist.mytaskstitle'/>"><roundcube:label name="tasklist.mytasks" /></a></li>
 			<li class="assigned" role="radio" aria-checked="false" aria-labelledby="aria-radio-assigned"><a href="#assigned" id="aria-radio-assigned" title="<roundcube:label name='tasklist.assignedtitle'/>"><roundcube:label name="tasklist.assigned" /></a></li>
@@ -139,6 +139,10 @@
 				<label class="col-sm-4 col-form-label"><roundcube:label name="tasklist.list" /></label>
 				<span class="task-text col-sm-8 form-control-plaintext"></span>
 			</div>
+			<div id="task-priority" class="form-group row">
+				<label class="col-sm-4 col-form-label"><roundcube:label name="tasklist.priority" /></label>
+				<span class="task-text col-sm-8 form-control-plaintext"></span>
+			</div>
 			<div id="task-completeness" class="form-group row">
 				<label class="col-sm-4 col-form-label"><roundcube:label name="tasklist.complete" /></label>
 				<span class="task-text col-sm-8 form-control-plaintext"></span>
@@ -199,8 +203,8 @@
 		<div class="task-text-old"></div>
 		<div class="task-text-new"></div>
 	</div>
-	<div class="form-group row task-flagged">
-		<label><roundcube:label name="tasklist.flagged" /></label>
+	<div class="form-group row task-priority">
+		<label><roundcube:label name="tasklist.priority" /></label>
 		<span class="task-text-old"></span> &#8674;
 		<span class="task-text-new"></span>
 	</div>
@@ -278,7 +282,7 @@
 				<option value="auto"><roundcube:label name="tasklist.auto" /></option>
 				<option value="datetime"><roundcube:label name="tasklist.datetime" /></option>
 				<option value="startdatetime"><roundcube:label name="tasklist.start" /></option>
-				<option value="flagged"><roundcube:label name="tasklist.flagged" /></option>
+				<option value="priority"><roundcube:label name="tasklist.priority" /></option>
 				<option value="complete"><roundcube:label name="tasklist.completeness" /></option>
 				<option value="changed"><roundcube:label name="tasklist.changed" /></option>
 			</select>
diff --git a/plugins/tasklist/skins/elastic/templates/taskedit.html b/plugins/tasklist/skins/elastic/templates/taskedit.html
index 2619d703a2..1deb9bc552 100644
--- a/plugins/tasklist/skins/elastic/templates/taskedit.html
+++ b/plugins/tasklist/skins/elastic/templates/taskedit.html
@@ -56,6 +56,12 @@
 					<roundcube:object name="plugin.status_select" id="taskedit-status" class="form-control" />
 				</div>
 			</div>
+			<div class="form-group row">
+				<label for="taskedit-priority" class="col-sm-2 col-form-label"><roundcube:label name="tasklist.priority" /></label>
+				<div class="col-sm-10">
+					<roundcube:object name="plugin.priority_select" id="taskedit-priority" class="form-control" />
+				</div>
+			</div>
 			<div class="form-group row" id="tasklist-select">
 				<label for="taskedit-tasklist" class="col-sm-2 col-form-label"><roundcube:label name="tasklist.list" /></label>
 				<div class="col-sm-10">
diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html
index ef14037ab4..13a0280639 100644
--- a/plugins/tasklist/skins/larry/templates/mainview.html
+++ b/plugins/tasklist/skins/larry/templates/mainview.html
@@ -90,7 +90,7 @@
 				<roundcube:endif />
 				<li class="later" role="radio" aria-checked="false" aria-labelledby="aria-radio-later"><a href="#later" id="aria-radio-later"><roundcube:label name="tasklist.later" /><span class="count"></span></a></li>
 				<li class="nodate" role="radio" aria-checked="false" aria-labelledby="aria-radio-nodate"><a href="#nodate" id="aria-radio-nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li>
-				<li class="flagged" role="radio" aria-checked="false" aria-labelledby="aria-radio-flagged"><a href="#flagged" id="aria-radio-flagged"><roundcube:label name="tasklist.flagged" /></a></li>
+				<li class="highpriority" role="radio" aria-checked="false" aria-labelledby="aria-radio-highpriority"><a href="#highpriority" id="aria-radio-highpriority"><roundcube:label name="tasklist.highpriority" /></a></li>
 				<roundcube:if condition="env:tasklist_driver == 'kolab'" />
 				<li class="mytasks" role="radio" aria-checked="false" aria-labelledby="aria-radio-mytasks"><a href="#mytasks" id="aria-radio-mytasks" title="<roundcube:label name='tasklist.mytaskstitle'/>"><roundcube:label name="tasklist.mytasks" /></a></li>
 				<li class="assigned" role="radio" aria-checked="false" aria-labelledby="aria-radio-assigned"><a href="#assigned" id="aria-radio-assigned" title="<roundcube:label name='tasklist.assignedtitle'/>"><roundcube:label name="tasklist.assigned" /></a></li>
@@ -110,7 +110,7 @@
 						<li><roundcube:button command="list-sort" prop="auto" type="link" label="tasklist.auto" role="radio" aria-checked="false" class="sortcol by-auto icon active" innerclass="icon" /></li>
 						<li><roundcube:button command="list-sort" prop="datetime" type="link" label="tasklist.datetime" role="radio" aria-checked="false" class="sortcol by-datetime icon active" innerclass="icon" /></li>
 						<li><roundcube:button command="list-sort" prop="startdatetime" type="link" label="tasklist.start" role="radio" aria-checked="false" class="sortcol by-startdatetime icon active" innerclass="icon" /></li>
-						<li><roundcube:button command="list-sort" prop="flagged" type="link" label="tasklist.flagged" role="radio" aria-checked="false" class="sortcol by-flagged icon active" innerclass="icon" /></li>
+						<li><roundcube:button command="list-sort" prop="priority" type="link" label="tasklist.priority" role="radio" aria-checked="false" class="sortcol by-priority icon active" innerclass="icon" /></li>
 						<li><roundcube:button command="list-sort" prop="complete" type="link" label="tasklist.completeness" role="radio" aria-checked="false" class="sortcol by-complete icon active" innerclass="icon" /></li>
 						<li><roundcube:button command="list-sort" prop="changed" type="link" label="tasklist.changed" role="radio" aria-checked="false" class="sortcol by-changed icon active" innerclass="icon" /></li>
 					</ul>
@@ -257,8 +257,8 @@
 		<div class="task-text-old"></div>
 		<div class="task-text-new"></div>
 	</div>
-	<div class="form-section task-flagged">
-		<label><roundcube:label name="tasklist.flagged" /></label>
+	<div class="form-section task-priority">
+		<label><roundcube:label name="tasklist.priority" /></label>
 		<span class="task-text-old"></span> &#8674;
 		<span class="task-text-new"></span>
 	</div>
@@ -367,4 +367,4 @@
 </script>
 
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/plugins/tasklist/skins/larry/templates/taskedit.html b/plugins/tasklist/skins/larry/templates/taskedit.html
index 80940eb52a..35c67b0f3e 100644
--- a/plugins/tasklist/skins/larry/templates/taskedit.html
+++ b/plugins/tasklist/skins/larry/templates/taskedit.html
@@ -50,6 +50,10 @@
 				<label for="taskedit-status"><roundcube:label name="tasklist.status" /></label>
 				<roundcube:object name="plugin.status_select" id="taskedit-status" />
 			</div>
+			<div class="form-section">
+				<label for="taskedit-priority"><roundcube:label name="tasklist.priority" /></label>
+				<roundcube:object name="plugin.priority_select" id="taskedit-priority" />
+			</div>
 			<div class="form-section" id="tasklist-select">
 				<label for="taskedit-tasklist"><roundcube:label name="tasklist.list" /></label>
 				<roundcube:object name="plugin.tasklist_select" id="taskedit-tasklist" />
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index f79d57a5cc..c19f076fcd 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -38,7 +38,7 @@
     var FILTER_MASK_LATER = 8;
     var FILTER_MASK_NODATE = 16;
     var FILTER_MASK_OVERDUE = 32;
-    var FILTER_MASK_FLAGGED = 64;
+    var FILTER_MASK_HIGHPRIORITY = 64;
     var FILTER_MASK_COMPLETE = 128;
     var FILTER_MASK_ASSIGNED = 256;
     var FILTER_MASK_MYTASKS = 512;
@@ -51,7 +51,7 @@
         later:    FILTER_MASK_LATER,
         nodate:   FILTER_MASK_NODATE,
         overdue:  FILTER_MASK_OVERDUE,
-        flagged:  FILTER_MASK_FLAGGED,
+        highpriority: FILTER_MASK_HIGHPRIORITY,
         complete: FILTER_MASK_COMPLETE,
         assigned: FILTER_MASK_ASSIGNED,
         mytasks:  FILTER_MASK_MYTASKS
@@ -400,12 +400,13 @@
                     item.toggleClass('complete');
                     return true;
 
-                case 'flagged':
+                case 'priority':
                     if (rcmail.busy)
                         return false;
 
-                    rec.flagged = rec.flagged ? 0 : 1;
-                    item.toggleClass('flagged').find('.flagged:first').attr('aria-checked', (rec.flagged ? 'true' : 'false'));
+                    item.removeClass('priority-' + get_priority_name(rec))
+                    rec.priority = cycle_priority(rec);
+                    item.addClass('priority-' + get_priority_name(rec))
                     save_task(rec, 'edit');
                     break;
 
@@ -1422,7 +1423,7 @@
         var div = $('<div>').addClass('taskhead').html(
             '<div class="progressbar"><div class="progressvalue" style="width:' + (rec.complete * 100) + '%"></div></div>' +
             '<input type="checkbox" name="completed[]" value="1" class="complete" aria-label="' + rcmail.gettext('complete','tasklist') + '" ' + (is_complete(rec) ? 'checked="checked" ' : '') + '/>' + 
-            '<span class="flagged" role="checkbox" tabindex="0" aria-checked="' + (rec.flagged ? 'true' : 'false') + '" aria-label="' + rcmail.gettext('flagged','tasklist') + '"></span>' +
+            '<span class="priority" role="button" tabindex="0" aria-label="' + rcmail.gettext('priority','tasklist') + '"></span>' +
             '<span class="title" id="' + label_id + '">' + text2html(Q(rec.title)) + '</span>' +
             '<span class="tags"></span>' +
             '<span class="date">' + Q(rec.date || rcmail.gettext('nodate','tasklist')) + '</span>' +
@@ -1457,13 +1458,13 @@
 
         if (is_complete(rec))
             div.addClass('complete');
-        if (rec.flagged)
-            div.addClass('flagged');
         if (!rec.date)
             div.addClass('nodate');
         if ((rec.mask & FILTER_MASK_OVERDUE))
             div.addClass('overdue');
 
+        div.addClass('priority-' + get_priority_name(rec));
+
         var li, inplace = false, parent = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > ul.childtasks', rcmail.gui_objects.resultlist) : null;
         if (replace && (li = $('li[rel="'+replace+'"]', rcmail.gui_objects.resultlist)) && li.length) {
             li.children('div.taskhead').first().replaceWith(div);
@@ -1516,8 +1517,8 @@
 /*
         if (is_complete(rec))
             div.addClass('complete');
-        if (rec.flagged)
-            div.addClass('flagged');
+        if (rec.priority)
+            div.addClass('priority-' + get_priority_name(rec));
         if (!rec.date)
             div.addClass('nodate');
         if (rec.mask & FILTER_MASK_OVERDUE)
@@ -1623,19 +1624,16 @@
         if (c != 0)
             return c;
 
-        // custom sorting
+        // custom sorting (higher priority)
         if (settings.sort_col && settings.sort_col != 'auto') {
-            alt = settings.sort_col == 'datetime' || settings.sort_col == 'startdatetime' ? 99999999999 : 0
+            alt = settings.sort_col == 'datetime' || settings.sort_col == 'startdatetime' || settings.sort_col == 'priority' ? 99999999999 : 0
             d = (a[settings.sort_col]||alt) - (b[settings.sort_col]||alt);
             inv = settings.sort_order == 'desc' ? -1 : 1;
         }
         // default sorting (auto)
-        else {
-            if (!d) d = (b._hasdate-0) - (a._hasdate-0);
-            if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
-        }
-
-        // fall-back to created/changed date
+        if (!d) d = (b._hasdate-0) - (a._hasdate-0);
+        if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
+        if (!d) d = (a.priority||999) - (b.priority||999);
         if (!d) d = (a.created||0) - (b.created||0);
         if (!d) d = (a.changed||0) - (b.changed||0);
 
@@ -1662,6 +1660,39 @@
     }
 
     /**
+     * Determine priority name
+     */
+    function get_priority_name(rec)
+    {
+        // https://icalendar.org/iCalendar-RFC-5545/3-8-1-9-priority.html
+        if (!rec.priority || rec.priority == 0)
+            return 'none';
+        if (rec.priority >= 6 && rec.priority <= 9)
+            return 'low';
+        if (rec.priority == 5)
+            return 'medium';
+        if (rec.priority >= 1 && rec.priority <= 4)
+            return 'high';
+
+        return 'none';
+    }
+
+    /**
+     * Switch priority
+     */
+    function cycle_priority(rec)
+    {
+        if (!rec.priority || rec.priority == 0)
+            return 9; // None->Low
+        if (rec.priority >= 6 && rec.priority <= 9)
+            return 5; // Low->Medium
+        if (rec.priority == 5)
+            return 4; // Medium->High
+        if (rec.priority >= 1 && rec.priority <= 4)
+            return 0; // High->None
+    }
+
+    /**
      *
      */
     function get_all_childs(id)
@@ -2005,6 +2036,7 @@
         });
 
         var status = rcmail.gettext('status-' + String(rec.status).toLowerCase(),'tasklist');
+        var priority = rcmail.gettext('priority-' + get_priority_name(rec),'tasklist');
 
         // fill dialog data
         $('#task-parent-title').html(Q(rec.parent_title || '')+' &raquo;').css('display', rec.parent_title ? 'block' : 'none');
@@ -2018,6 +2050,7 @@
         $('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%');
         $('#task-status')[(rec.status ? 'show' : 'hide')]().children('.task-text').text(status);
         $('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : ''));
+        $('#task-priority .task-text').text(priority);
         $('#task-attendees, #task-organizer, #task-created-changed, #task-created, #task-changed, #task-rsvp, #task-rsvp-comment').hide();
 
         $('#event-status-badge > span').text(status);
@@ -2036,9 +2069,7 @@
           $dialog.addClass('status-' + String(rec.status).toLowerCase());
         }
 
-        if (rec.flagged) {
-          $dialog.addClass('status-flagged');
-        }
+        $dialog.addClass('status-priority-' + get_priority_name(rec));
 
         if (rec.recurrence && rec.recurrence_text) {
             $('#task-recurrence').show().children('.task-text').html(Q(rec.recurrence_text));
@@ -2461,6 +2492,7 @@
         var complete = $('#taskedit-completeness').val((rec.complete || 0) * 100);
         completeness_slider.slider('value', complete.val());
         var taskstatus = $('#taskedit-status').val(rec.status || '');
+        var priority = $('#taskedit-priority').val(rec.priority || 0);
         var tasklist = $('#taskedit-tasklist').prop('disabled', rec.parent_id ? true : false);
         var notify = $('#edit-attendees-donotify').get(0);
         var invite = $('#edit-attendees-invite').get(0);
@@ -2581,7 +2613,7 @@
             data._status_before = me.selected_task.status + '';
 
             // copy form field contents into task object to save
-            $.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, status:taskstatus }, function(key,input){
+            $.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, status:taskstatus, priority:priority }, function(key,input){
                 data[key] = input.val();
             });
 
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 675a048527..59dd3c2606 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -38,7 +38,7 @@
     public const FILTER_MASK_LATER = 8;
     public const FILTER_MASK_NODATE = 16;
     public const FILTER_MASK_OVERDUE = 32;
-    public const FILTER_MASK_FLAGGED = 64;
+    public const FILTER_MASK_HIGHPRIORITY = 64;
     public const FILTER_MASK_COMPLETE = 128;
     public const FILTER_MASK_ASSIGNED = 256;
     public const FILTER_MASK_MYTASKS = 512;
@@ -52,7 +52,7 @@
         'later'    => self::FILTER_MASK_LATER,
         'nodate'   => self::FILTER_MASK_NODATE,
         'overdue'  => self::FILTER_MASK_OVERDUE,
-        'flagged'  => self::FILTER_MASK_FLAGGED,
+        'highpriority' => self::FILTER_MASK_HIGHPRIORITY,
         'complete' => self::FILTER_MASK_COMPLETE,
         'assigned' => self::FILTER_MASK_ASSIGNED,
         'mytasks'  => self::FILTER_MASK_MYTASKS,
@@ -680,8 +680,8 @@
                 $rec['complete'] /= 100;
             }
         }
-        if (isset($rec['flagged'])) {
-            $rec['flagged'] = intval($rec['flagged']);
+        if (isset($rec['priority'])) {
+            $rec['priority'] = intval($rec['priority']);
         }
 
         // fix for garbage input
@@ -1214,7 +1214,7 @@
     private function encode_task(&$rec)
     {
         $rec['mask']     = $this->filter_mask($rec);
-        $rec['flagged']  = intval($rec['flagged'] ?? 0);
+        $rec['priority'] = intval($rec['priority'] ?? 0);
         $rec['complete'] = floatval($rec['complete'] ?? 0);
 
         if (!empty($rec['created']) && is_object($rec['created'])) {
@@ -1365,8 +1365,8 @@
         $start   = !empty($rec['startdate']) ? $rec['startdate'] : '1900-00-00';
         $duedate = !empty($rec['date']) ? $rec['date'] : '3000-00-00';
 
-        if (!empty($rec['flagged'])) {
-            $mask |= self::FILTER_MASK_FLAGGED;
+        if (!empty($rec['priority']) && $rec['priority'] <= 4 && $rec['priority'] >= 1) {
+            $mask |= self::FILTER_MASK_HIGHPRIORITY;
         }
         if ($this->driver->is_complete($rec)) {
             $mask |= self::FILTER_MASK_COMPLETE;
@@ -2470,9 +2470,7 @@
             $object['status'] = 'COMPLETED';
         }
 
-        if ($task['flagged']) {
-            $object['priority'] = 1;
-        } elseif (empty($task['priority'])) {
+        if (empty($task['priority'])) {
             $object['priority'] = 0;
         }
 
@@ -2487,7 +2485,6 @@
         $task = $vtodo;
 
         $task['tags'] = array_filter((array)$vtodo['categories']);
-        $task['flagged'] = $vtodo['priority'] == 1;
         $task['complete'] = floatval($vtodo['complete'] / 100);
 
         // convert from DateTime to internal date format
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index b7b54fd911..2b1bfb70be 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -142,6 +142,7 @@
         $this->plugin->register_handler('plugin.tasklists', [$this, 'tasklists']);
         $this->plugin->register_handler('plugin.tasklist_select', [$this, 'tasklist_select']);
         $this->plugin->register_handler('plugin.status_select', [$this, 'status_select']);
+        $this->plugin->register_handler('plugin.priority_select', [$this, 'priority_select']);
         $this->plugin->register_handler('plugin.searchform', [$this->rc->output, 'search_form']);
         $this->plugin->register_handler('plugin.quickaddform', [$this, 'quickadd_form']);
         $this->plugin->register_handler('plugin.tasks', [$this, 'tasks_resultview']);
@@ -354,6 +355,21 @@
     }
 
     /**
+     * Render HTML form for task priority selector
+     */
+    public function priority_select($attrib = [])
+    {
+        $attrib['name'] = 'priority';
+        $select = new html_select($attrib);
+        $select->add($this->plugin->gettext('priority-high'), 4);
+        $select->add($this->plugin->gettext('priority-medium'), 5);
+        $select->add($this->plugin->gettext('priority-low'), 9);
+        $select->add($this->plugin->gettext('priority-none'), 0);
+
+        return $select->show(null);
+    }
+
+    /**
      * Render a HTML select box for list selection
      */
     public function tasklist_select($attrib = [])
