diff --git a/src/content/mytinytodo.js b/src/content/mytinytodo.js
index 3fe78f4..b21ad0c 100644
--- a/src/content/mytinytodo.js
+++ b/src/content/mytinytodo.js
@@ -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('
');
};
-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
*/
diff --git a/src/includes/api/TasksController.php b/src/includes/api/TasksController.php
index 2f0d6cb..0a5bc14 100644
--- a/src/includes/api/TasksController.php
+++ b/src/includes/api/TasksController.php
@@ -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
];
}
diff --git a/src/includes/class.config.php b/src/includes/class.config.php
index 9e1d11d..2446656 100644
--- a/src/includes/class.config.php
+++ b/src/includes/class.config.php
@@ -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')
diff --git a/src/includes/lang/en.json b/src/includes/lang/en.json
index d481a1e..04e11e9 100644
--- a/src/includes/lang/en.json
+++ b/src/includes/lang/en.json
@@ -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",
diff --git a/src/includes/lang/ru.json b/src/includes/lang/ru.json
index fa123c7..d3d95b5 100644
--- a/src/includes/lang/ru.json
+++ b/src/includes/lang/ru.json
@@ -191,6 +191,7 @@
"set_appearance_dark": "Тёмная",
"set_newtaskcounter_h": "Счетчик новых задач",
"set_newtaskcounter": "Проверять наличие новых задач",
+ "set_newtaskcountericon": "Показывать счетчик в favicon",
"set_extensions": "Дополнения",
"set_activate": "Активировать",
"set_deactivate": "Деактивировать",
diff --git a/src/index.php b/src/index.php
index 8675753..40b0205 100644
--- a/src/index.php
+++ b/src/index.php
@@ -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 "{}";
diff --git a/src/settings.php b/src/settings.php
index 6c34fbd..b6b1941 100644
--- a/src/settings.php
+++ b/src/settings.php
@@ -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');