+ Can show a counter of new tasks as a favicon (GH-5, GH-25)

This commit is contained in:
maxpozdeev 2025-02-15 21:50:09 +03:00
parent b05f40f77c
commit c11b7f5b34
7 changed files with 127 additions and 11 deletions

View file

@ -79,6 +79,7 @@ var mytinytodo = window.mytinytodo = _mtt = {
markdown: true,
viewTaskOnClick: false,
newTaskCounter: false,
newTaskCounterIcon: false,
},
timers: {
@ -725,6 +726,10 @@ var mytinytodo = window.mytinytodo = _mtt = {
// Counter
if (this.options.newTaskCounter) {
this.addAction('listsLoaded', newTaskCounterStart);
this.addAction('listSelected', newTaskCounterOnListSelected)
if (this.options.newTaskCounterIcon) {
this.addAction('newTaskCounterUpdated', newTaskCounterUpdated);
}
}
this.doAction( 'init' );
@ -1182,6 +1187,7 @@ function loadTasks(opts)
});
curList.lastTime = json.time;
setNewTaskCounterForList(curList.id, 0);
_mtt.doAction("newTaskCounterUpdated", curList.id);
if(opts.beforeShow && opts.beforeShow.call) {
opts.beforeShow();
}
@ -1623,7 +1629,10 @@ function tabSelect(elementOrId)
if (id == -1) $('#mtt').addClass('show-all-tasks');
else $('#mtt').removeClass('show-all-tasks');
if (filter.search != '') liveSearchToggle(0, 1);
mytinytodo.doAction('listSelected', tabLists.get(id));
mytinytodo.doAction('listSelected', {
'list': curList,
'prevList':prevList
});
}
const newTitle = curList.name + ' - ' + _mtt.options.title;
const isFirstLoad = flag.firstLoad;
@ -2059,6 +2068,7 @@ function submitFullTask(form)
var item = json.list[0];
taskList[item.id] = item;
taskOrder.push(parseInt(item.id));
curList.lastTaskCreatedTime = item.dateInt;
$('#tasklist').append(_mtt.prepareTaskStr(item));
changeTaskOrder(item.id);
_mtt.pageBack();
@ -2471,8 +2481,9 @@ function cmenuOnListRenamed(list)
$('#cmenu_list\\:'+list.id).text(list.name);
};
function cmenuOnListSelected(list)
function cmenuOnListSelected(a)
{
const list = a.list;
$('#cmenulistscontainer li').removeClass('mtt-item-disabled');
$('#cmenu_list\\:'+list.id).addClass('mtt-item-disabled').removeClass('mtt-list-hidden');
};
@ -2490,8 +2501,9 @@ function cmenuOnListHidden(list)
};
function tabmenuOnListSelected(list)
function tabmenuOnListSelected(a)
{
const list = a.list;
if (list.published) {
$('#btnPublish').addClass('mtt-item-checked');
$('#btnRssFeed').removeClass('mtt-item-disabled');
@ -2629,8 +2641,9 @@ function slmenuOnListAdded(list)
$('#slmenucontainer ul').append('<li id="slmenu_list:'+list.id+'" class="list-id-'+list.id+'"><div class="menu-icon"></div><a href="'+ _mtt.urlForList(list)+ '">'+list.name+'</a></li>');
};
function slmenuOnListSelected(list)
function slmenuOnListSelected(a)
{
const list = a.list;
$('#slmenucontainer li').removeClass('mtt-item-checked');
$('#slmenucontainer li.list-id-'+list.id).addClass('mtt-item-checked').removeClass('mtt-list-hidden');
@ -2768,19 +2781,108 @@ function newTaskCounter()
setNewTaskCounterForList(list.id, counters[list.id]);
}
});
_mtt.doAction("newTaskCounterUpdated");
}
});
}
function setNewTaskCounterForList(listId, counter)
{
const list = tabLists.get(listId);
if (!list) return;
if (list.newTaskCounterOld) {
counter += list.newTaskCounterOld;
}
if (counter > 0) {
$('#list_' + listId).find('.counter').text(counter).removeClass('hidden');
list.newTaskCounter = counter;
} else {
$('#list_' + listId).find('.counter').text('').addClass('hidden');
list.newTaskCounter = 0;
}
}
function newTaskCounterOnListSelected(a)
{
if (a.prevList && a.prevList.newTaskCounter) {
a.prevList.newTaskCounterOld = a.prevList.newTaskCounter;
}
}
/**
* Set favicon with number of new tasks
*/
function newTaskCounterUpdated()
{
// Calc a total number of new tasks in visible tabs
let total = 0;
tabLists.getAll().forEach( (list) => {
if (list.newTaskCounter && (!list.hidden || list.id != -1)) {
total += list.newTaskCounter;
}
});
// Restore original icon
if (total <= 0) {
const o = document.querySelectorAll('link[rel="icon"]');
let oType, oHref;
for (let i = 0; i < o.length; i++) {
if (o[i].dataset.ohref) {
oHref = o[i].dataset.ohref;
oType = o[i].dataset.otype;
o[i].parentNode.removeChild(o[i]);
}
}
if (oHref) {
const n = document.createElement('link');
n.setAttribute('rel', 'icon');
n.setAttribute('href', oHref);
n.setAttribute('type', oType);
document.querySelector('head').appendChild(n);
}
return;
}
// Draw new icon
const c = document.createElement('canvas');
c.height = c.width = 64;
const ctx = c.getContext('2d');
//filled circle in center
ctx.lineWidth = 4;
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(c.width / 2, c.height / 2, c.width / 2 - 2, 0, 2 * Math.PI, false);
ctx.fill();
//number in center
ctx.font = '48px Helvetica';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText( total > 9 ? '9+' : total, 32, 32, 50);
// Save params of original icon
const o = document.querySelectorAll('link[rel="icon"]');
let oType, oHref;
for (let i = 0; i < o.length; i++) {
oHref = o[i].dataset.ohref;
oType = o[i].dataset.otype;
if (!oHref) {
oHref = o[i].getAttribute('href');
oType = o[i].getAttribute('type') || '';
}
o[i].parentNode.removeChild(o[i]);
}
// Set new icon
const n = document.createElement('link');
n.setAttribute('rel', 'icon');
n.setAttribute('href', c.toDataURL()); //"data:image/png;base64,......"
if (oHref) {
n.dataset.ohref = oHref;
n.dataset.otype = oType;
}
document.querySelector('head').appendChild(n);
}
/*
Errors and Info messages
*/

View file

@ -272,6 +272,7 @@ class TasksController extends ApiController {
$db = DBConnection::instance();
$a = [];
$time = time();
if ($sqlWhereList) {
$sqlWhere = implode(' OR ', $sqlWhereList);
@ -300,7 +301,8 @@ class TasksController extends ApiController {
'ok' => true,
'total' => count($b) + count($a),
'tasks' => $b,
'lists' => $a
'lists' => $a,
'time' => $time
];
}

View file

@ -91,7 +91,8 @@ class Config
'appearance' => array('default'=>'system', 'type'=>'s', 'options'=>array('system','light','dark')),
# New tasks counter
'newTaskCounter' => array('default' => 0, 'type'=>'i'),
'newTaskCounter' => array('default' => 0, 'type'=>'i'),
'newTaskCounterIcon' => array('default' => 0, 'type'=>'i'),
# Array of activated extensions
'extensions' => array('default'=>[], 'type'=>'a')

View file

@ -16,9 +16,9 @@
"advanced_add": "Advanced",
"searching": "Searching for",
"tasks": "Tasks",
"taskdate_inline_created": "created at %s",
"taskdate_inline_edited": "edited at %s",
"taskdate_inline_completed": "completed at %s",
"taskdate_inline_created": "created on %s",
"taskdate_inline_edited": "edited on %s",
"taskdate_inline_completed": "completed on %s",
"taskdate_inline_duedate": "Due %s",
"taskdate_created": "Created",
"taskdate_edited": "Last edited",
@ -191,6 +191,7 @@
"set_appearance_dark": "Dark",
"set_newtaskcounter_h": "New tasks counter",
"set_newtaskcounter": "Check for new tasks",
"set_newtaskcountericon": "Show counter in favicon",
"set_extensions": "Extensions",
"set_activate": "Activate",
"set_deactivate": "Deactivate",

View file

@ -191,6 +191,7 @@
"set_appearance_dark": "Тёмная",
"set_newtaskcounter_h": "Счетчик новых задач",
"set_newtaskcounter": "Проверять наличие новых задач",
"set_newtaskcountericon": "Показывать счетчик в favicon",
"set_extensions": "Дополнения",
"set_activate": "Активировать",
"set_deactivate": "Деактивировать",

View file

@ -96,8 +96,13 @@ function js_options()
"autotag" => Config::get('autotag') ? true : false,
"markdown" => Config::get('markup') == 'v1' ? false : true,
"newTaskCounter" => Config::get('newTaskCounter') ? true : false,
"newTaskCounterIcon" => Config::get('newTaskCounterIcon') ? true : false,
);
$json = json_encode($a, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
$flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE;
if (MTT_DEBUG) {
$flags |= JSON_PRETTY_PRINT;
}
$json = json_encode($a, $flags);
if ($json === false) {
error_log("MTT Error: Failed to encode array of options to JSON. Code: ". (int)json_last_error());
echo "{}";

View file

@ -56,6 +56,7 @@ if(isset($_POST['save']))
Config::set('exactduedate', (int)_post('exactduedate'));
Config::set('appearance', removeNewLines(trim(_post('appearance'))) );
Config::set('newTaskCounter', (int)_post('newTaskCounter'));
Config::set('newTaskCounterIcon', (int)_post('newTaskCounterIcon'));
Config::save();
$t['saved'] = 1;
jsonExit($t);
@ -387,7 +388,10 @@ header('Content-type:text/html; charset=utf-8');
<div class="tr">
<div class="th"><?php _e('set_newtaskcounter_h');?>:</div>
<div class="td"><label><input type="checkbox" name="newTaskCounter" value="1" <?php if(_c('newTaskCounter')) echo 'checked="checked"'; ?> /> <?php _e('set_newtaskcounter');?></label></div>
<div class="td">
<label><input type="checkbox" name="newTaskCounter" value="1" <?php if(_c('newTaskCounter')) echo 'checked="checked"'; ?> /> <?php _e('set_newtaskcounter');?></label> <br>
<label><input type="checkbox" name="newTaskCounterIcon" value="1" <?php if(_c('newTaskCounterIcon')) echo 'checked="checked"'; ?> /> <?php _e('set_newtaskcountericon');?></label>
</div>
</div>
<?php