mirror of
https://github.com/maxpozdeev/mytinytodo.git
synced 2026-03-11 08:55:27 +00:00
+ Can filter tasks by having any tag or not having tags (closes GH-64)
This commit is contained in:
parent
4548aa17de
commit
8082278af5
5 changed files with 65 additions and 25 deletions
|
|
@ -312,7 +312,8 @@ var mytinytodo = window.mytinytodo = _mtt = {
|
|||
$('#tagcloudcontent').on('click', '.tag', function(event){
|
||||
//tag is not escaped
|
||||
addFilterTag( this.dataset.tag, this.dataset.tagId, (event.metaKey || event.ctrlKey ? true : false) );
|
||||
if(_mtt.menus.tagcloud) _mtt.menus.tagcloud.close();
|
||||
if (_mtt.menus.tagcloud)
|
||||
_mtt.menus.tagcloud.close();
|
||||
return false;
|
||||
});
|
||||
|
||||
|
|
@ -948,25 +949,32 @@ var mytinytodo = window.mytinytodo = _mtt = {
|
|||
|
||||
filter: {
|
||||
_filters: [],
|
||||
clear: function() {
|
||||
|
||||
clear() {
|
||||
this._filters = [];
|
||||
$('#mtt-tag-toolbar').hide();
|
||||
$('#mtt-tag-filters').html('');
|
||||
},
|
||||
addTag: function(tagId, tag, exclude)
|
||||
|
||||
addTag(tagId, tag, exclude)
|
||||
{
|
||||
tagId += 0;
|
||||
for (let i in this._filters) {
|
||||
if (this._filters[i].tagId && this._filters[i].tagId == tagId)
|
||||
for (const filter of this._filters) {
|
||||
if (filter.tagId && filter.tagId == tagId)
|
||||
return false;
|
||||
}
|
||||
this._filters.push({tagId:tagId, tag:tag, exclude:exclude});
|
||||
if (tagId == -1) {
|
||||
// for display purposes only
|
||||
tag = exclude ? _mtt.lang.get('withAnyTag') : _mtt.lang.get('withoutTags');
|
||||
exclude = false;
|
||||
}
|
||||
const tagHtml = this.prepareTagHtml(tagId, tag, ['tag-filter', 'tag-id-'+tagId, exclude ? 'tag-filter-exclude' : '']) ;
|
||||
$('#mtt-tag-filters').append(tagHtml);
|
||||
$('#mtt-tag-toolbar').show();
|
||||
return true;
|
||||
},
|
||||
cancelTag: function(tagId)
|
||||
|
||||
cancelTag(tagId)
|
||||
{
|
||||
for (let i in this._filters) {
|
||||
if (this._filters[i].tagId && this._filters[i].tagId == tagId) {
|
||||
|
|
@ -980,23 +988,25 @@ var mytinytodo = window.mytinytodo = _mtt = {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
getTags: function(withExcluded)
|
||||
|
||||
getTags(withExcluded)
|
||||
{
|
||||
let a = [];
|
||||
for (let i in this._filters) {
|
||||
if (this._filters[i].tagId) {
|
||||
if (this._filters[i].exclude && withExcluded)
|
||||
a.push('^'+ this._filters[i].tag);
|
||||
else if (!this._filters[i].exclude)
|
||||
a.push(this._filters[i].tag)
|
||||
for (const filter of this._filters) {
|
||||
if (filter.tagId) {
|
||||
if (filter.exclude && withExcluded)
|
||||
a.push('^'+ filter.tag);
|
||||
else if (!filter.exclude)
|
||||
a.push(filter.tag)
|
||||
}
|
||||
}
|
||||
return a.join(', ');
|
||||
},
|
||||
prepareTagHtml: function(tagId, tag, classes)
|
||||
|
||||
prepareTagHtml(tagId, tag, classes)
|
||||
{
|
||||
// tag is not escaped
|
||||
return '<span class="' + classes.join(' ') + ' mtt-filter-close" tagid="' + tagId + '">' + escapeHtml(tag) + '<span class="tag-filter-btn"></span></span>';
|
||||
return `<span class="${classes.join(' ')} mtt-filter-close" tagid="${tagId}">${escapeHtml(tag)}<span class="tag-filter-btn"></span></span>`;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1988,11 +1998,14 @@ function loadTags(listId, callback)
|
|||
let cloud = '';
|
||||
tagsList.forEach( item => {
|
||||
// item.tag is escaped with htmlspecialchars()
|
||||
cloud += ' <span class="tag" data-tag="' + item.tag + '" data-tag-id="' + item.id + '">' + item.tag + '</span>';
|
||||
cloud += ` <span class="tag" data-tag="${item.tag}" data-tag-id="${item.id}">${item.tag}</span>`;
|
||||
});
|
||||
if (cloud == '') {
|
||||
cloud = _mtt.lang.get('noTags');
|
||||
}
|
||||
else {
|
||||
cloud = `<span class="tag" data-tag="^" data-tag-id="-1">${_mtt.lang.get('withoutTags')}</span>` + cloud;
|
||||
}
|
||||
$('#tagcloudcontent').html(cloud)
|
||||
flag.tagsChanged = false;
|
||||
callback();
|
||||
|
|
@ -2008,7 +2021,8 @@ function cancelTagFilter(tagId, dontLoadTasks)
|
|||
|
||||
function addFilterTag(tag, tagId, exclude)
|
||||
{
|
||||
if(!_mtt.filter.addTag(tagId, tag, exclude)) return false;
|
||||
if (!_mtt.filter.addTag(tagId, tag, exclude))
|
||||
return false;
|
||||
loadTasks();
|
||||
};
|
||||
|
||||
|
|
@ -2621,7 +2635,7 @@ function slmenuOnListsLoaded()
|
|||
let s = '';
|
||||
tabLists.getAll().forEach( (list) => {
|
||||
const classChecked = (list.id == curList.id) ? 'mtt-item-checked' : '';
|
||||
const classHidden = list.hidden ? 'mtt-list-hidden' : '';
|
||||
const classHidden = list.hidden ? 'mtt-list-hidden' : '';
|
||||
s += `<li id="slmenu_list:${list.id}" class="list-id-${list.id} ${classChecked} ${classHidden}">
|
||||
<div class="menu-icon"></div><a href="${_mtt.urlForList(list)}">${list.name}</a><div class="counter hidden"></div></li>`;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class TasksController extends ApiController {
|
|||
$db = DBConnection::instance();
|
||||
$dbcore = DBCore::default();
|
||||
|
||||
$sqlWhere = $sqlWhereListId = '';
|
||||
$sqlWhere = $sqlWhereListId = $sqlHaving = '';
|
||||
$userLists = [];
|
||||
if ($listId == -1) {
|
||||
$userLists = $this->getUserListsSimple();
|
||||
|
|
@ -46,8 +46,26 @@ class TasksController extends ApiController {
|
|||
$tagExIds = array();
|
||||
foreach ($at as $atv) {
|
||||
$atv = trim($atv);
|
||||
if ($atv == '' || $atv == '^') continue;
|
||||
if (substr($atv,0,1) == '^') {
|
||||
if ($atv == '')
|
||||
continue;
|
||||
// tasks without tags (ignore other tags included or excluded)
|
||||
if ($atv == '^') {
|
||||
$tagIds = [];
|
||||
$tagExIds = [];
|
||||
if ($db::DBTYPE == DBConnection::DBTYPE_MYSQL)
|
||||
$sqlHaving = "tags_ids = ''";
|
||||
else
|
||||
$sqlHaving = "string_agg(tags.name, ',') IS NULL"; // catches if tag name is ''
|
||||
break;
|
||||
}
|
||||
// tasks with any tag
|
||||
else if ($atv == '^^') {
|
||||
if ($db::DBTYPE == DBConnection::DBTYPE_MYSQL)
|
||||
$sqlHaving = "tags_ids != ''";
|
||||
else
|
||||
$sqlHaving = "string_agg(tags.name, ',') != ''";
|
||||
}
|
||||
else if (substr($atv,0,1) == '^') {
|
||||
array_push($tagExIds, ...$dbcore->getTagIdsByName(substr($atv,1)));
|
||||
} else {
|
||||
$tagIds[] = $dbcore->getTagIdsByName($atv);
|
||||
|
|
@ -110,6 +128,8 @@ class TasksController extends ApiController {
|
|||
else {
|
||||
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
|
||||
}
|
||||
if ($sqlHaving != '')
|
||||
$sqlHaving = "HAVING $sqlHaving";
|
||||
|
||||
$q = $db->dq("
|
||||
SELECT todo.*, todo.duedate IS NULL AS ddn, $groupConcat
|
||||
|
|
@ -117,7 +137,7 @@ class TasksController extends ApiController {
|
|||
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
|
||||
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
|
||||
WHERE $sqlWhereListId $sqlWhere
|
||||
GROUP BY todo.id
|
||||
GROUP BY todo.id $sqlHaving
|
||||
$sqlSort
|
||||
");
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,8 @@ class Lang
|
|||
'listNotFound',
|
||||
'noPublicLists',
|
||||
'noTags',
|
||||
'withoutTags',
|
||||
'withAnyTag',
|
||||
'invalidpass',
|
||||
'addList',
|
||||
'addListDefault',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_header": {
|
||||
"ver": "v1.8.2",
|
||||
"date": "2025-02-15",
|
||||
"date": "2025-02-16",
|
||||
"language": "English",
|
||||
"original_name": "English",
|
||||
"authors": [
|
||||
|
|
@ -205,6 +205,8 @@
|
|||
"listNotFound": "List not found",
|
||||
"noPublicLists": "No public tasks",
|
||||
"noTags": "No tags",
|
||||
"withoutTags": "No tags",
|
||||
"withAnyTag": "Any tag",
|
||||
"invalidpass": "Wrong password",
|
||||
"addList": "Create new list",
|
||||
"addListDefault": "Todo",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_header": {
|
||||
"ver": "v1.8.2",
|
||||
"date": "2025-02-15",
|
||||
"date": "2025-02-16",
|
||||
"language": "Russian",
|
||||
"original_name": "Русский",
|
||||
"authors": [
|
||||
|
|
@ -205,6 +205,8 @@
|
|||
"listNotFound": "Список не найден",
|
||||
"noPublicLists": "Нет опубликованных списков",
|
||||
"noTags": "Нет тегов",
|
||||
"withoutTags": "Без тегов",
|
||||
"withAnyTag": "Любой тег",
|
||||
"invalidpass": "Неверный пароль",
|
||||
"addList": "Новый список",
|
||||
"addListDefault": "Todo",
|
||||
|
|
|
|||
Loading…
Reference in a new issue