diff --git a/src/api.php b/src/api.php
index 2d6c717..5eae879 100644
--- a/src/api.php
+++ b/src/api.php
@@ -45,6 +45,9 @@ $endpoints = array(
'/tasks/parseTitle' => [
'POST' => [ TasksController::class , 'postTitleParse' ],
],
+ '/tasks/newCounter' => [
+ 'POST' => [ TasksController::class , 'postNewCounter' ],
+ ],
'/tagCloud/(-?\d+)' => [
'GET' => [ TagsController::class , 'getCloud' ],
],
diff --git a/src/content/mytinytodo.js b/src/content/mytinytodo.js
index 6eaed67..e17e3c9 100644
--- a/src/content/mytinytodo.js
+++ b/src/content/mytinytodo.js
@@ -30,6 +30,7 @@ var tabLists = {
_length: 0,
_order: [],
_alltasks: {},
+ lastTime: 0,
clear: function(){
this._lists = {}; this._length = 0; this._order = [];
this._alltasks = { id:-1, showCompl:0, sort:3, name:_mtt.lang.get('alltasks') };
@@ -76,11 +77,13 @@ var mytinytodo = window.mytinytodo = _mtt = {
history: true,
markdown: true,
viewTaskOnClick: false,
+ newTaskCounter: true,
},
timers: {
previewtag: 0,
ajaxAnimation: 0,
+ newTaskCounter: 0,
},
lang: {
@@ -719,6 +722,11 @@ var mytinytodo = window.mytinytodo = _mtt = {
});
}
+ // Counter
+ if (this.options.newTaskCounter) {
+ this.addAction('listsLoaded', newTaskCounterStart);
+ }
+
this.doAction( 'init' );
return this;
@@ -804,8 +812,10 @@ var mytinytodo = window.mytinytodo = _mtt = {
if (list) {
openListId = list.id;
}
+ tabLists.lastTime = res.time;
res.list.forEach( (item) => {
+ item.lastTime = res.time;
if ( item.id == -1 ) {
tabLists._alltasks = item;
ti += prepareListHtml(item);
@@ -1070,7 +1080,7 @@ function renameCurList()
var item = json.list[0];
curList = item;
tabLists.replace(item);
- $('#lists ul>.mtt-tab-selected>a').attr('title', item.name).find('span').html(item.name); //FIXME: wft?
+ $('#list_'+curList.id).replaceWith(prepareListHtml(curList, true));
mytinytodo.doAction('listRenamed', item);
});
});
@@ -1164,6 +1174,8 @@ function loadTasks(opts)
taskOrder.push(parseInt(item.id));
changeTaskCnt(item, 1);
});
+ curList.lastTime = json.time;
+ $('#list_'+curList.id).find('.counter').text('').addClass('hidden');
if(opts.beforeShow && opts.beforeShow.call) {
opts.beforeShow();
}
@@ -1172,18 +1184,22 @@ function loadTasks(opts)
});
};
-function prepareListHtml(list)
+function prepareListHtml(list, isSelected)
{
- var opentag = '';
+ const selected = isSelected ? ' mtt-tab-selected' : '';
+ let opentag = '';
if (list.id == -1) {
- opentag = '
';
+ opentag = '';
}
else {
- opentag = '';
+ opentag = '';
}
return opentag +
- '' + list.name + '' +
- '
';
+ ''+
+ ''+
+ '' + list.name + '' +
+ '
'+
+ '';
}
function prepareTaskStr(item, noteExp)
@@ -2680,6 +2696,63 @@ function setLocalStorageItem(key, value)
}
}
+function newTaskCounterStart()
+{
+ clearInterval(_mtt.timers.newTaskCounter);
+ _mtt.timers.newTaskCounter = setInterval(newTaskCounter, 60*1000); //every 60 sec
+}
+
+function newTaskCounter()
+{
+ const params = {
+ list: curList.id,
+ later: curList.lastTime,
+ lists: [],
+ };
+ tabLists.getAll().forEach( (list) => {
+ if (list.hidden || list.id == -1 || list.id == curList.id) {
+ return;
+ }
+ params.lists.push({
+ listId: list.id,
+ later: list.lastTime
+ });
+ });
+
+ fetch(_mtt.apiUrl + 'tasks/newCounter', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'MTT-Token': _mtt.options.token,
+ },
+ body: JSON.stringify(params)
+ })
+ .then(response => response.json())
+ .then(json => {
+ if (json && json.total) {
+ if (Array.isArray(json.tasks)) {
+ let curCounter = 0;
+ json.tasks.forEach((id) => {
+ if (taskList[id]) {
+ console.log('skip existing id ', id);
+ }
+ else {
+ curCounter++;
+ }
+ });
+ if (curCounter > 0) {
+ $('#list_'+curList.id).find('.counter').text(curCounter).removeClass('hidden');
+ }
+ }
+ if (Array.isArray(json.lists)) {
+ json.lists.forEach((item) => {
+ $('#list_'+item.listId).find('.counter').text(item.counter).removeClass('hidden');
+ });
+ }
+ }
+ });
+}
+
/*
Errors and Info messages
*/
diff --git a/src/content/theme/style.css b/src/content/theme/style.css
index 9b599ae..400c75e 100644
--- a/src/content/theme/style.css
+++ b/src/content/theme/style.css
@@ -286,25 +286,30 @@ select.form-input {
border-bottom:none;
border-top-right-radius:8px;
transition:background-color 0.1s ease-in;
+ max-width:230px;
+ min-width:95px;
}
.mtt-tab a {
margin:0; text-decoration:none; white-space:nowrap;
color: var(--color-tab-text);
display:inline-block; outline:none;
- box-sizing:border-box; height:2.2rem; padding:1px 0.3rem 0 0.3rem; display:flex; align-items:center;
+ box-sizing:border-box;
+ height:2.2rem;
+ padding:1px 0.3rem 0 0.3rem;
+ display:flex;
+ align-items:center;
}
.mtt-tab a:active { outline:0; text-decoration:none; }
-.mtt-tab a>span {
+.mtt-tab a > .title {
display:inline-block;
text-align:center;
- min-width:75px;
- max-width:195px;
cursor:pointer;
padding:0;
overflow:hidden;
text-overflow: ellipsis;
margin-left:0.3rem;
margin-right:0.3rem;
+ flex-grow:1;
}
.mtt-tab .list-action { display:none; }
.mtt-tab .mtt-img-button:hover,
@@ -331,6 +336,19 @@ select.form-input {
background-color: var(--color-placeholder);
border-color: var(--color-placeholder-border);
}
+.mtt-tab .counter {
+ display: inline-block;
+ min-width: 1.2rem;
+ height: 1rem;
+ border-radius: 1rem;
+ font-size: 0.8rem;
+ text-align: center;
+ background-color: #686868; /* #de5141 */
+ color: white;
+}
+.mtt-tab .counter.hidden {
+ display: none;
+}
#tabs_buttons {
padding-left:2px; padding-right:2px;
diff --git a/src/includes/api/ListsController.php b/src/includes/api/ListsController.php
index 012737f..6440a7e 100644
--- a/src/includes/api/ListsController.php
+++ b/src/includes/api/ListsController.php
@@ -28,6 +28,7 @@ class ListsController extends ApiController {
$t['list'][] = $this->prepareAllTasksList(); // show alltasks lists only for authorized user
$t['total'] = 1;
}
+ $t['time'] = time();
$q = $db->dq("SELECT * FROM {$db->prefix}lists $sqlWhere ORDER BY ow ASC, id ASC");
while ($r = $q->fetchAssoc())
{
diff --git a/src/includes/api/TasksController.php b/src/includes/api/TasksController.php
index c285a22..32e0f1a 100644
--- a/src/includes/api/TasksController.php
+++ b/src/includes/api/TasksController.php
@@ -98,6 +98,7 @@ class TasksController extends ApiController {
$t = array();
$t['total'] = 0;
$t['list'] = array();
+ $t['time'] = time();
$groupConcat = '';
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
@@ -106,6 +107,7 @@ class TasksController extends ApiController {
else {
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
}
+
$q = $db->dq("
SELECT todo.*, todo.duedate IS NULL AS ddn, $groupConcat
FROM {$db->prefix}todolist AS todo
@@ -246,6 +248,65 @@ class TasksController extends ApiController {
$this->response->data = $t;
}
+
+ function postNewCounter()
+ {
+ checkReadAccess();
+ $lists = $this->req->jsonBody['lists'] ?? false;
+ if (!is_array($lists)) {
+ $this->response->data = [ 'total' => 0 ];
+ return;
+ }
+ $userLists = [];
+ if (!haveWriteAccess()) {
+ $userLists = $this->getUserListsSimple(true);
+ if ($userLists) {
+ $sqlWhereList = "AND list_id IN (". implode(',', $userLists). ")";
+ // remove lists without access granted
+ $lists = array_filter($lists, function($item) use ($userLists) {
+ return in_array( (string)($item['listId'] ?? ''), $userLists );
+ });
+ }
+ }
+ $sqlWhereList = [];
+ foreach ($lists as $item) {
+ if (!isset($item['later'])) continue;
+ $later = (int)$item['later'];
+ if ($later <= 0) continue;
+ $sqlWhereList[] = "(list_id = ". (int)$item['listId']. " AND d_created > $later)";
+ }
+ $sqlWhere = implode(' OR ', $sqlWhereList);
+
+
+ $db = DBConnection::instance();
+ $a = [];
+ $q = $db->dq("SELECT list_id, COUNT(id) c FROM {$db->prefix}todolist
+ WHERE $sqlWhere GROUP BY list_id");
+ while ($r = $q->fetchAssoc()) {
+ $a[] = [
+ 'listId' => (int)$r['list_id'],
+ 'counter' => (int)$r['c'],
+ ];
+ }
+
+ $b = [];
+ $list = (int) ($this->req->jsonBody['list'] ?? 0);
+ $later = (int) ($this->req->jsonBody['later'] ?? 0);
+ if ($list > 0 && $later > 0 && (!$userLists || in_array((string)$list, $userLists))) {
+ $q = $db->dq("SELECT id FROM {$db->prefix}todolist
+ WHERE list_id = $list AND d_created > $later");
+ while ($r = $q->fetchAssoc()) {
+ $b[] = (int)$r['id'];
+ }
+ }
+
+ $this->response->data = [
+ 'total' => count($b) + count($a),
+ 'tasks' => $b,
+ 'lists' => $a
+ ];
+ }
+
/* Private Functions */
private function newTaskInList(int $listId): ?array
@@ -488,13 +549,17 @@ class TasksController extends ApiController {
return $t;
}
- private function getUserListsSimple(): array
+ private function getUserListsSimple(bool $readOnly = false): array
{
$db = DBConnection::instance();
+ $sqlWhere = '';
+ if ($readOnly) {
+ $sqlWhere = "WHERE published=1";
+ }
$a = array();
- $q = $db->dq("SELECT id,name FROM {$db->prefix}lists ORDER BY id ASC");
+ $q = $db->dq("SELECT id,name FROM {$db->prefix}lists $sqlWhere ORDER BY id ASC");
while($r = $q->fetchRow()) {
- $a[$r[0]] = $r[1];
+ $a[ (string)$r[0] ] = (string)$r[1];
}
return $a;
}