mirror of
https://github.com/maxpozdeev/mytinytodo.git
synced 2026-03-11 08:55:27 +00:00
+ add badge to tabs with counter of newly created tasks (GH-5, GH-25)
This commit is contained in:
parent
f870bc3cf0
commit
42e265d802
5 changed files with 174 additions and 14 deletions
|
|
@ -45,6 +45,9 @@ $endpoints = array(
|
||||||
'/tasks/parseTitle' => [
|
'/tasks/parseTitle' => [
|
||||||
'POST' => [ TasksController::class , 'postTitleParse' ],
|
'POST' => [ TasksController::class , 'postTitleParse' ],
|
||||||
],
|
],
|
||||||
|
'/tasks/newCounter' => [
|
||||||
|
'POST' => [ TasksController::class , 'postNewCounter' ],
|
||||||
|
],
|
||||||
'/tagCloud/(-?\d+)' => [
|
'/tagCloud/(-?\d+)' => [
|
||||||
'GET' => [ TagsController::class , 'getCloud' ],
|
'GET' => [ TagsController::class , 'getCloud' ],
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ var tabLists = {
|
||||||
_length: 0,
|
_length: 0,
|
||||||
_order: [],
|
_order: [],
|
||||||
_alltasks: {},
|
_alltasks: {},
|
||||||
|
lastTime: 0,
|
||||||
clear: function(){
|
clear: function(){
|
||||||
this._lists = {}; this._length = 0; this._order = [];
|
this._lists = {}; this._length = 0; this._order = [];
|
||||||
this._alltasks = { id:-1, showCompl:0, sort:3, name:_mtt.lang.get('alltasks') };
|
this._alltasks = { id:-1, showCompl:0, sort:3, name:_mtt.lang.get('alltasks') };
|
||||||
|
|
@ -76,11 +77,13 @@ var mytinytodo = window.mytinytodo = _mtt = {
|
||||||
history: true,
|
history: true,
|
||||||
markdown: true,
|
markdown: true,
|
||||||
viewTaskOnClick: false,
|
viewTaskOnClick: false,
|
||||||
|
newTaskCounter: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
timers: {
|
timers: {
|
||||||
previewtag: 0,
|
previewtag: 0,
|
||||||
ajaxAnimation: 0,
|
ajaxAnimation: 0,
|
||||||
|
newTaskCounter: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
lang: {
|
lang: {
|
||||||
|
|
@ -719,6 +722,11 @@ var mytinytodo = window.mytinytodo = _mtt = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Counter
|
||||||
|
if (this.options.newTaskCounter) {
|
||||||
|
this.addAction('listsLoaded', newTaskCounterStart);
|
||||||
|
}
|
||||||
|
|
||||||
this.doAction( 'init' );
|
this.doAction( 'init' );
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -804,8 +812,10 @@ var mytinytodo = window.mytinytodo = _mtt = {
|
||||||
if (list) {
|
if (list) {
|
||||||
openListId = list.id;
|
openListId = list.id;
|
||||||
}
|
}
|
||||||
|
tabLists.lastTime = res.time;
|
||||||
|
|
||||||
res.list.forEach( (item) => {
|
res.list.forEach( (item) => {
|
||||||
|
item.lastTime = res.time;
|
||||||
if ( item.id == -1 ) {
|
if ( item.id == -1 ) {
|
||||||
tabLists._alltasks = item;
|
tabLists._alltasks = item;
|
||||||
ti += prepareListHtml(item);
|
ti += prepareListHtml(item);
|
||||||
|
|
@ -1070,7 +1080,7 @@ function renameCurList()
|
||||||
var item = json.list[0];
|
var item = json.list[0];
|
||||||
curList = item;
|
curList = item;
|
||||||
tabLists.replace(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);
|
mytinytodo.doAction('listRenamed', item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1164,6 +1174,8 @@ function loadTasks(opts)
|
||||||
taskOrder.push(parseInt(item.id));
|
taskOrder.push(parseInt(item.id));
|
||||||
changeTaskCnt(item, 1);
|
changeTaskCnt(item, 1);
|
||||||
});
|
});
|
||||||
|
curList.lastTime = json.time;
|
||||||
|
$('#list_'+curList.id).find('.counter').text('').addClass('hidden');
|
||||||
if(opts.beforeShow && opts.beforeShow.call) {
|
if(opts.beforeShow && opts.beforeShow.call) {
|
||||||
opts.beforeShow();
|
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) {
|
if (list.id == -1) {
|
||||||
opentag = '<li id="list_all" class="mtt-tab' + (list.hidden ? ' mtt-tab-hidden' : '') + '">';
|
opentag = '<li id="list_all" class="mtt-tab' + selected + (list.hidden ? ' mtt-tab-hidden' : '') + '">';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
opentag = '<li id="list_' + list.id + '" class="mtt-tab' + (list.hidden ? ' mtt-tab-hidden' : '') + '">';
|
opentag = '<li id="list_' + list.id + '" class="mtt-tab' + selected + (list.hidden ? ' mtt-tab-hidden' : '') + '">';
|
||||||
}
|
}
|
||||||
return opentag +
|
return opentag +
|
||||||
'<a href="' + _mtt.urlForList(list) + '" title="' + list.name + '"><span>' + list.name + '</span>' +
|
'<a href="' + _mtt.urlForList(list) + '" title="' + list.name + '">'+
|
||||||
'<div class="list-action mtt-img-button"><span></span></div></a></li>';
|
'<span class="counter hidden"></span>'+
|
||||||
|
'<span class="title">' + list.name + '</span>' +
|
||||||
|
'<div class="list-action mtt-img-button"><span></span></div>'+
|
||||||
|
'</a></li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareTaskStr(item, noteExp)
|
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
|
Errors and Info messages
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -286,25 +286,30 @@ select.form-input {
|
||||||
border-bottom:none;
|
border-bottom:none;
|
||||||
border-top-right-radius:8px;
|
border-top-right-radius:8px;
|
||||||
transition:background-color 0.1s ease-in;
|
transition:background-color 0.1s ease-in;
|
||||||
|
max-width:230px;
|
||||||
|
min-width:95px;
|
||||||
}
|
}
|
||||||
.mtt-tab a {
|
.mtt-tab a {
|
||||||
margin:0; text-decoration:none; white-space:nowrap;
|
margin:0; text-decoration:none; white-space:nowrap;
|
||||||
color: var(--color-tab-text);
|
color: var(--color-tab-text);
|
||||||
display:inline-block; outline:none;
|
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:active { outline:0; text-decoration:none; }
|
||||||
.mtt-tab a>span {
|
.mtt-tab a > .title {
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
min-width:75px;
|
|
||||||
max-width:195px;
|
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
padding:0;
|
padding:0;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin-left:0.3rem;
|
margin-left:0.3rem;
|
||||||
margin-right:0.3rem;
|
margin-right:0.3rem;
|
||||||
|
flex-grow:1;
|
||||||
}
|
}
|
||||||
.mtt-tab .list-action { display:none; }
|
.mtt-tab .list-action { display:none; }
|
||||||
.mtt-tab .mtt-img-button:hover,
|
.mtt-tab .mtt-img-button:hover,
|
||||||
|
|
@ -331,6 +336,19 @@ select.form-input {
|
||||||
background-color: var(--color-placeholder);
|
background-color: var(--color-placeholder);
|
||||||
border-color: var(--color-placeholder-border);
|
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 {
|
#tabs_buttons {
|
||||||
padding-left:2px; padding-right:2px;
|
padding-left:2px; padding-right:2px;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ class ListsController extends ApiController {
|
||||||
$t['list'][] = $this->prepareAllTasksList(); // show alltasks lists only for authorized user
|
$t['list'][] = $this->prepareAllTasksList(); // show alltasks lists only for authorized user
|
||||||
$t['total'] = 1;
|
$t['total'] = 1;
|
||||||
}
|
}
|
||||||
|
$t['time'] = time();
|
||||||
$q = $db->dq("SELECT * FROM {$db->prefix}lists $sqlWhere ORDER BY ow ASC, id ASC");
|
$q = $db->dq("SELECT * FROM {$db->prefix}lists $sqlWhere ORDER BY ow ASC, id ASC");
|
||||||
while ($r = $q->fetchAssoc())
|
while ($r = $q->fetchAssoc())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ class TasksController extends ApiController {
|
||||||
$t = array();
|
$t = array();
|
||||||
$t['total'] = 0;
|
$t['total'] = 0;
|
||||||
$t['list'] = array();
|
$t['list'] = array();
|
||||||
|
$t['time'] = time();
|
||||||
|
|
||||||
$groupConcat = '';
|
$groupConcat = '';
|
||||||
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
|
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
|
||||||
|
|
@ -106,6 +107,7 @@ class TasksController extends ApiController {
|
||||||
else {
|
else {
|
||||||
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
|
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
|
||||||
}
|
}
|
||||||
|
|
||||||
$q = $db->dq("
|
$q = $db->dq("
|
||||||
SELECT todo.*, todo.duedate IS NULL AS ddn, $groupConcat
|
SELECT todo.*, todo.duedate IS NULL AS ddn, $groupConcat
|
||||||
FROM {$db->prefix}todolist AS todo
|
FROM {$db->prefix}todolist AS todo
|
||||||
|
|
@ -246,6 +248,65 @@ class TasksController extends ApiController {
|
||||||
$this->response->data = $t;
|
$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 Functions */
|
||||||
|
|
||||||
private function newTaskInList(int $listId): ?array
|
private function newTaskInList(int $listId): ?array
|
||||||
|
|
@ -488,13 +549,17 @@ class TasksController extends ApiController {
|
||||||
return $t;
|
return $t;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUserListsSimple(): array
|
private function getUserListsSimple(bool $readOnly = false): array
|
||||||
{
|
{
|
||||||
$db = DBConnection::instance();
|
$db = DBConnection::instance();
|
||||||
|
$sqlWhere = '';
|
||||||
|
if ($readOnly) {
|
||||||
|
$sqlWhere = "WHERE published=1";
|
||||||
|
}
|
||||||
$a = array();
|
$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()) {
|
while($r = $q->fetchRow()) {
|
||||||
$a[$r[0]] = $r[1];
|
$a[ (string)$r[0] ] = (string)$r[1];
|
||||||
}
|
}
|
||||||
return $a;
|
return $a;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue