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' => [
|
||||
'POST' => [ TasksController::class , 'postTitleParse' ],
|
||||
],
|
||||
'/tasks/newCounter' => [
|
||||
'POST' => [ TasksController::class , 'postNewCounter' ],
|
||||
],
|
||||
'/tagCloud/(-?\d+)' => [
|
||||
'GET' => [ TagsController::class , 'getCloud' ],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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 = '<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 {
|
||||
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 +
|
||||
'<a href="' + _mtt.urlForList(list) + '" title="' + list.name + '"><span>' + list.name + '</span>' +
|
||||
'<div class="list-action mtt-img-button"><span></span></div></a></li>';
|
||||
'<a href="' + _mtt.urlForList(list) + '" title="' + list.name + '">'+
|
||||
'<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)
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue