Full Code of maxpozdeev/mytinytodo for AI

master 1b00932aa16b cached
140 files
748.4 KB
219.2k tokens
663 symbols
1 requests
Download .txt
Showing preview only (815K chars total). Download the full file or copy to clipboard to get everything.
Repository: maxpozdeev/mytinytodo
Branch: master
Commit: 1b00932aa16b
Files: 140
Total size: 748.4 KB

Directory structure:
gitextract_6m_aa8ts/

├── .editorconfig
├── .gitignore
├── README.md
├── buildtar.php
├── composer.json
├── composer.sh
└── src/
    ├── .htaccess
    ├── COPYRIGHT
    ├── LICENSE
    ├── api.php
    ├── config-sample.php
    ├── content/
    │   ├── index.html
    │   ├── js/
    │   │   ├── index.html
    │   │   └── jquery.ui.touch-punch.js
    │   ├── mytinytodo.js
    │   ├── mytinytodo_api.js
    │   └── theme/
    │       ├── dark.css
    │       ├── images/
    │       │   ├── COPYRIGHT
    │       │   ├── index.html
    │       │   └── svg2base64.php
    │       ├── index.html
    │       ├── markdown.css
    │       ├── print.css
    │       ├── style.css
    │       └── style_rtl.css
    ├── db/
    │   └── .htaccess
    ├── docker-config.php
    ├── export.php
    ├── ext/
    │   ├── .htaccess
    │   ├── CustomCSS/
    │   │   ├── .htaccess
    │   │   ├── extension.json
    │   │   ├── lang/
    │   │   │   ├── en.json
    │   │   │   ├── pl.json
    │   │   │   └── ru.json
    │   │   └── loader.php
    │   ├── _examples/
    │   │   └── CustomSmartSyntax/
    │   │       ├── .htaccess
    │   │       ├── extension.json
    │   │       └── loader.php
    │   ├── backup/
    │   │   ├── .htaccess
    │   │   ├── class.backup.php
    │   │   ├── class.check.php
    │   │   ├── class.controller.php
    │   │   ├── class.download.php
    │   │   ├── class.restore.php
    │   │   ├── extension.json
    │   │   ├── lang/
    │   │   │   ├── en.json
    │   │   │   └── ru.json
    │   │   └── loader.php
    │   ├── index.html
    │   ├── notifications/
    │   │   ├── .htaccess
    │   │   ├── class.controller.php
    │   │   ├── class.observer.php
    │   │   ├── class.sender.php
    │   │   ├── class.telegramapi.php
    │   │   ├── cli-notify.php
    │   │   ├── extension.json
    │   │   ├── lang/
    │   │   │   ├── de.json
    │   │   │   ├── en.json
    │   │   │   └── ru.json
    │   │   └── loader.php
    │   └── updater/
    │       ├── .htaccess
    │       ├── class.controller.php
    │       ├── class.updater.php
    │       ├── extension.json
    │       ├── lang/
    │       │   ├── de.json
    │       │   ├── en.json
    │       │   └── ru.json
    │       └── loader.php
    ├── feed.php
    ├── includes/
    │   ├── .htaccess
    │   ├── api/
    │   │   ├── AuthController.php
    │   │   ├── ExtSettingsController.php
    │   │   ├── ListsController.php
    │   │   ├── TagsController.php
    │   │   └── TasksController.php
    │   ├── class.config.php
    │   ├── class.db.mysql.php
    │   ├── class.db.mysqli.php
    │   ├── class.db.postgres.php
    │   ├── class.db.sqlite3.php
    │   ├── class.dbconnection.php
    │   ├── class.dbcore.php
    │   ├── class.lang.php
    │   ├── class.sessionhandler.php
    │   ├── classes.php
    │   ├── common.php
    │   ├── filters.php
    │   ├── index.html
    │   ├── lang/
    │   │   ├── _percent.php
    │   │   ├── ar.json
    │   │   ├── bg.json
    │   │   ├── ca.json
    │   │   ├── cz.json
    │   │   ├── da.json
    │   │   ├── de.json
    │   │   ├── el.json
    │   │   ├── en-rtl.json
    │   │   ├── en.json
    │   │   ├── es-mx.json
    │   │   ├── es.json
    │   │   ├── et.json
    │   │   ├── fa.json
    │   │   ├── fr.json
    │   │   ├── he.json
    │   │   ├── hu.json
    │   │   ├── it.json
    │   │   ├── ja.json
    │   │   ├── lt.json
    │   │   ├── mk.json
    │   │   ├── nl.json
    │   │   ├── no.json
    │   │   ├── pl.json
    │   │   ├── pt-br.json
    │   │   ├── pt-pt.json
    │   │   ├── readme.md
    │   │   ├── ro.json
    │   │   ├── ru.json
    │   │   ├── sk.json
    │   │   ├── sl.json
    │   │   ├── sr.json
    │   │   ├── sv.json
    │   │   ├── th.json
    │   │   ├── tr.json
    │   │   ├── uk.json
    │   │   ├── vi.json
    │   │   ├── zh-cn.json
    │   │   └── zh-tw.json
    │   ├── markup.commonmark.php
    │   ├── markup.parsedown.php
    │   ├── markup.php
    │   ├── notifications.php
    │   ├── smartsyntax.php
    │   ├── theme.php
    │   └── version.php
    ├── index.php
    ├── init.php
    ├── mtt-edit-settings.php
    ├── mtt-emergency.php
    ├── settings.php
    └── setup.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true

# 'insert_final_newline = false' should not remove last empty line

[*]
charset = utf-8
end_of_line = lf
# tab_width here is only used to represent tabs if indent_size is not set
tab_width = 4
trim_trailing_whitespace = true
insert_final_newline = true

[*.php]
indent_style = space
indent_size = 4

[*.js]
indent_style = space
tab_width = 4

[*.{css,yml,html,svg,xml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false
insert_final_newline = false

[/src/includes/theme.php]
indent_style = space
indent_size = 2


================================================
FILE: .gitignore
================================================
.DS_Store
src/db/todolist.db*
src/db/config.php
src/db/config-*
src/db/backup.xml*
src/config.php
src/includes/vendor/
src/content/theme/custom.css

tests/


================================================
FILE: README.md
================================================
# myTinyTodo

Your tiny todo list

Original website - http://www.mytinytodo.net/

### System requirements
- PHP 7.2 or greater
- PHP extensions:
  - mbstring
  - pdo_sqlite, intl (SQLite version)
  - pdo_mysql or mysqli (MySQL version)
  - pdo_pgsql (PostgreSQL version)
- One of databases:
  - MySQL 5.7 or greater / MariaDB 10.2 or greater
  - PostgreSQL 10 or greater
  - SQLite (system library)

Supported browsers: Chrome 49, Safari 10, Firefox 53.

Internet Explorer and Opera with Presto engine are not supported.



================================================
FILE: buildtar.php
================================================
#!/usr/bin/env php
<?php

// PHP 5.4 is required

if ( !isset($argv) || !isset($argv[1]) ) {
    die("Usage: buildtar.php <path_to_repo> [-o source.tar.gz] [-v VERSION]\n");
}

$repo = $argv[1];
$dir = sys_get_temp_dir(). DIRECTORY_SEPARATOR. "mytinytodo.build";
$curdir = getcwd();
$archive = $curdir. DIRECTORY_SEPARATOR. 'mytinytodo-v@VERSION-@REV.tar.gz';
$ver = 0;

while ($arg = next($argv))
{
    if ($arg == '-o') {
        $archive = next($argv);
    }
    elseif ($arg == '-v') {
        $ver = next($argv);
    }
}

deleteTreeIfDir($dir);
$out = `git clone $repo $dir 2>&1`;
if (!is_dir($dir)) {
    die("Error while clone: $out\n");
}
print "> Repository was cloned to temp dir: $dir\n";

#get current version number if not specified
if (!$ver) {
    require_once(__DIR__ . '/src/includes/version.php');
    $ver = mytinytodo\Version::VERSION;
}
chdir($dir. DIRECTORY_SEPARATOR. 'src');
$rev = trim(`git show --format=format:%H --summary`);
$rev = substr($rev, 0, 8);
##$ver = str_replace('@REV', $rev, $ver);
print "> Version is $ver\n";

unlink('./docker-config.php');
unlink('./includes/lang/en-rtl.json');
unlink('./includes/lang/_percent.php');
unlink('./mtt-edit-settings.php');
unlink('./mtt-emergency.php');
unlink('./content/theme/images/svg2base64.php');

chdir('..'); # to the root of repo

assert( strpos(getcwd(), ':') === false ); # FIXME: if path contains a colon ':'
echo("> Run Composer\n");
$retval = 0;
if (false === system( "./composer.sh install --no-dev --no-interaction --optimize-autoloader", $retval) || $retval != 0) {
    die("Failed to install composer libs via docker\n");
}

# ext
if (is_dir('src/ext')) {
    mkdir('src/ext2');
    chdir('src/ext');
    deleteTreeIfDir('_examples');
    $extCount = 0;
    $exts = array_diff(scandir('.') ?? [], ['.', '..']);
    foreach ($exts as $ext) {
        if (is_dir($ext)) {
            rename($ext, "../ext2/$ext");
            $extCount++;
        }
    }
    chdir('../ext2');
    if ($extCount > 0) {
        `tar --no-xattrs -czf ../ext/extensions.tar.gz *`;  #OS dep.!!!
    }
    chdir('../..');
    deleteTreeIfDir('src/ext2');
    echo("> Extensions were packed\n");
}


rename('src', 'mytinytodo') or die("Cant rename 'src'\n");

`tar --no-xattrs -czf mytinytodo.tar.gz mytinytodo`;  #OS dep.!!!
if (!file_exists('mytinytodo.tar.gz')) {
    die("Failed to pack files (no output tar.gz file)\n");
}

$archive = str_replace('@VERSION', $ver, $archive);
$archive = str_replace('@REV', $rev, $archive);

chdir($curdir);
if ( ! rename("$dir/mytinytodo.tar.gz", $archive) ) {
    die("Failed to move mytinytodo.tar.gz to $archive");
}

deleteTreeIfDir($dir);
echo("> Temp dir was cleaned\n");

echo("> Build is stored in $archive\n");






function deleteTreeIfDir($dir)
{
    if ( !is_dir($dir) ) {
        return;
    }
    switch (PHP_OS) {
        case 'Darwin':
        case 'Linux':
            system("rm -rf ". escapeshellarg($dir));
            break;
        case 'Windows':
            system("rmdir /s /q ". escapeshellarg($dir));
            break;
        default:
            die("Unknown system ". PHP_OS. "\n");
    }
}


================================================
FILE: composer.json
================================================
{
    "name": "maxpozdeev/mytinytodo",
    "type": "project",
    "license": "GPL-2.0-or-later",
    "homepage": "https://mytinytodo.net",
    "authors": [
        {
            "name": "Max Pozdeev",
            "role": "Developer"
        }
    ],
    "config": {
        "vendor-dir": "src/includes/vendor"
    },
    "require": {
        "php": ">=7.2",
        "ext-mbstring": "*",
        "erusev/parsedown": "1.7.x-dev#f7285e7",
        "symfony/polyfill-intl-normalizer": "^1.31"
    },
    "require-dev": {
        "league/commonmark": "^2.6"
    }
}


================================================
FILE: composer.sh
================================================
#!/bin/sh

#dir="$( dirname -- "$( readlink -f -- "$0"; )"; )"
dir="$PWD"

app=$(which podman)
if [ -z $app ]; then
  app="docker"
fi

$app run -it --rm -v "$dir:/app" composer $@


================================================
FILE: src/.htaccess
================================================
# For REST API in Apache
#<IfModule mod_rewrite.c>
#    RewriteEngine On
#    RewriteCond %{REQUEST_FILENAME} !-f
#    RewriteCond %{REQUEST_FILENAME} !-d
#    RewriteRule ^api/(.*)$ api.php/$1 [L,QSA]
#</IfModule>
#<Limit GET POST PUT DELETE>
#  Allow from all
#</Limit>


# In Nginx set something like this:

# Deny access to some files and folders
#location ~ ^/(db|includes)/ {
#    deny all;
#}
#location ~ /\.ht {
#    deny all;
#}
#location ~* ^/ext/.*\.(json|md)$ {
#    deny all;
#}

# Optional
# location /api/ {
#    rewrite ^/api/(.*) /api.php/$1 last;
# }


================================================
FILE: src/COPYRIGHT
================================================
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with this program as the file LICENSE; if not, please see
https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.


myTinyTodo
--------------
Url:        https://www.mytinytodo.net/
Copyright:  2009-2011,2019-2025 Max Pozdeev <maxpozdeev@gmail.com>
License:    GPL version 2 or any later (see LICENSE file)


myTinyTodo uses other works:

jQuery
--------------
Url:        http://jquery.com/
Copyright:  (c) JS Foundation and other contributors | https://jquery.org/license/
License:    MIT license. Compatible with GNU GPL (see https://blog.jquery.com/2012/09/10/jquery-licensing-changes/)

jQuery UI
--------------
Url:        http://jqueryui.com/
Copyright:  Copyright jQuery Foundation and other contributors
License:    MIT license. Compatible with GNU GPL.

jQuery UI Touch Punch (fork by RWAP Software)
---------------------------------------------
Url:        https://github.com/RWAP/jquery-ui-touch-punch
based on original touchpunch
Original:   https://github.com/furf/jquery-ui-touch-punch
Copyright:  Copyright 2011–2014, Dave Furfero
License:    Dual licensed under the MIT or GPL Version 2 licenses.

Parsedown
--------------
Url:        https://parsedown.org/
Copyright:  (c) 2013-2018 Emanuil Rusev, erusev.com
License:    MIT license. Compatible with GNU GPL.

Other libraries (in includes/vendor)
----------------------------------------------
symfony/polyfill-intl-normalizer
league/commonmark

Images
--------------
This software contains images by 3d-parties.
See file content/theme/images/COPYRIGHT for details.


================================================
FILE: src/LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


================================================
FILE: src/api.php
================================================
<?php declare(strict_types=1);

/*
    This file is a part of myTinyTodo.
    (C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>
    Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/

require_once('./init.php');

if (MTT_DEBUG) {
    set_error_handler('myErrorHandler'); //catch Notices, Warnings
    set_exception_handler('myExceptionHandler');
}
else {
    ini_set('display_errors', '0');
}

require_once(MTTINC. 'api/ListsController.php');
require_once(MTTINC. 'api/TasksController.php');
require_once(MTTINC. 'api/TagsController.php');
require_once(MTTINC. 'api/AuthController.php');
require_once(MTTINC. 'api/ExtSettingsController.php');

$endpoints = array(
    '/lists' => [
        'GET'  => [ ListsController::class , 'get' ],
        'POST' => [ ListsController::class , 'post' ],
        'PUT'  => [ ListsController::class , 'put' ],
    ],
    '/lists/(-?\d+)' => [
        'GET'     => [ ListsController::class , 'getId' ],
        'PUT'     => [ ListsController::class , 'putId' ],
        'DELETE'  => [ ListsController::class , 'deleteId' ],
        'POST'    => [ ListsController::class , 'putId' ], //compatibility
    ],
    '/tasks' => [
        'GET'  => [ TasksController::class , 'get' ],
        'POST' => [ TasksController::class , 'post' ],
        'PUT'  => [ TasksController::class , 'put' ],
    ],
    '/tasks/(-?\d+)' => [
        'PUT'     => [ TasksController::class , 'putId' ],
        'DELETE'  => [ TasksController::class , 'deleteId' ],
        'POST'    => [ TasksController::class , 'putId' ], //compatibility
    ],
    '/tasks/parseTitle' => [
        'POST' => [ TasksController::class , 'postTitleParse' ],
    ],
    '/tasks/newCounter' => [
        'POST' => [ TasksController::class , 'postNewCounter' ],
    ],
    '/tagCloud/(-?\d+)' => [
        'GET'  => [ TagsController::class , 'getCloud' ],
    ],
    '/suggestTags' => [
        'GET'  => [ TagsController::class , 'getSuggestions' ],
    ],
    '/(login|logout|session)' => [
        'POST' => [ AuthController::class , 'postAction' ],
    ],
    '/ext-settings/(.+)' => [
        'GET'     => [ ExtSettingsController::class , 'get' ],
        'PUT'     => [ ExtSettingsController::class , 'put' ],
        'POST'    => [ ExtSettingsController::class , 'put' ], //compatibility
    ]
);

// look for extensions
foreach (MTTExtensionLoader::loadedExtensions() as $instance) {
    if ($instance instanceof MTTHttpApiExtender) {
        $newRoutes = $instance->extendHttpApi();
        foreach ($newRoutes as $endpoint => $methods) {
            $endpoint = '/ext/'. $instance::bundleId. $endpoint;
            foreach ($methods as $k => &$v) {
                $v[3] = true; // Mark extension method
            }
            $endpoints[$endpoint] = $methods;
        }
    }
}

$req = new ApiRequest();
$response = new ApiResponse();
$executed = false;
$data = null;

foreach ($endpoints as $search => $methods) {
    $m = array();
    if (preg_match("#^$search$#", $req->path, $m)) {
        $classDescr = $methods[$req->method] ?? null;
        // check if http method is supported for path
        if ( is_null($classDescr) ) {
            $response->htmlContent("Unknown method for resource", 500)
                ->exit();
        }
        if ( !is_array($classDescr) || count($classDescr) < 2) {
            $response->htmlContent("Incorrect method definition", 500)
                ->exit();
        }
        // check if class method exists
        $class = $classDescr[0];
        $classMethod = $classDescr[1];
        $isExtMethod = $classDescr[3] ?? false;
        if ($isExtMethod) {
            if (false == ($classDescr[2] ?? false)) { //TODO: describe $classDescr[2]
                // By default all extension methods require write access rights
                checkWriteAccess();
            }
        }
        $param = null;
        if (count($m) >= 2) {
            $param = $m[1];
        }
        if (method_exists($class, $classMethod)) { // test for static with ReflectionMethod?
            if ($req->method != 'GET' && $req->contentType == 'application/json') {
                if ($req->decodeJsonBody() === false) {
                    $response->htmlContent("Failed to parse JSON body", 500)
                        ->exit();
                }
            }
            $instance = new $class($req, $response);
            $instance->$classMethod($param);
            $executed = true;
            break;
        }
        else {
            if (MTT_DEBUG) {
                $response->htmlContent("Class method $class:$classMethod() not found", 405)
                    ->exit();
            }
            $response->htmlContent("Class method not found", 405)
                ->exit();
        }
    }
}

if (!$executed) {
    if (MTT_DEBUG) {
        $response->htmlContent("Unknown endpoint: {$req->method} {$req->path}", 404)
            ->exit();
    }
    $response->htmlContent("Unknown endpoint", 404);
}
$response->exit();



function myErrorHandler($errno, $errstr, $errfile, $errline)
{
    if ($errno==E_ERROR || $errno==E_CORE_ERROR || $errno==E_COMPILE_ERROR || $errno==E_USER_ERROR || $errno==E_PARSE) {
        $error = 'Error';
    }
    elseif ($errno==E_WARNING || $errno==E_CORE_WARNING || $errno==E_COMPILE_WARNING || $errno==E_USER_WARNING) {
        if (error_reporting() & $errno) $error = 'Warning'; else return;
    }
    elseif ($errno==E_NOTICE || $errno==E_USER_NOTICE || $errno==E_DEPRECATED || $errno==E_USER_DEPRECATED) {
        if (error_reporting() & $errno) $error = 'Notice'; else return;
    }
    else $error = "Error ($errno)"; // here may be E_RECOVERABLE_ERROR
    throw new Exception("$error: '$errstr' in $errfile:$errline", -1);
}

function myExceptionHandler(Throwable $e)
{
    // to avoid Exception thrown without a stack frame
    try
    {
        if (-1 == $e->getCode()) {
            //thrown in myErrorHandler
            http_response_code(500);
            logAndDie( $e->getMessage() );
        }

        $c = get_class($e);
        $errText = "Exception ($c): '". $e->getMessage(). "' in ". $e->getFile(). ":". $e->getLine() ;

        if (MTT_DEBUG) {
            if ( count($e->getTrace()) > 0 ) {
                $errText .= "\n". $e->getTraceAsString() ;
            }
        }
        http_response_code(500);
        logAndDie($errText);
    }
    catch (Exception $e) {
        http_response_code(500);
        logAndDie('Exception in ExceptionHandler: \''. $e->getMessage() .'\' in '. $e->getFile() .':'. $e->getLine());
    }
    exit;
}

function checkReadAccess(?int $listId = null)
{
    check_token();
    $db = DBConnection::instance();
    if (is_logged()) return true;
    if ($listId !== null)
    {
        $id = $db->sq("SELECT id FROM {$db->prefix}lists WHERE id=? AND published=1", array($listId));
        if ($id) return;
    }
    http_response_code(403);
    jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) );
}

function checkWriteAccess(?int $listId = null)
{
    check_token();
    if (haveWriteAccess($listId)) return;
    http_response_code(403);
    jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) );
}

function haveWriteAccess(?int $listId = null) : bool
{
    if (is_readonly()) {
        return false;
    }
    // check list exist
    if ($listId !== null && $listId != -1)
    {
        $db = DBConnection::instance();
        $count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}lists WHERE id=?", array($listId));
        if (!$count) return false;
    }
    return true;
}


================================================
FILE: src/config-sample.php
================================================
<?php

/*
  Uncomment the line with MTT_DB_TYPE if you make clean install only.
  Leave it commented (with # at start) if you are upgrading from version before 1.7.
  Select the database type: sqlite or mysql or postgres.
*/

#define("MTT_DB_TYPE", "sqlite");

define("MTT_DB_HOST", "localhost");

define("MTT_DB_NAME", "mytinytodo");

define("MTT_DB_USER", "mtt");

define("MTT_DB_PASSWORD", "mtt");

define("MTT_DB_PREFIX", "");

// set mysqli if needed
define("MTT_DB_DRIVER", "");

define("MTT_SALT", "Put random text here");

================================================
FILE: src/content/index.html
================================================


================================================
FILE: src/content/js/index.html
================================================


================================================
FILE: src/content/js/jquery.ui.touch-punch.js
================================================
/*!
 * jQuery UI Touch Punch 1.1.5 as modified by RWAP Software
 * based on original touchpunch v0.2.3 which has not been updated since 2014
 *
 * Updates by RWAP Software to take account of various suggested changes on the original code issues
 *
 * Original: https://github.com/furf/jquery-ui-touch-punch
 * Copyright 2011–2014, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Fork: https://github.com/RWAP/jquery-ui-touch-punch
 *
 * Modified by Max Pozdeev in 2022
 * - Added delay before mousedown dispatch
 *
 * Depends:
 * jquery.ui.widget.js
 * jquery.ui.mouse.js
 */

(function( factory ) {
    if ( typeof define === "function" && define.amd ) {

        // AMD. Register as an anonymous module.
        define([ "jquery", "jquery-ui" ], factory );
    } else {

        // Browser globals
        factory( jQuery );
    }
}(function ($) {

  // Detect touch support - Windows Surface devices and other touch devices
  $.mspointer = window.navigator.msPointerEnabled;		
  $.touch = ( 'ontouchstart' in document
   	|| 'ontouchstart' in window
   	|| window.TouchEvent
   	|| (window.DocumentTouch && document instanceof DocumentTouch)
   	|| navigator.maxTouchPoints > 0
   	|| navigator.msMaxTouchPoints > 0
  );

  // Ignore browsers without touch or mouse support
  if ((!$.touch && !$.mspointer) || !$.ui.mouse) {
	return;
  }

  let mouseProto = $.ui.mouse.prototype,
      _mouseInit = mouseProto._mouseInit,
      _mouseDestroy = mouseProto._mouseDestroy,
      touchHandled, lastClickTime  = 0;

    let delay = 300,
        delayTimer,
        delayEvent,
        delayStarted = false,
        delayFinished = false,
        lastClickCoord;


    /**
    * Get the x,y position of a touch event
    * @param {Object} event A touch event
    */
    function getTouchCoords (event) {
        return {
            x: event.originalEvent.changedTouches[0].pageX,
            y: event.originalEvent.changedTouches[0].pageY
        };
    }

  /**
   * Simulate a mouse event based on a corresponding touch event
   * @param {Object} event A touch event
   * @param {String} simulatedType The corresponding mouse event
   */
  function simulateMouseEvent (event, simulatedType) {

    // Ignore multi-touch events
    if (event.originalEvent.touches.length > 1) {
      return;
    }

    //Ignore input or textarea elements so user can still enter text
    if ($(event.target).is("input") || $(event.target).is("textarea")) {
      return;
    }

    // Prevent "Ignored attempt to cancel a touchmove event with cancelable=false" errors
    if (event.cancelable) {
      event.preventDefault();
    }

    let touch = event.originalEvent.changedTouches[0],
        simulatedEvent = new MouseEvent(simulatedType, {
          bubbles: true,
          cancelable: true,
          view:window,
          screenX:touch.screenX,
          screenY:touch.screenY,
          clientX:touch.clientX,
          clientY:touch.clientY
        });

    // Dispatch the simulated event to the target element
    event.target.dispatchEvent(simulatedEvent);
  }

    function startDelayTimer (event) {
        clearTimeout(delayTimer);
        delayEvent = event;
        delayTimer = setTimeout(function() {
            fireMouseDown.call(this);
        }, delay);
        delayStarted = true;
        delayFinished = false;
    }

    function fireMouseDown () {

        const self = this;

        delayFinished = true;

        // Set the flag to prevent other widgets from inheriting the touch event
        touchHandled = true;

        // Track movement to determine if interaction was a click
        self._touchMoved = false;

        // Simulate the mouseover event
        simulateMouseEvent(delayEvent, 'mouseover');

        // Simulate the mousemove event
        simulateMouseEvent(delayEvent, 'mousemove');

        // Simulate the mousedown event
        simulateMouseEvent(delayEvent, 'mousedown');
    }


  /**
   * Handle the jQuery UI widget's touchstart events
   * @param {Object} event The widget element's touchstart event
   */
  mouseProto._touchStart = function (event) {

    let self = this;

    // Interaction time
    this._startedMove = event.timeStamp;

    // Track movement to determine if interaction was a click
    self._startPos = getTouchCoords(event);

    // Ignore the event if another widget is already being handled
    if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
      return;
    }

    if (!delayStarted) {
        startDelayTimer.call(self, event);
    }
  };

  /**
   * Handle the jQuery UI widget's touchmove events
   * @param {Object} event The document's touchmove event
   */
  mouseProto._touchMove = function (event) {

    //
    if (!delayFinished) {
        delayStarted = false;
        clearTimeout(delayTimer);
        return;
    }

    // Ignore event if not handled
    if (!touchHandled) {
      return;
    }

    // Interaction was moved
    this._touchMoved = true;

    // Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');
  };

  /**
   * Handle the jQuery UI widget's touchend events
   * @param {Object} event The document's touchend event
   */
  mouseProto._touchEnd = function (event) {

    //
    if (delayStarted) {
        clearTimeout(delayTimer);
        delayStarted = false;
        if (!delayFinished) {
            fireMouseDown();
        }
    }

    // Ignore event if not handled
    if (!touchHandled) {
      return;
    }

    // Simulate the mouseup event
    simulateMouseEvent(event, 'mouseup');

    // Simulate the mouseout event
    simulateMouseEvent(event, 'mouseout');

    // If the touch interaction did not move, it should trigger a click
    // Check for this in two ways - length of time of simulation and distance moved
    // Allow for Apple Stylus to be used also
    let timeMoving = event.timeStamp - this._startedMove;
    if (!this._touchMoved || timeMoving < 500) {

        // Simulate the dblclick event if last click was not far away from the previous one
        if ( event.timeStamp - lastClickTime < 400 &&
            Math.abs(lastClickCoord.x - this._startPos.x) < 10 && Math.abs(lastClickCoord.y - this._startPos.y) < 10) {
            simulateMouseEvent(event, 'dblclick');
        }
        // Simulate the click event
        else
            simulateMouseEvent(event, 'click');

        lastClickTime = event.timeStamp
        lastClickCoord = this._startPos;
    }
    else {
        let endPos = getTouchCoords(event);
        if ((Math.abs(endPos.x - this._startPos.x) < 10) && (Math.abs(endPos.y - this._startPos.y) < 10)) {
            // If the touch interaction did not move, it should trigger a click
            if (!this._touchMoved || event.originalEvent.changedTouches[0].touchType === 'stylus') {
                // Simulate the click event
                simulateMouseEvent(event, 'click');
            }
        }
    }

    // Unset the flag to determine the touch movement stopped
    this._touchMoved = false;

    // Unset the flag to allow other widgets to inherit the touch event
    touchHandled = false;
  };

  let _touchStartBound;
  let _touchMoveBound;
  let _touchEndBound

  /**
   * A duck punch of the $.ui.mouse _mouseInit method to support touch events.
   * This method extends the widget with bound touch event handlers that
   * translate touch events to mouse events and pass them to the widget's
   * original mouse event handling methods.
   */
  mouseProto._mouseInit = function () {

    let self = this;
	  
    // Microsoft Surface Support = remove original touch Action
    if ($.mspointer) {
      self.element[0].style.msTouchAction = 'none';
    }	

    _touchStartBound = mouseProto._touchStart.bind(self);
    _touchMoveBound  = mouseProto._touchMove.bind(self);
    _touchEndBound   = mouseProto._touchEnd.bind(self);	  

    // Delegate the touch handlers to the widget's element
    self.element.on({
      touchstart: _touchStartBound,
      touchmove: _touchMoveBound,
      touchend: _touchEndBound
    });

    // Call the original $.ui.mouse init method
    _mouseInit.call(self);
  };

  /**
   * Remove the touch event handlers
   */
  mouseProto._mouseDestroy = function () {

    let self = this;

    // Delegate the touch handlers to the widget's element
    self.element.off({
      touchstart: _touchStartBound,
      touchmove: _touchMoveBound,
      touchend: _touchEndBound
    });

    // Call the original $.ui.mouse destroy method
    _mouseDestroy.call(self);

    //
    clearTimeout(delayTimer);
    delayEvent = null

  };

}));


================================================
FILE: src/content/mytinytodo.js
================================================
/*
    This file is a part of myTinyTodo.
    (C) Copyright 2009-2010,2020-2025 Max Pozdeev <maxpozdeev@gmail.com>
    Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/

(function(){

"use strict";

var taskList = new Array(), taskOrder = new Array();
var filter = { compl:0, search:'', due:'' };
var sortOrder; //save task order before dragging
var searchTimer;
var objPrio = {};
var flag = {
    needAuth: false,
    isLogged: false,
    tagsChanged: true,
    readOnly: false,
    editFormChanged: false,
    firstLoad: true,
    dontChangeHistoryOnce: false,
    showTagsFromAllLists: false
};
var taskCnt = { total:0, past: 0, today:0, soon:0 };
var tabLists = {
    _lists: {},
    _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') };
    },
    length: function(){ return this._length; },
    exists: function(id){ if(this._lists[id] || id==-1) return true; else return false; },
    add: function(list){ this._lists[list.id] = list; this._length++; this._order.push(list.id); },
    replace: function(list){ this._lists[list.id] = list; },
    get: function(id){ if(id==-1) return this._alltasks; else return this._lists[id]; },
    getAll: function(){ var r = []; for(var i in this._order) { r.push(this._lists[this._order[i]]); }; return r; },
    reorder: function(order){ this._order = order; }
};
var curList = 0;
var tagsList = [];
var _mtt; /* internal alias for window.mytinytodo */

var mytinytodo = window.mytinytodo = _mtt = {

    theme: {
        newTaskFlashColor: '#ffffaa',
        editTaskFlashColor: '#bbffaa',
        deleteTaskFlashColor: '#ffaaaa',
        msgFlashColor: '#ffffff'
    },

    actions: {},
    menus: {},
    mttUrl: '',
    homeUrl: '',
    apiUrl: '',
    options: {
        token: '',
        title: '',
        openList: 0,
        autotag: false,
        instantSearch: true,
        tagPreview: false,
        tagPreviewDelay: 700, //milliseconds
        ajaxAnimationDelay: 200,
        saveShowNotes: false,
        showdate: false,
        showdateInline: false,
        firstdayofweek: 1,
        touchDevice: false,
        calendarIcon: 'calendar.png', // need themeUrl+icon
        history: true,
        markdown: true,
        viewTaskOnClick: false,
        newTaskCounter: false,
        newTaskCounterIcon: false,
    },

    timers: {
        previewtag: 0,
        ajaxAnimation: 0,
        newTaskCounter: 0,
        searchTags: 0,
    },

    lang: {
        __lang: null,

        daysMin: [],
        daysLong: [],
        monthsShort: [],
        monthsLong: [],

        get: function(v) {
            if(this.__lang[v]) return this.__lang[v];
            else return v;
        },

        init: function(lang)
        {
            this.__lang = lang;
            this.daysMin = this.__lang.daysMin;
            this.daysLong = this.__lang.daysLong;
            this.monthsShort = this.__lang.monthsShort;
            this.monthsLong = this.__lang.monthsLong;
        },

        isRTL: function() {
            return this.get('_rtl') > 0 ? true : false;
        }
    },

    pages: {
        current: null,
        prev: []
    },

    pageDefault: {
        page: 'tasks',
        pageClass: '',
        lastScrollTop: 0,
        onOpen: function() { this.loadLists(); }
    },

    curList: function(){
        return curList;
    },

    flag: flag,
    lastHistoryState: null,

    // procs
    setApiDriver: function(driver)
    {
        this.db = new driver({
            useREST: false
        });
        return this;
    },

    init: function(options)
    {
        // required properties
        if (options.hasOwnProperty('lang')) {
            this.lang.init(options.lang);
            delete options.lang;
        }
        if (options.hasOwnProperty('mttUrl')) {
            this.mttUrl = options.mttUrl;
            delete options.mttUrl;
        }
        if (options.hasOwnProperty('apiUrl')) {
            this.apiUrl = options.apiUrl;
            delete options.apiUrl;
        }
        else {
            this.apiUrl = this.mttUrl + 'api.php?_path=/';
        }
        if (options.hasOwnProperty('db')) {
            delete options.db;
        }
        if (options.hasOwnProperty('homeUrl')) {
            this.homeUrl = options.homeUrl;
            delete options.homeUrl;
        }
        else {
            this.homeUrl = this.mttUrl;
        }
        if ( ! options.hasOwnProperty('touchDevice') ) {
            this.options.touchDevice = ('ontouchend' in document);
        }

        jQuery.extend(this.options, options);

        if (this.options.token) {
            jQuery.ajaxSetup( { headers: { "MTT-Token": this.options.token } } )
        }

        flag.needAuth = options.needAuth ? true : false;
        flag.isLogged = options.isLogged ? true : false;

        if (this.options.showdate) {
            $('#mtt').addClass('show-date');
        }
        if (this.options.showdateInline) {
            $('#mtt').addClass('date-inline');
        }

        // handlers
        $('.mtt-tabs-new-button').click(function(){
            addList();
        });

        $('.mtt-tabs-select-button').click(function(event){
            if (!_mtt.menus.selectlist) {
                _mtt.menus.selectlist = new mttMenu( 'slmenucontainer', { onclick:slmenuSelect, alignRight: true } );
            }
            _mtt.menus.selectlist.show(this);
        });


        $('#newtask_form').submit(function(){
            submitNewTask(this);
            return false;
        });

        $('#newtask_submit').mousedown(function(e){
            e.preventDefault(); //keep the focus in #task
            $('#newtask_form').submit();
        });

        $('#newtask_adv').click(function(){
            showEditForm(1);
            return false;
        });

        $('#task').keydown(function(event){
            if(event.keyCode == 27) {
                $(this).val('');
            }
        }).focusin(function(){
            $('#task_placeholder').removeClass('placeholding');
            $('#toolbar').addClass('mtt-intask');
        }).focusout(function(){
            if('' == $(this).val()) $('#task_placeholder').addClass('placeholding');
            $('#toolbar').removeClass('mtt-intask');
        });


        $('#search_close').click(function(){
            liveSearchToggle(0);
            return false;
        });

        $('#search').keyup(function(event){
            if(event.keyCode == 27) return;
            if($(this).val() == '') $('#search_close').hide();  //actual value is only on keyup
            else $('#search_close').show();
            if (_mtt.options.instantSearch) {
                clearTimeout(searchTimer);
                searchTimer = setTimeout(function(){searchTasks()}, 300);
            }
        })
        .keydown(function(event){
            if(event.keyCode == 27) {  // cancel on Esc (NB: no esc event on keypress in Chrome and on keyup in Opera)
                if($(this).val() != '') {
                    $(this).val('');
                    $('#search_close').hide();
                    searchTasks();
                }
                else {
                    liveSearchToggle(0);
                }
                return false; //need to return false in firefox (for AJAX?)
            }
            else if ( event.keyCode == 13 ) {
                searchTasks(1);
                return false;
            }
        }).focusin(function(){
            $('#toolbar').addClass('mtt-insearch');
        }).focusout(function(){
            $('#toolbar').removeClass('mtt-insearch');
        });


        $('#taskview').click(function(){
            if(!_mtt.menus.taskview) _mtt.menus.taskview = new mttMenu('taskviewcontainer');
            _mtt.menus.taskview.show(this);
        });

        $('#mtt-tag-filters').on('click', '.mtt-filter-close', function(){
            cancelTagFilter($(this).attr('tagid'));
        });

        $('#mtt-tag-toolbar-close').click(function(){
            cancelTagFilter(0);
        });

        $('#tagcloudbtn').click(function(){
            if (flag.readOnly) {
                $('#tagcloudAllLists').prop('checked', false).prop('disabled', true);
            }
            else if (curList.id == -1) {
                $('#tagcloudAllLists').prop('checked', true).prop('disabled', true);
            }
            else {
                $('#tagcloudAllLists').prop('checked', flag.showTagsFromAllLists).prop('disabled', false);
            }
            if (!_mtt.menus.tagcloud) _mtt.menus.tagcloud = new mttMenu('tagcloud', {
                beforeShow: function(){
                    if (flag.tagsChanged) {
                        $('#tagcloudcontent').html('');
                        $('#tagcloudload').show();
                        loadTags(curList.id, function() {
                            $('#tagcloudload').hide();
                            document.getElementById('tagcloudSearch').value = '';
                        });
                    }
                },
                alignRight: true,
                onClose: function(){
                    document.getElementById('tagcloudSearch').value = '';
                    searchTags();
                }
            });
            _mtt.menus.tagcloud.show(this);
        });

        $('#tagcloudSearch').keyup(function(event) {
            if (event.keyCode == 27) return;
            clearTimeout(_mtt.timers.searchTags);
            _mtt.timers.searchTags = setTimeout(function(){searchTags()}, 400);

        })
        .keydown(function(event){
            if (event.keyCode == 27) { // Cancel on Esc
                if (this.value === '') return; //allow to close the popup
                this.value = '';
                clearTimeout(_mtt.timers.searchTags);
                searchTags();
                return false;
            }
        })

        $('#tagcloudcancel').click(function(){
            if(_mtt.menus.tagcloud) _mtt.menus.tagcloud.close();
        });

        $('#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();
            return false;
        });

        $('#tagcloudAllLists').click(function(){
            flag.showTagsFromAllLists = this.checked;
            $('#tagcloudcontent').html('');
            $('#tagcloudload').show();
            loadTags(curList.id, function(){
                $('#tagcloudload').hide();
                $('#tagcloudSearch').val('');
            });
        });

        $('#mtt-notes-show').click(function(e){
            toggleAllNotes(1, e);
            this.blur();
            return false;
        });

        $('#mtt-notes-hide').click(function(e){
            toggleAllNotes(0, e);
            this.blur();
            return false;
        });

        $('#taskviewcontainer li').click(function(){
            if(this.id == 'view_tasks') setTaskview(0);
            else if(this.id == 'view_past') setTaskview('past');
            else if(this.id == 'view_today') setTaskview('today');
            else if(this.id == 'view_soon') setTaskview('soon');
        });


        // Tabs
        $('#lists').on('click', 'li.mtt-tab', function(event) {
            var listId = this.id.split('_', 2)[1];
            if (listId === 'all') listId = -1;
            if(event.metaKey || event.ctrlKey) {
                // hide the tab
                hideList(listId);
                return false;
            }
            tabSelect(listId);
            return false;
        });

        $('#lists').on('click', 'li.mtt-tab .list-action', function(){
            listMenu(this);
            return false;   //stop bubble to tab click
        });

        //Priority popup
        $('#priopopup .prio-neg-1').click(function(){
            prioClick(-1,this);
        });

        $('#priopopup .prio-zero').click(function(){
            prioClick(0,this);
        });

        $('#priopopup .prio-pos-1').click(function(){
            prioClick(1,this);
        });

        $('#priopopup .prio-pos-2').click(function(){
            prioClick(2,this);
        });

        $('#priopopup').mouseleave(function(){
            $(this).hide()}
        );


        // edit form handlers
        $('#alltags_show').click(function(){
            toggleEditAllTags(1);
            return false;
        });

        $('#alltags_hide').click(function(){
            toggleEditAllTags(0);
            return false;
        });

        $('#taskedit_form').submit(function(){
            return saveTask(this);
        });

        $('#alltags').on('click', '.tag', function(){
            addEditTag(this.dataset.tag);
            return false;
        });

        $("#duedate").datepicker({
            dateFormat: _mtt.duedatepickerformat(),
            firstDay: _mtt.options.firstdayofweek,
            showOn: 'button',
            buttonImage: _mtt.options.calendarIcon,
            buttonImageOnly: true,
            constrainInput: false,
            duration:'',
            dayNamesMin:_mtt.lang.daysMin,
            dayNames:_mtt.lang.daysLong,
            monthNamesShort:_mtt.lang.monthsShort,
            monthNames:_mtt.lang.monthsLong,
            changeMonth: true,
            changeYear: true,
            isRTL: _mtt.lang.isRTL()
        });

        function ac_split( val ) {
            return val.split( /,\s*/ );
        }
        function ac_extractLast( term ) {
            return ac_split( term ).pop();
        }

        $("#edittags").autocomplete({
            source: function(request, response) {
                var taskId = document.getElementById('taskedit_form').id.value;
                var listId = (taskId != '') ? taskList[taskId].listId : curList.id;
                _mtt.db.request('suggestTags', {list:listId, q:ac_extractLast(request.term)}, function(json){
                    response(json);
                })
            },/*
            search: function() {
                // custom minLength
                var term = ac_extractLast( this.value );
                if ( term.length < 2 ) {
                  return false;
                }
            },*/
            focus: function() {
                // prevent value inserted on focus using keyboard
                return false;
            },
            select: function( event, ui ) {
                var terms = ac_split( this.value );
                terms.pop(); // remove the current input
                terms.push( ui.item.value ); // add the selected item
                terms.push( "" ); // add placeholder to get the comma-and-space at the end
                this.value = terms.join( ", " );
                return false;
            }
        });

        $('#taskedit_form').find('select,input,textarea').bind('change keypress', function(){
            flag.editFormChanged = true;
        });

        $('#taskviewer_edit_btn').on('click', function() {
            const id = document.getElementById('page_taskviewer').dataset.id;
            editTask(id);
        });

        if (this.options.touchDevice) {
            this.options.viewTaskOnClick = true;
        }

        if (this.options.viewTaskOnClick) {
            $('#mtt').addClass('view-task-on-click');
        }

        // tasklist handlers
        $("#tasklist").on('click', '> li.task-row .task-title', function(e) {
            if ( findParentNode(e.target, 'A') ) {
                return; //ignore clicks on links
            }
            const li = findParentNode(this, 'LI');
            if (li && li.id) {
                if (e.altKey) {
                    viewTask(li.dataset.id);
                    return;
                }
                if (_mtt.options.viewTaskOnClick) {
                    viewTask(li.dataset.id);
                }
            }
        });

        $('#tasklist').on('dblclick', '> li.task-row .task-middle, > li.task-row .task-note-block', function(){
            let id = parseInt(getLiTaskId(this));
            if (id) {
                //clear selection
                if (document.selection && document.selection.empty && document.selection.createRange().text)
                    document.selection.empty();
                else if (window.getSelection)
                    window.getSelection().removeAllRanges();
                editTask(id);
            }
        });

        $('#tasklist').on('click', '.taskactionbtn', function(){
            var id = parseInt(getLiTaskId(this));
            if(id) taskContextMenu(this, id);
            return false;
        });

        $('#tasklist').on('click', 'input[type=checkbox]', function(){
            var id = parseInt(getLiTaskId(this));
            if(id) completeTask(id, this);
            //return false;
        });

        $('#tasklist').on('click', '.task-toggle', function(){
            var id = getLiTaskId(this);
            if(id) $('#taskrow_'+id).toggleClass('task-expanded');
            return false;
        });

        $('#tasklist').on('click', '.tag', function(event){
            clearTimeout(_mtt.timers.previewtag);
            $('#tasklist li').removeClass('not-in-tagpreview');
            //tag is not escaped
            addFilterTag(this.dataset.tag, this.dataset.tagId, (event.metaKey || event.ctrlKey ? true : false) );
            return false;
        });

        if(!this.options.touchDevice) {
            $('#tasklist').on('mouseover mouseout', '.task-prio', function(event){
                var id = parseInt(getLiTaskId(this));
                if(!id) return;
                if(event.type == 'mouseover') prioPopup(1, this, id);
                else prioPopup(0, this);
            });
        }

        $('#tasklist').on('click', '.mtt-action-note-cancel', function(){
            var id = parseInt(getLiTaskId(this));
            if(id) cancelTaskNote(id);
            return false;
        });

        $('#tasklist').on('click', '.mtt-action-note-save', function(){
            var id = parseInt(getLiTaskId(this));
            if(id) saveTaskNote(id);
            return false;
        });

        if (this.options.tagPreview && !this.options.touchDevice) {
            $('#tasklist').on('mouseover mouseout', '.tag', function(event){
                const cl = 'tag-id-' + this.dataset.tagId;
                const sel = (event.metaKey || event.ctrlKey) ? 'li.'+cl : 'li:not(.'+cl+')';
                if (event.type == 'mouseover') {
                    _mtt.timers.previewtag = setTimeout( function(){
                        $('#tasklist '+sel).addClass('not-in-tagpreview');
                    }, _mtt.options.tagPreviewDelay);
                }
                else {
                    clearTimeout(_mtt.timers.previewtag);
                    $('#tasklist li').removeClass('not-in-tagpreview');
                }
            });
        }

        $("#tasklist").sortable({
            items: '> :not(.task-completed)',
            cancel: 'span,input,a,textarea,.task-note-block',
            delay: 150,
            start: tasklistSortStart,
            update: tasklistSortUpdated,
            placeholder: 'mtt-task-placeholder',
            cursor: 'grabbing'
        });


        $("#lists ul").sortable({
            delay: 150,
            update: listOrderChanged,
            items: '> :not(#list_all)',
            forcePlaceholderSize : true,
            placeholder: 'mtt-tab mtt-tab-sort-placeholder',
            cursor: 'grabbing'
        });


        if (this.options.touchDevice) {
            $("#tasklist").disableSelection();
            $("#tasklist").sortable('option', {
                axis: 'y',
                delay: 50,
                cancel: 'input',
                distance: 0
            });
            /*$('#cmenu_note').hide();*/
            $("#lists ul").sortable('disable');
            $("#mtt").addClass("touch-device");
        }


        // AJAX Errors
        $(document).ajaxSend(function(r,s){
            hideAlert();
            clearTimeout(_mtt.timers.ajaxAnimation);
            _mtt.timers.ajaxAnimation = setTimeout( function(){
                $("#mtt").addClass("ajax-loading");
            }, _mtt.options.ajaxAnimationDelay );
        });

        $(document).ajaxStop(function(r,s){
            clearTimeout(_mtt.timers.ajaxAnimation);
            $("#mtt").removeClass("ajax-loading");
        });

        $(document).ajaxError(function(event, request, settings){
            var errtxt;
            if (request.status == 0) errtxt = 'Bad connection';
            else if(request.status == 403) errtxt = request.responseText;
            else if (request.status != 200) errtxt = 'HTTP: '+request.status+'/'+request.statusText + "\n" + request.responseText;
            else errtxt = request.responseText;
            flashError(_mtt.lang.get('error'), errtxt);
        });


        // Error Message details
        $("#msg>.msg-text").click(function(){
            $("#msg>.msg-details").toggle();
        });


        // Authentication
        $('#login_btn').click(function(){
            showLogin();
            return false;
        });

        $('#logout_btn').click(function(){
            logout();
            return false;
        });

        $('#login_form').submit(function(){
            doAuth(this);
            return false;
        });


        // Settings
        $(document).on('click', 'a[data-settings-link]', function(event) {
            var settingsPage = this.dataset.settingsLink;
            if (settingsPage == 'index') {
                showSettings( (event.metaKey || event.ctrlKey) ? 1 : 0 );
            }
            else if (settingsPage == 'ext-activate' || settingsPage == 'ext-deactivate') {
                activateExtension(settingsPage == 'ext-activate' ? true : false, this.dataset.ext);
            }
            else if (settingsPage == 'ext-index') {
                showExtensionSettings(this.dataset.ext);
            }
            return false;
        });

        $("#page_ajax").on('submit', '#settings_form', function() {
            saveSettings(this);
            return false;
        });

        $("#page_ajax").on('submit', '#ext_settings_form', function() {
            saveExtensionSettings(this);
            return false;
        });

        $(document).on('click', '.mtt-back-button', function() {
            _mtt.pageBack(true);
            this.blur();
            return false;
        });

        $(window).bind('beforeunload', function() {
            if (_mtt.pages.current && _mtt.pages.current.page == 'taskedit' && flag.editFormChanged) {
                return _mtt.lang.get('confirmLeave');
            }
        });

        $("#page_ajax").on('click', 'a[data-ext-settings-action],button[data-ext-settings-action]', function() {
            extensionSettingsAction(this.dataset.extSettingsAction, this.dataset.ext);
            return false;
        });


        // tab menu
        this.addAction('listSelected', tabmenuOnListSelected);

        // task context menu
        this.addAction('listsLoaded', cmenuOnListsLoaded);
        this.addAction('listRenamed', cmenuOnListRenamed);
        this.addAction('listAdded', cmenuOnListAdded);
        this.addAction('listSelected', cmenuOnListSelected);
        this.addAction('listOrderChanged', cmenuOnListOrderChanged);
        this.addAction('listHidden', cmenuOnListHidden);

        // select list menu
        this.addAction('listsLoaded', slmenuOnListsLoaded);
        this.addAction('listRenamed', slmenuOnListRenamed);
        this.addAction('listAdded', slmenuOnListAdded);
        this.addAction('listSelected', slmenuOnListSelected);
        this.addAction('listHidden', slmenuOnListHidden);

        //History
        if (this.options.history) {
            window.onpopstate = historyOnPopState;
            window.history.scrollRestoration = 'manual';
        }

        // Appearance mode for CSS
        if (window.matchMedia) {
            document.documentElement.setAttribute('data-system-appearance', window.matchMedia("(prefers-color-scheme: dark)").matches ? 'dark' : 'light');
            // TODO: use MediaQueryList.onchange since Safari 14 (macos 10.14) is min target
            window.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
              document.documentElement.setAttribute('data-system-appearance', e.matches ? 'dark' : 'light');
            });
        }

        // Counter
        if (this.options.newTaskCounter /* TODO: && !flag.readOnly */) {
            this.addAction('listsLoaded', newTaskCounterStart);
            this.addAction('listSelected', newTaskCounterOnListSelected)
            if (this.options.newTaskCounterIcon) {
                this.addAction('newTaskCounterUpdated', newTaskCounterUpdated);
            }
        }

        this.doAction( 'init' );

        return this;
    },

    log: function(v)
    {
        console.log.apply(this, arguments);
    },

    addAction: function(action, proc)
    {
        if(!this.actions[action]) this.actions[action] = new Array();
        this.actions[action].push(proc);
    },

    doAction: function(action, opts)
    {
        if(!this.actions[action]) return;
        for(var i in this.actions[action]) {
            this.actions[action][i](opts);
        }
    },

    setOptions: function(opts) {
        jQuery.extend(this.options, opts);
    },

    run: function()
    {
        var path = this.parseAnchor();

        updateAccessStatus();

        if (path.settings) {
            showSettings(path.settings == 'json' ? 1 : 0);
        }
        else if (path.search && path.list) {
            filter.search = path.search;
            this.pageSet('tasks', '');
            this.loadLists();
        }
        else {
            this.pageSet('tasks', '');
            this.loadLists();
        }
    },

    loadLists: function()
    {
        if(filter.search != '') {
            //filter.search = '' will be in tabSelect
            $('#searchbarkeyword').text('');
            $('#searchbar').hide();
        }
        $('#page_tasks').hide();
        $('#tasklist').html('');
        $('#tasks_info').hide();

        tabLists.clear();

        this.db.loadLists(null, function(res)
        {
            var ti = '';
            var openListId = 0;

            if (res && res.total && res.list)
            {
                // open required or last opened or first non-hidden list
                let list;
                if (_mtt.options.openList) {
                    list = res.list.find( item => _mtt.options.openList == item.id );
                }
                else {
                    const lastOpenList = getLocalStorageItem('lastList');
                    if (lastOpenList && !flag.readOnly) {
                        list = res.list.find( item => !item.hidden && lastOpenList == item.id );
                    }
                    if (!list) {
                        list = res.list.find( item => !item.hidden );
                    }
                }
                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);
                    }
                    else {
                        tabLists.add(item);
                        ti += prepareListHtml(item);
                    }
                });
            }

            if (openListId == 0) {
                curList = 0;
            }

            if (_mtt.options.markdown == true) {
                $('#mtt').addClass('markdown-enabled');
            }

            if (tabLists.length() > 0) {
                $('#mtt').removeClass('no-lists');
            }
            else {
                $('#mtt').addClass('no-lists');
            }

            if (_mtt.options.openList != 0 && openListId == 0) {
                // cant open list - not found
                $('#tasks_info .v').text(_mtt.lang.get('listNotFound'))
                $('#tasks_info').show();
            }
            else if (tabLists.length() == 0) {
                if (flag.readOnly) $('#tasks_info .v').text(_mtt.lang.get('noPublicLists'));
                else $('#tasks_info .v').text(_mtt.lang.get('listNotFound'))
                $('#tasks_info').show();
            }

            _mtt.options.openList = 0;
            $('#lists .mtt-tab-selected').removeClass('mtt-tab-selected');
            $('#mtt').addClass('no-list-selected');
            $('#lists ul').html(ti);
            $('#lists').show();
            _mtt.doAction('listsLoaded');

            if (tabLists.length() > 0 && openListId != 0) {
                tabSelect(openListId);
            }
            $('#page_tasks').show();
        });
    },

    duedatepickerformat: function()
    {
        if (!this.options.duedatepickerformat)
            return 'yy-mm-dd';

        const s = this.options.duedatepickerformat.replace(/(.)/g, function(t,s) {
            switch(t) {
                case 'Y': return 'yy';
                case 'y': return 'yy';
                case 'd': return 'dd';
                case 'j': return 'd';
                case 'm': return 'mm';
                case 'M': return 'M';
                case 'n': return 'm';
                case ' ':
                case '/':
                case '.':
                case '-': return t;
                default: return '';
            }
        });

        if (s == '')
            return 'yy-mm-dd';
        return s;
    },

    errorDenied: function()
    {
        flashError(this.lang.get('denied'));
    },

    pageSet: function(page, pageClass)
    {
        if (this.pages.current) {
            var prev = this.pages.current;
            prev.lastScrollTop = $(window).scrollTop();
            this.pages.prev.push(this.pages.current);
            $('#mtt').removeClass('page-' + prev.page);
            $('#page_'+ prev.page).removeClass('mtt-page-'+prev.page.pageClass).hide();
        }
        $(window).scrollTop(0);
        this.pages.current = { page:page, pageClass:pageClass };
        $('#mtt').addClass('page-' + page);
        $('#page_'+ this.pages.current.page).show().addClass('mtt-page-'+ this.pages.current.pageClass);
    },

    pageBack: function(clicked)
    {
        hideAlert();
        $(document).off('keydown.mttback');
        // If clicked on back button in settings or taskviewer we'll use history navigation
        if ( clicked && this.pages.current && this.pages.prev.length > 0 &&
            ((_mtt.pages.current.page == 'ajax' && _mtt.pages.current.pageClass == 'settings')
              || _mtt.pages.current.page == 'taskviewer') ) {
            window.history.back();
            return;
        }
        if (this.pages.current.page == 'tasks') {
            return;
        }
        if (this.pages.current) {
            var prev = this.pages.current;
            $('#mtt').removeClass('page-' + prev.page);
            $('#page_'+ prev.page).removeClass('mtt-page-'+prev.pageClass);
            $('#page_'+ prev.page).hide();
        }
        var cur = this.pages.prev.pop();
        this.pages.current = cur ? cur : this.pageDefault;
        $('#mtt').addClass('page-' + this.pages.current.page);
        $('#page_'+ this.pages.current.page).addClass('mtt-page-'+ this.pages.current.pageClass).show();
        $(window).scrollTop(this.pages.current.lastScrollTop);
        if (!cur && this.pages.current.onOpen) {
            this.pages.current.onOpen.call(this);
        }
    },


    filter: {
        _filters: [],

        clear() {
            this._filters = [];
            $('#mtt-tag-toolbar').hide();
            $('#mtt-tag-filters').html('');
        },

        addTag(tagId, tag, exclude)
        {
            //Catch 'any tag' filter
            if (tagId == -2) {
                tagId = -1;
                tag = '^';
                exclude = true
            }
            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(tagId)
        {
            for (let i in this._filters) {
                if (this._filters[i].tagId && this._filters[i].tagId == tagId) {
                    this._filters.splice(i, 1);
                    $('#mtt-tag-filters .tag-filter.tag-id-'+tagId).remove();
                    if (this._filters.length == 0) {
                        $('#mtt-tag-toolbar').hide();
                    }
                    return true;
                }
            }
            return false;
        },

        getTags(withExcluded)
        {
            let a = [];
            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(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>`;
        }
    },

    parseAnchor: function()
    {
        if(location.hash == '') return false;
        var h = location.hash.substr(1);
        var a = h.split("/");
        var p = {};
        var s = '';

        for(var i=0; i<a.length; i++)
        {
            s = a[i];
            switch(s) {
                case "list": if(a[++i].match(/^-?\d+$/)) { p[s] = a[i]; } break;
                case "alltasks": p.list = '-1'; break;
                case "settings": p.settings = true; break;
                case "settings.json": p.settings = 'json'; break;
                case "search":   p.search = decodeURIComponent(a[++i]); break;
            }
        }

        if(p.list) this.options.openList = p.list;

        return p;
    },

    urlForList: function(list)
    {
        var l = list || curList;
        if (l === undefined) return '';
        if (l.id == -1) return '#alltasks';
        return '#list/' + l.id;
    },

    urlForExport: function(format, list)
    {
        var l = list || curList;
        if (l === undefined) return '';
        if (!format.match(/^[a-z0-9-]+$/i)) return '';
        return this.mttUrl + 'export.php?list='+l.id +'&format='+format;
    },

    urlForFeed: function(list)
    {
        list = list || curList;
        if (list === undefined) return '';
        return _mtt.mttUrl + 'feed.php?list=' + list.id;
    },

    urlForSettings: function(json = 0)
    {
        if (json == 1) return '#settings.json';
        return '#settings';
    },

    urlForExtSettings: function(ext)
    {
        return '#settings/ext/' + ext;
    }

}; // End of mytinytodo object

function addList()
{
    mttPrompt( _mtt.lang.get('addList'), _mtt.lang.get('addListDefault'), function(r)
    {
        _mtt.db.request('addList', {name:r}, function(json){
            if (!parseInt(json.total)) return;
            var item = json.list[0];
            var i = tabLists.length();
            tabLists.add(item);
            if (i > 0) {
                $('#lists ul').append(prepareListHtml(item));
                mytinytodo.doAction('listAdded', item);
            }
            else {
                _mtt.loadLists();
            }
        });
    });
};

function renameCurList()
{
    if (!curList) return;
    mttPrompt( _mtt.lang.get('renameList'), dehtml(curList.name), function(r)
    {
        _mtt.db.request('renameList', {list:curList.id, name:r}, function(json){
            if (!parseInt(json.total)) return;
            var item = json.list[0];
            curList = item;
            tabLists.replace(item);
            $('#list_'+curList.id).replaceWith(prepareListHtml(curList, true));
            mytinytodo.doAction('listRenamed', item);
        });
    });
};

function deleteCurList()
{
    if (!curList) return false;
    mttConfirm( _mtt.lang.get('deleteList'), function()
    {
        _mtt.db.request('deleteList', {list:curList.id}, function(json){
            if (!parseInt(json.total)) return;
            _mtt.loadLists();
        });
    });
};

function publishCurList()
{
    if(!curList) return false;
    _mtt.db.request('publishList', { list:curList.id, publish:curList.published?0:1 }, function(json){
        if(!parseInt(json.total)) return;
        curList.published = curList.published?0:1;
        if(curList.published) {
            $('#btnPublish').addClass('mtt-item-checked');
            $('#btnRssFeed').removeClass('mtt-item-disabled');
        }
        else {
            $('#btnPublish').removeClass('mtt-item-checked');
            $('#btnRssFeed').addClass('mtt-item-disabled');
        }
    });
};

function enableFeedKeyInCurList()
{
    if (!curList) return false;
    _mtt.db.request('enableFeedKey', {
        list: curList.id,
        enable: (curList.feedKey === undefined || curList.feedKey === '') ? 1 : 0
    }, function(json){
        if (!parseInt(json.total)) return;
        var item = json.list[0];
        curList.feedKey = item.feedKey;
        if (curList.feedKey) {
            $('#btnFeedKey').addClass('mtt-item-checked');
            $('#btnShowFeedKey').removeClass('mtt-item-disabled');
            mttAlert(curList.feedKey);
        }
        else {
            $('#btnFeedKey').removeClass('mtt-item-checked');
            $('#btnShowFeedKey').addClass('mtt-item-disabled');
        }
    });
};

function showFeedKeyInCurList()
{
    if (!curList) return false;
    if (curList.feedKey === undefined || curList.feedKey === '') return false;
    mttAlert(curList.feedKey);
};


function loadTasks(opts)
{
    if(!curList) return false;
    updateSortUI(curList.sort);
    opts = opts || {};
    if(opts.clearTasklist) {
        $('#tasklist').html('');
        $('#total').html('0');
    }

    _mtt.db.request('loadTasks', {
        list: curList.id,
        compl: curList.showCompl,
        sort: curList.sort,
        search: filter.search,
        tag: _mtt.filter.getTags(true),
        setCompl: opts.setCompl,
        saveSort: opts.saveSort
    }, function(json){
        taskList.length = 0;
        taskOrder.length = 0;
        taskCnt.total = taskCnt.past = taskCnt.today = taskCnt.soon = 0;
        var tasks = '';
        $.each(json.list, function(i,item){
            tasks += _mtt.prepareTaskStr(item);
            taskList[item.id] = item;
            taskOrder.push(parseInt(item.id));
            changeTaskCnt(item, 1);
        });
        curList.lastTime = json.time;
        setNewTaskCounterForList(curList.id, 0);
        _mtt.doAction("newTaskCounterUpdated", curList.id);
        if(opts.beforeShow && opts.beforeShow.call) {
            opts.beforeShow();
        }
        refreshTaskCnt();
        $('#tasklist').html(tasks);
    });
};

function prepareListHtml(list, isSelected)
{
    const classSelected = isSelected ? 'mtt-tab-selected' : '';
    const classHidden = list.hidden ? 'mtt-tab-hidden' : '';
    const liId = list.id == -1 ? 'list_all' : 'list_' + list.id;
    return `<li id="${liId}" class="mtt-tab ${classSelected} ${classHidden}" data-id="${list.id}">` +
           '<a href="' + _mtt.urlForList(list) + '" title="' + list.name + '">'+
             '<div class="title-block"><span class="counter hidden"></span>'+
             '<span class="title">' + list.name + '</span></div>' +
             '<div class="list-action mtt-img-button"><span></span></div>'+
           '</a></li>';
}

function prepareTaskStr(item, noteExp)
{
    return '<li id="taskrow_'+item.id+'" ' + 'data-id="'+item.id + '" class="task-row ' + (item.compl?'task-completed ':'') + item.dueClass + (item.note!=''?' task-has-note':'') +
                ((curList.showNotes && item.note != '') || noteExp ? ' task-expanded' : '') + prepareDomClassOfTags(item.tags_ids) + '">' +
                    prepareTaskBlocks(item) + "</li>\n";
};
_mtt.prepareTaskStr = prepareTaskStr;

function prepareTaskBlocks(item)
{
    const id = item.id;
    let markdown = '';
    if (_mtt.options.markdown == true) markdown = 'markdown-note';
    return '' +
        '<div class="task-block">' +
            '<div class="task-left">' +
                '<div class="task-toggle"></div>' +
                '<label><input type="checkbox" '+(flag.readOnly?'disabled="disabled"':'')+(item.compl?'checked="checked"':'')+'></label>' +
            "</div>\n" +

            '<div class="task-middle">' +
                '<div class="task-middle-top">' +
                    '<div class="task-through">' +
                        preparePrio(item.prio,id) +
                        '<span class="task-title">' + prepareTaskTitleInlineHtml(item.title) + '</span> ' +
                        (curList.id == -1 ? prepareListNameInline(item) : '') +
                        '<span class="task-tags">' + prepareTagsStr(item) + '</span>' +
                        '<div class="task-date">' + prepareInlineDate(item) + '</div>' +
                    '</div>' +
                    '<div class="task-through-right">' + prepareDueDate(item) + "</div>" +
                '</div>' +
            "</div>" +

            '<div class="task-actions"><div class="taskactionbtn"></div></div>' +
        '</div>' +

        '<div class="task-note-block">' +
            '<div id="tasknote' + id + '" class="task-note ' + markdown + '">' + prepareTaskNoteInlineHtml(item.note, item.noteText) + '</div>' +
            '<div id="tasknotearea'+id+'" class="task-note-area"><textarea id="notetext'+id+'"></textarea>'+
                '<span class="task-note-actions"><a href="#" class="mtt-action-note-save">'+_mtt.lang.get('actionNoteSave') +
                    '</a> | <a href="#" class="mtt-action-note-cancel">'+_mtt.lang.get('actionNoteCancel')+'</a></span>' +
            '</div>' +
        '</div>';
};
_mtt.prepareTaskBlocks = prepareTaskBlocks;

function prepareTaskTitleInlineHtml(s)
{
    // Task title is already escaped on back-end
    return s;
}
_mtt.prepareTaskTitleInlineHtml = prepareTaskTitleInlineHtml;

function prepareListNameInline(item)
{
    // Used in AllTasks list
    // List name is already escaped on back-end
    return '<span class="task-listname">'+ item.listName +'</span>';
}
_mtt.prepareListNameInline = prepareListNameInline;

function prepareTaskNoteInlineHtml(s, rawText)
{
    // Task note is already escaped on back-end
    return s;
};
_mtt.prepareTaskNoteInlineHtml = prepareTaskNoteInlineHtml;

function preparePrio(prio,id)
{
    var cl =''; var v = '';
    if(prio < 0) { cl = 'prio-neg prio-neg-'+Math.abs(prio); v = '&#8722;'+Math.abs(prio); }    // &#8722; = &minus; = −
    else if(prio > 0) { cl = 'prio-pos prio-pos-'+prio; v = '+'+prio; }
    else { cl = 'prio-zero'; v = '&#177;0'; }                                                   // &#177; = &plusmn; = ±
    return '<span class="task-prio '+cl+'">'+v+'</span>';
};
_mtt.preparePrio = preparePrio;

function prepareTagsStr(item, delimiter = ', ')
{
    if (!item.tags || item.tags == '') return '';
    let a = item.tags.split(',');
    if (!a.length) return '';
    const b = item.tags_ids.split(',')
    for (let i in a) {
        // tag is escaped
        a[i] = '<span class="tag" data-tag="'+a[i]+'" data-tag-id="'+b[i]+'">'+a[i]+'</span>';
    }
    return a.join(delimiter);
};
_mtt.prepareTagsStr = prepareTagsStr;

function prepareDomClassOfTags(ids)
{
    if(!ids || ids == '') return '';
    var a = ids.split(',');
    if(!a.length) return '';
    for(var i in a) {
        a[i] = 'tag-id-'+a[i];
    }
    return ' '+a.join(' ');
};
_mtt.prepareDomClassOfTags = prepareDomClassOfTags;

function prepareDueDate(item)
{
    if(!item.duedate) return '';
    return '<span class="duedate" title="'+item.dueTitle+'">'+item.dueStr+'</span>';
};
_mtt.prepareDueDate = prepareDueDate;

function prepareInlineDate(item)
{
    var inlineDate = item.dateInlineTitle;
    var title = item.dateFull;
    if (item.compl) {
        inlineDate = item.dateCompletedInlineTitle;
        title = item.dateCompletedFull;
    }
    else if ( item.isEdited && (curList.sort == 4 || curList.sort == 104) ) {
        inlineDate = item.dateEditedInlineTitle;
        title = item.dateEditedFull;
    }
    return '<span class="task-id">#' + item.id + '</span> <span title="' + title +'">' + inlineDate + '</span>';
}
_mtt.prepareInlineDate = prepareInlineDate;

function submitNewTask(form)
{
    if(form.task.value == '') return false;
    _mtt.db.request('newTask', { list:curList.id, title: form.task.value, tag:_mtt.filter.getTags() }, function(json){
        if(!json.total) return;
        $('#total').text( parseInt($('#total').text()) + 1 );
        taskCnt.total++;
        form.task.value = '';
        var item = json.list[0];
        taskList[item.id] = item;
        taskOrder.push(parseInt(item.id));
        $('#tasklist').append(_mtt.prepareTaskStr(item));
        changeTaskOrder(item.id);
        $('#taskrow_'+item.id).effect("highlight", {color:_mtt.theme.newTaskFlashColor}, 2000);
        refreshTaskCnt();
    });
    flag.tagsChanged = true;
    return false;
};


function changeTaskOrder(id)
{
    id = parseInt(id);
    if (taskOrder.length < 2) {
        return;
    }
    if (id && (curList.sort == 5 || curList.sort == 105)) {
        // re-sort the whole list in case of database sorting is not the same due to collation
        changeTaskOrder();
    }
    const oldOrder = taskOrder.slice();
    function firstNonZero(order, compl,  ...args) {
        const m = (order < 100) ? 1 : -1;
        if (compl != 0) return compl;
        for (const arg of args) {
            if (arg != 0) return arg * m;
        }
        return 0;
    }
    // sortByHand
    if (curList.sort == 0 || curList.sort == 100) {
        taskOrder.sort( (a, b) => firstNonZero(
            curList.sort,
            taskList[a].compl - taskList[b].compl,
            taskList[a].ow - taskList[b].ow
        ))
    }
    // sortByPrio and reverse
    else if (curList.sort == 1 || curList.sort == 101) {
        taskOrder.sort( (a, b) => firstNonZero(
            curList.sort,
            taskList[a].compl - taskList[b].compl,
            taskList[b].prio - taskList[a].prio,
            taskList[a].dueInt - taskList[b].dueInt,
            taskList[a].ow - taskList[b].ow
        ));
    }
    // sortByDueDate and reverse
    else if (curList.sort == 2 || curList.sort == 102) {
        taskOrder.sort( (a, b) => firstNonZero(
            curList.sort,
            taskList[a].compl - taskList[b].compl,
            taskList[a].dueInt - taskList[b].dueInt,
            taskList[b].prio - taskList[a].prio,
            taskList[a].ow - taskList[b].ow
        ))
    }
    // sortByDateCreated and reverse
    else if (curList.sort == 3 || curList.sort == 103) {
        taskOrder.sort( (a, b) => firstNonZero(
            curList.sort,
            taskList[a].compl - taskList[b].compl,
            taskList[a].dateInt - taskList[b].dateInt,
            taskList[b].prio - taskList[a].prio,
            taskList[a].ow - taskList[b].ow
        ));
    }
    // sortByDateModified and reverse
    else if (curList.sort == 4 || curList.sort == 104) {
        taskOrder.sort( (a, b) => firstNonZero(
            curList.sort,
            taskList[a].compl - taskList[b].compl,
            taskList[a].dateEditedInt - taskList[b].dateEditedInt,
            taskList[b].prio - taskList[a].prio,
            taskList[a].ow - taskList[b].ow
        ))
    }
    // sortByTitle and reverse
    else if (curList.sort == 5 || curList.sort == 105) {
        taskOrder.sort( (a, b) => firstNonZero(
            curList.sort,
            taskList[a].compl - taskList[b].compl,
            taskList[a].title.localeCompare(taskList[b].title, 'en', {sensitivity: 'base'}),
            taskList[b].prio - taskList[a].prio,
            taskList[a].ow - taskList[b].ow
        ))
    }
    else {
        return;
    }
    if (oldOrder.toString() == taskOrder.toString()) {
        return;
    }
    if (id && taskList[id]) {
        // optimization: determine where to insert task: top or after some task
        const indx = $.inArray(id, taskOrder);
        if (indx == 0) {
            $('#tasklist').prepend($('#taskrow_'+id))
        } else {
            const after = taskOrder[indx-1];
            $('#taskrow_' + after).after($('#taskrow_'+id));
        }
    }
    else {
        const o = $('#tasklist');
        for (const i in taskOrder) {
            o.append($('#taskrow_' + taskOrder[i]));
        }
    }
};


function prioPopup(act, el, id)
{
    if(act == 0) {
        clearTimeout(objPrio.timer);
        return;
    }
    var offset = $(el).offset();
    $('#priopopup').css({ position: 'absolute', top: offset.top + 1, left: offset.left + 1 });
    objPrio.taskId = id;
    objPrio.el = el;
    objPrio.timer = setTimeout("$('#priopopup').show()", 300);
};

function prioClick(prio, el)
{
    el.blur();
    prio = parseInt(prio);
    $('#priopopup').fadeOut('fast'); //.hide();
    setTaskPrio(objPrio.taskId, prio);
};

function setTaskPrio(id, prio)
{
    _mtt.db.request('setTaskPriority', {id:id, priority:prio});
    taskList[id].prio = prio;
    var $t = $('#taskrow_'+id);
    $t.find('.task-prio').replaceWith(preparePrio(prio, id));
    if (curList.sort != 0 && curList.sort != 100) changeTaskOrder(id);
    $t.effect("highlight", {color:_mtt.theme.editTaskFlashColor}, 'normal');
};

function setSort(v, init)
{
    if (v < 0 || (v > 5 && v < 100) || v > 105) {
        return;
    }
    curList.sort = v;
    loadTasks({saveSort:1});
};


function updateSortUI(v)
{
    $('#listmenucontainer .sort-item').removeClass('mtt-item-checked').children('.mtt-sort-direction').text('');
    if (v == 0 || v == 100) $('#sortByHand').addClass('mtt-item-checked').children('.mtt-sort-direction').text(v==0 ? '↓' : '↑');
    else if(v==1 || v==101) $('#sortByPrio').addClass('mtt-item-checked').children('.mtt-sort-direction').text(v==1 ? '↑' : '↓');
    else if(v==2 || v==102) $('#sortByDueDate').addClass('mtt-item-checked').children('.mtt-sort-direction').text(v==2 ? '↑' : '↓');
    else if(v==3 || v==103) $('#sortByDateCreated').addClass('mtt-item-checked').children('.mtt-sort-direction').text(v==3 ? '↓' : '↑');
    else if(v==4 || v==104) $('#sortByDateModified').addClass('mtt-item-checked').children('.mtt-sort-direction').text(v==4 ? '↓' : '↑');
    else if(v==5 || v==105) $('#sortByTitle').addClass('mtt-item-checked').children('.mtt-sort-direction').text(v==5 ? '↓' : '↑');
    else return;

    curList.sort = v;
    if ( (v == 0 || v == 100) && !flag.readOnly) $("#tasklist").sortable('enable');
    else $("#tasklist").sortable('disable');
};


function changeTaskCnt(task, dir, old)
{
    if(dir > 0) dir = 1;
    else if(dir < 0) dir = -1;
    if(dir == 0 && old != null && task.dueClass != old.dueClass) //on saveTask
    {
        if(old.dueClass != '') taskCnt[old.dueClass]--;
        if(task.dueClass != '') taskCnt[task.dueClass]++;
    }
    else if(dir == 0 && old == null) //on comleteTask
    {
        if(!curList.showCompl && task.compl) taskCnt.total--;
        if(task.dueClass != '') taskCnt[task.dueClass] += task.compl ? -1 : 1;
    }
    if(dir != 0) {
        if(task.dueClass != '' && !task.compl) taskCnt[task.dueClass] += dir;
        taskCnt.total += dir;
    }
};

function refreshTaskCnt()
{
    $('#cnt_total').text(taskCnt.total);
    $('#cnt_past').text(taskCnt.past);
    $('#cnt_today').text(taskCnt.today);
    $('#cnt_soon').text(taskCnt.soon);
    if(filter.due == '') $('#total').text(taskCnt.total);
    else if(taskCnt[filter.due] != null) $('#total').text(taskCnt[filter.due]);
};


function setTaskview(v)
{
    if(v == 0)
    {
        if(filter.due == '') return;
        $('#taskview .btnstr').text(_mtt.lang.get('tasks'));
        $('#tasklist').removeClass('filter-'+filter.due);
        filter.due = '';
        $('#total').text(taskCnt.total);
    }
    else if(v=='past' || v=='today' || v=='soon')
    {
        if(filter.due == v) return;
        else if(filter.due != '') {
            $('#tasklist').removeClass('filter-'+filter.due);
        }
        $('#tasklist').addClass('filter-'+v);
        $('#taskview .btnstr').text(_mtt.lang.get('f_'+v));
        $('#total').text(taskCnt[v]);
        filter.due = v;
    }
};


function toggleAllNotes(show, event)
{
    for (let id in taskList)
    {
        if (taskList[id].note == '') continue;
        if (show) $('#taskrow_'+id).addClass('task-expanded');
        else $('#taskrow_'+id).removeClass('task-expanded');
    }
    curList.showNotes = show;
    if (_mtt.options.saveShowNotes || (event && (event.metaKey || event.ctrlKey)) ) {
        _mtt.db.request('setShowNotesInList', {list:curList.id, shownotes:show}, function(json){});
    }
};


function tabSelect(elementOrId)
{
    let id;
    if (typeof elementOrId == 'number') id = elementOrId;
    else if(typeof elementOrId == 'string') id = parseInt(elementOrId);
    else {
        id = $(elementOrId).attr('id');
        if (!id) return;
        id = id.split('_', 2)[1];
        if (id === 'all') id = -1;
    }

    if ( !tabLists.exists(id) ) {
        $('#tasks_info .v').text(_mtt.lang.get('listNotFound'))
        $('#tasks_info').show();
        $('.mtt-need-list').addClass('mtt-item-disabled');
        return;
    }
    else {
        $('#tasks_info').hide();
        $('.mtt-need-list').removeClass('mtt-item-disabled');
        $('#mtt').removeClass('no-list-selected');
    }

    var prevList = curList;
    curList = tabLists.get(id);

    $('#lists .mtt-tab-selected').removeClass('mtt-tab-selected');

    if (id == -1) {
        $('#list_all').addClass('mtt-tab-selected').removeClass('mtt-tab-hidden');
        $('#listmenucontainer .mtt-need-real-list').addClass('mtt-item-hidden');
    }
    else {
        $('#list_'+id).addClass('mtt-tab-selected').removeClass('mtt-tab-hidden');
        $('#listmenucontainer .mtt-need-real-list').removeClass('mtt-item-hidden');
    }

    if (prevList.id != id) {
        if (id == -1) $('#mtt').addClass('show-all-tasks');
        else $('#mtt').removeClass('show-all-tasks');
        if (filter.search != '') liveSearchToggle(0, 1);
        mytinytodo.doAction('listSelected', {
            'list': curList,
            'prevList':prevList
        });
    }
    const newTitle = dehtml(curList.name) + ' - ' + _mtt.options.title;
    const isFirstLoad = flag.firstLoad;
    //replaceHistoryState( 'list', { list:id }, _mtt.urlForList(curList), newTitle );
    updateHistoryState( { list:id }, _mtt.urlForList(curList), newTitle );
    if (!flag.readOnly) {
        setLocalStorageItem('lastList', ''+id);
    }

    if (curList.hidden && flag.readOnly != true) {
        curList.hidden = false;
        _mtt.db.request('setHideList', {list:curList.id, hide:0});
    }
    flag.tagsChanged = true;
    cancelTagFilter(0, 1);
    setTaskview(0);

    if (isFirstLoad && filter.search != '') {
        $('#search').val(filter.search);
        $('#search_close').show();
        searchTasks(true);
    }
    else {
        filter.search = '';
        loadTasks({clearTasklist:1});
    }
};



function listMenu(el)
{
    if(!mytinytodo.menus.listMenu) mytinytodo.menus.listMenu = new mttMenu('listmenucontainer', {onclick:listMenuClick, onhover:listMenuHover});
    mytinytodo.menus.listMenu.show(el);
};

function listMenuClick(el, menu)
{
    if(!el.id) return;
    switch(el.id) {
        case 'btnAddList': addList(); break;
        case 'btnRenameList': renameCurList(); break;
        case 'btnDeleteList': deleteCurList(); break;
        case 'btnPublish': publishCurList(); break;
        case 'btnFeedKey': enableFeedKeyInCurList(); break;
        case 'btnShowFeedKey': showFeedKeyInCurList(); break;
        case 'btnHideList': hideList(curList.id); break;
        case 'btnExportCSV': return true;
        case 'btnExportICAL': return true;
        case 'btnRssFeed': return true;
        case 'btnShowCompleted': showCompletedToggle(); break;
        case 'btnClearCompleted': clearCompleted(); break;
        case 'sortByHand': setSort(curList.sort==0 ? 100 : 0); break;
        case 'sortByPrio': setSort(curList.sort==1 ? 101 : 1); break;
        case 'sortByDueDate': setSort(curList.sort==2 ? 102 : 2); break;
        case 'sortByDateCreated': setSort(curList.sort==3 ? 103 : 3); break;
        case 'sortByDateModified': setSort(curList.sort==4 ? 104 : 4); break;
        case 'sortByTitle': setSort(curList.sort==5 ? 105 : 5); break;
    }
    return false;
};

function listMenuHover(el, menu)
{
    if(!el.id) return;
    switch(el.id) {
        case 'btnExportCSV': $('#'+el.id+'>a').attr('href', _mtt.urlForExport('csv')) ; break;
        case 'btnExportICAL': $('#'+el.id+'>a').attr('href', _mtt.urlForExport('ical')) ; break;
        case 'btnRssFeed': $('#'+el.id+'>a').attr('href', _mtt.urlForFeed()) ; break;
    }
}

function deleteTask(id)
{
    mttConfirm( _mtt.lang.get('confirmDelete'), function()
    {
        flag.tagsChanged = true;
        _mtt.db.request('deleteTask', {id:id}, function(json){
            if (!parseInt(json.total)) return;
            var item = json.list[0];
            taskOrder.splice($.inArray(id,taskOrder), 1);
            $('#taskrow_'+id).effect("highlight", {color:_mtt.theme.deleteTaskFlashColor}, 'normal', function(){ $(this).remove() });
            changeTaskCnt(taskList[id], -1);
            refreshTaskCnt();
            delete taskList[id];
        });
    })
    return false;
};

function completeTask(id, ch)
{
    if(!taskList[id]) return; //click on already removed from the list while anim. effect
    var compl = 0;
    if(ch.checked) compl = 1;
    _mtt.db.request('completeTask', {id:id, compl:compl, list:curList.id}, function(json){
        if(!parseInt(json.total)) return;
        var item = json.list[0];
        if(item.compl) $('#taskrow_'+id).addClass('task-completed');
        else $('#taskrow_'+id).removeClass('task-completed');
        taskList[id] = item;
        changeTaskCnt(taskList[id], 0);
        if(item.compl && !curList.showCompl) {
            delete taskList[id];
            taskOrder.splice($.inArray(id,taskOrder), 1);
            $('#taskrow_'+id).fadeOut('normal', function(){ $(this).remove() });
        }
        else if(curList.showCompl) {
            $('#taskrow_'+item.id).replaceWith(_mtt.prepareTaskStr(taskList[id]));
            $('#taskrow_'+id).fadeOut('fast', function(){
                changeTaskOrder(id);
                $(this).effect("highlight", {color:_mtt.theme.editTaskFlashColor}, 'normal', function(){$(this).css('display','')});
            });
        }
        refreshTaskCnt();
    });
    return false;
};

function toggleTaskNote(id)
{
    var aArea = '#tasknotearea'+id;
    if($(aArea).css('display') == 'none')
    {
        $('#notetext'+id).val(taskList[id].noteText);
        $(aArea).show();
        $('#tasknote'+id).hide();
        $('#taskrow_'+id).addClass('task-expanded');
        $('#notetext'+id).focus();
    } else {
        cancelTaskNote(id)
    }
    return false;
};

function cancelTaskNote(id)
{
    if(taskList[id].note == '') $('#taskrow_'+id).removeClass('task-expanded');
    $('#tasknotearea'+id).hide();
    $('#tasknote'+id).show();
    return false;
};

function saveTaskNote(id)
{
    _mtt.db.request('editNote', {id:id, note:$('#notetext'+id).val()}, function(json){
        if(!parseInt(json.total)) return;
        var item = json.list[0];
        taskList[id].note = item.note;
        taskList[id].noteText = item.noteText;
        $('#tasknote'+id).html(prepareTaskNoteInlineHtml(item.note, item.noteText));
        if(item.note == '') $('#taskrow_'+id).removeClass('task-has-note task-expanded');
        else $('#taskrow_'+id).addClass('task-has-note task-expanded');
        cancelTaskNote(id);
    });
    return false;
};

function fillTaskViewer(id)
{
    const item = taskList[id];
    if (!item) return false;
    $('#page_taskviewer').attr('data-id', item.id);
    $('#taskviewer_id').text('#' + item.id);
    $('#page_taskviewer .title').html(item.title);
    $('#page_taskviewer .note').html(item.note);
    $('#page_taskviewer .prio .content').html(preparePrio(item.prio,item.id));
    $('#page_taskviewer .due .content').html(item.duedate);
    $('#page_taskviewer .tags .content').html(prepareTagsStr(item, ''));
    $('#page_taskviewer .list .content').text(curList.id == -1 ? item.listName : curList.name);
    if (item.note == '') {
        $('#page_taskviewer').addClass('no-note');
    }
    else {
        $('#page_taskviewer').removeClass('no-note');
    }
    return item;
}

function viewTask(id)
{
    const item = fillTaskViewer(id);
    if (!item) return;
    _mtt.pageSet('taskviewer');
    updateHistoryState({ task: item.id, list: item.listId }, '#task/'+item.id, dehtml(item.title) + ' - ' + dehtml(curList.name) + ' - ' + _mtt.options.title);
}


function editTask(id)
{
    var item = taskList[id];
    if(!item) return false;
    // no need to clear form
    var form = document.getElementById('taskedit_form');
    form.task.value = item.titleText;
    form.note.value = item.noteText;
    form.id.value = item.id;
    form.tags.value = dehtml(item.tags).split(',').join(', ');
    form.duedate.value = item.duedate;
    form.prio.value = item.prio;
    $('#taskedit_id').text('#' + item.id);
    $('#taskedit_info .date-created-value').text(item.date).attr('title', item.dateFull);;
    if (item.isEdited && !item.compl) {
        $('#taskedit_info .date-edited-value').text(item.dateEdited).attr('title', item.dateEditedFull);
        $('#taskedit_info .date-edited').show()
    }
    else {
        $('#taskedit_info .date-edited').hide();
    }
    if (item.compl) {
        $('#taskedit_info .date-completed-value').text(item.dateCompleted).attr('title', item.dateCompletedFull);;
        $('#taskedit_info .date-completed').show()
    }
    else {
        $('#taskedit_info .date-completed').hide();
    }
    toggleEditAllTags(0);
    showEditForm();
    return false;
};

function clearEditForm()
{
    var form = document.getElementById('taskedit_form');
    form.task.value = '';
    form.note.value = '';
    form.tags.value = '';
    form.duedate.value = '';
    form.prio.value = '0';
    form.id.value = '';
    toggleEditAllTags(0);
};

function showEditForm(isAdd)
{
    let form = document.getElementById('taskedit_form');
    if (isAdd)
    {
        clearEditForm();
        $('#page_taskedit').removeClass('mtt-inedit').addClass('mtt-inadd');
        form.isadd.value = 1;
        if (_mtt.options.autotag) form.tags.value = _mtt.filter.getTags();
        if ($('#task').val() != '')
        {
            _mtt.db.request('parseTaskStr', { list:curList.id, title:$('#task').val(), tag:_mtt.filter.getTags() }, function(json){
                if(!json) return;
                form.task.value = json.title
                form.tags.value = (form.tags.value != '') ? form.tags.value +', '+ json.tags : json.tags;
                form.prio.value = json.prio;
                form.duedate.value = json.duedate;
                $('#task').val('');

            });
        }
    }
    else {
        $('#page_taskedit').removeClass('mtt-inadd').addClass('mtt-inedit');
        form.isadd.value = 0;
    }
    $(document).on('keydown.mttback', function(event) {
        if (event.keyCode == 27) { //Esc pressed
            _mtt.pageBack(true);
        }
    });

    flag.editFormChanged = false;
    _mtt.pageSet('taskedit');
};

function saveTask(form)
{
    $("#edittags").autocomplete('close');
    if (flag.readOnly)
        return false;
    if (form.isadd.value != 0)
        return submitFullTask(form);

    let duedate = $("#duedate").datepicker('getDate');
    if (duedate) {
        duedate = duedate.getFullYear() + '-' + (duedate.getMonth() + 1) + '-' + duedate.getDate();
    }

    _mtt.db.request('editTask', {id:form.id.value, title: form.task.value, note:form.note.value,
        prio:form.prio.value, tags:form.tags.value, duedate:duedate},
        function(json) {
            if (!parseInt(json.total))
                return;
            const item = json.list[0];
            changeTaskCnt(item, 0, taskList[item.id]);
            taskList[item.id] = item;
            const noteExpanded = (item.note != '' && $('#taskrow_'+item.id).is('.task-expanded')) ? 1 : 0;
            $('#taskrow_'+item.id).replaceWith(_mtt.prepareTaskStr(item, noteExpanded));
            if (curList.sort != 0 && curList.sort != 100) {
                changeTaskOrder(item.id);
            }
            refreshTaskCnt();
            _mtt.pageBack(); //back to list or viewer
            if (_mtt.pages.current.page == 'taskviewer') {
                fillTaskViewer(item.id);
            }
            else {
                $('#taskrow_'+item.id).effect("highlight", {color:_mtt.theme.editTaskFlashColor}, 'normal', function(){$(this).css('display','')});
            }

    });
    flag.tagsChanged = true;
    return false;
};

function toggleEditAllTags(show)
{
    if (show)
    {
        if (curList.id == -1) {
            const taskId = document.getElementById('taskedit_form').id.value;
            loadTags(taskList[taskId].listId, fillEditAllTags);
        }
        else if (flag.tagsChanged)
            loadTags(curList.id, fillEditAllTags);
        else
            fillEditAllTags();
        showhide($('#alltags_hide'), $('#alltags_show'));
    }
    else {
        $('#alltags').hide();
        showhide($('#alltags_show'), $('#alltags_hide'))
    }
};

function fillEditAllTags()
{
    const a = [];
    tagsList.forEach( (item) => {
        a.push('<span class="tag" data-tag="' + item.tag +'">' + item.tag + '</span>');
    });
    const content = (a.length == 0)  ?  _mtt.lang.get('noTags')  :  a.join('');
    $('#alltags').html(content);
    $('#alltags').show();
};

function addEditTag(tag)
{
    var v = $('#edittags').val();
    if(v == '') {
        $('#edittags').val(tag);
        return;
    }
    var r = v.search(new RegExp('(^|,)\\s*'+tag+'\\s*(,|$)'));
    if(r < 0) $('#edittags').val(v+', '+tag);
};

function loadTags(listId, callback)
{
    if (flag.showTagsFromAllLists) listId = -1;
    _mtt.db.request('tagCloud', {list:listId}, function(json){
        if (!parseInt(json.total)) tagsList = [];
        else tagsList = json.items;
        flag.tagsChanged = false;
        _mtt.doAction('tagsLoaded', tagsList);
        setTagcloudContent(tagsList);
        callback();
    });
};

function setTagcloudContent(tags, isFiltered = false)
{
    let cloud = '';
    tags.forEach( item => {
        // item.tag is escaped with htmlspecialchars()
        cloud += ` <span class="tag" data-tag="${item.tag}" data-tag-id="${item.id}">${item.tag}</span>`;
    });
    if (cloud == '') {
        cloud = _mtt.lang.get('noTags');
    }
    else if (!isFiltered) {
        cloud = `<span class="tag special-no-tags" data-tag="^"  data-tag-id="-1">${_mtt.lang.get('withoutTags')}</span>` +
                `<span class="tag special-any-tag" data-tag="^^" data-tag-id="-2">${_mtt.lang.get('withAnyTag')}</span>` + cloud;
    }
    $('#tagcloudcontent').html(cloud)
}

function cancelTagFilter(tagId, dontLoadTasks)
{
    if(tagId)  _mtt.filter.cancelTag(tagId);
    else _mtt.filter.clear();
    if(dontLoadTasks==null || !dontLoadTasks) loadTasks();
};

function addFilterTag(tag, tagId, exclude)
{
    if (!_mtt.filter.addTag(tagId, tag, exclude))
        return false;
    loadTasks();
};

function searchTags()
{
    const filter = document.getElementById('tagcloudSearch').value.toLocaleLowerCase();
    if (filter === '') {
        setTagcloudContent(tagsList);
        return;
    }
    const filtered = [];
    tagsList.forEach( item => {
        if (item.tagText.toLocaleLowerCase().search(filter) === -1)
            return;
        filtered.push(item);
    });
    setTagcloudContent(filtered, true);
}

function liveSearchToggle(toSearch, dontLoad)
{
    if(toSearch)
    {
        $('#search').focus();
    }
    else
    {
        if($('#search').val() != '') {
            filter.search = '';
            $('#search').val('');
            $('#searchbarkeyword').text('');
            $('#searchbar').hide();
            $('#search_close').hide();
            if(!dontLoad) loadTasks();
        }

        $('#search').blur();
    }
};

function searchTasks(force)
{
    var newkeyword = $('#search').val();
    if(newkeyword == filter.search && !force) return false;
    filter.search = newkeyword;
    if (filter.search != '') {
        $('#searchbarkeyword').text(filter.search);
        $('#searchbar').fadeIn('fast');
    }
    else $('#searchbar').fadeOut('fast');
    loadTasks();
    return false;
};


function submitFullTask(form)
{
    if(flag.readOnly) return false;

    let duedate = $("#duedate").datepicker('getDate');
    if (duedate) {
        duedate = duedate.getFullYear() + '-' + (duedate.getMonth() + 1) + '-' + duedate.getDate();
    }

    _mtt.db.request( 'fullNewTask',
        {
            list: curList.id,
            tag: _mtt.filter.getTags(),
            title: form.task.value,
            note: form.note.value,
            prio: form.prio.value,
            tags: form.tags.value,
            duedate: duedate
        },
        function(json) {
            if (!parseInt(json.total)) return;
            form.task.value = '';
            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();
            $('#taskrow_'+item.id).effect("highlight", {color:_mtt.theme.newTaskFlashColor}, 2000);
            changeTaskCnt(item, 1);
            refreshTaskCnt();
        }
    );

    flag.tagsChanged = true;
    return false;
};


function tasklistSortStart(event, ui)
{
    // remember initial order before sorting
    sortOrder = $(this).sortable('toArray');
};

function tasklistSortUpdated(event, ui)
{
    if (!ui.item[0]) {
        return;
    }
    const itemId = ui.item[0].id;
    const n = $(this).sortable('toArray');

    // remove possible empty id's
    for (let i = 0; i < sortOrder.length; i++) {
        if (sortOrder[i] == '') {
            sortOrder.splice(i,1); i--;
        }
    }
    if (n.toString() == sortOrder.toString()) {
        return;
    }

    // make index: id=>position
    const posBefore = {};
    for (let j = 0; j < sortOrder.length; j++) {
        posBefore[sortOrder[j]] = j;
    }
    const posAfter = {};
    for (let j = 0; j < n.length; j++) {
        posAfter[n[j]] = j;
        taskOrder[j] = parseInt(n[j].split('_')[1]);
    }

    // prepare params
    const o = [];
    const newWeight = taskList[sortOrder[posAfter[itemId]].split('_')[1]].ow;
    let diff;
    for (const j in posBefore)
    {
        diff = posAfter[j] - posBefore[j]; // depends on position
        if (curList.sort == 100) {
            diff *= -1;
        }
        if (diff != 0) {
            const taskId = j.split('_')[1];
            if (j == itemId) {
                diff = newWeight - taskList[taskId].ow; // just for new weight
            }
            o.push({id:taskId, diff:diff});
            taskList[taskId].ow += diff;
        }
    }

    _mtt.db.request('changeOrder', {order:o});
};


function mttMenu(container, options)
{
    var menu = this;
    this.container = document.getElementById(container);
    this.$container = $(this.container);
    this.isOpen = false;
    this.options = options || {};
    this.submenu = [];
    this.curSubmenu = null;
    this.showTimer = null;
    this.ts = (new Date).getTime();
    this.container.mttmenu = this.ts;

    if (!this.options.hasOwnProperty('isRTL')) {
        this.options.isRTL = ($('body').css('direction') == 'rtl') ? true : false;
    }
    if (!this.options.hasOwnProperty('alignRight')) {
        this.options.alignRight = false;
    }
    if (!this.options.hasOwnProperty('adjustWidth')) {
        this.options.adjustWidth = true;
    }

    this.$container.find('li').click(function(){
        var r = menu.onclick(this, menu);
        return (typeof r === 'undefined') ? false : r;
    })
    .each(function(){

        var submenu = 0;
        if($(this).is('.mtt-menu-indicator'))
        {
            submenu = new mttMenu($(this).attr('submenu'), menu.options);
            submenu.$caller = $(this);
            submenu.parent = menu;
            if(menu.root) submenu.root = menu.root;  //!! be careful with circular references
            else submenu.root = menu;
            menu.submenu.push(submenu);
            submenu.ts = submenu.container.mttmenu = submenu.root.ts;
        }

        $(this).hover(
            function(){
                if(!$(this).is('.mtt-menu-item-active')) menu.$container.find('li').removeClass('mtt-menu-item-active');
                clearTimeout(menu.showTimer);
                if(menu.hideTimer && menu.parent) {
                    clearTimeout(menu.hideTimer);
                    menu.hideTimer = null;
                    menu.$caller.addClass('mtt-menu-item-active');
                    clearTimeout(menu.parent.showTimer);
                }

                if(menu.curSubmenu && menu.curSubmenu.isOpen && menu.curSubmenu != submenu && !menu.curSubmenu.hideTimer)
                {
                    menu.$container.find('li').removeClass('mtt-menu-item-active');
                    var curSubmenu = menu.curSubmenu;
                    curSubmenu.hideTimer = setTimeout(function(){
                        curSubmenu.hide();
                        curSubmenu.hideTimer = null;
                    }, 300);
                }

                if (menu.options.onhover) menu.options.onhover(this, menu);

                if(!submenu || menu.curSubmenu == submenu && menu.curSubmenu.isOpen)
                    return;

                menu.showTimer = setTimeout(function(){
                    menu.curSubmenu = submenu;
                    submenu.showSub();
                }, 400);
            },
            function(){}
        );

    });

    this.onclick = function(item, fromMenu)
    {
        if ($(item).is('.mtt-item-disabled,.mtt-menu-indicator,.mtt-item-hidden')) return;
        var r = undefined;
        if (this.options.onclick) r = this.options.onclick(item, fromMenu);
        if (menu.root) menu.root.close();
        else menu.close();
        return r;
    };

    this.hide = function()
    {
        for(var i in this.submenu) this.submenu[i].hide();
        clearTimeout(this.showTimer);
        this.$container.hide();
        this.$container.find('li').removeClass('mtt-menu-item-active');
        this.isOpen = false;
    };

    this.close = function(event)
    {
        if(!this.isOpen) return;
        if(event)
        {
            // ignore if event (click) was on caller or container
            var t = event.target;
            if(t == this.caller || (t.mttmenu && t.mttmenu == this.ts)) return;
            while(t.parentNode) {
                if(t.parentNode == this.caller || (t.mttmenu && t.mttmenu == this.ts)) return;
                t = t.parentNode;
            }
        }
        this.hide();
        $(this.caller).removeClass('mtt-menu-button-active');
        $(document).off('mousedown.mttmenu');
        $(document).off('keydown.mttmenu');

        // onClose trigger
        if(this.options.onClose && this.options.onClose.call) {
            this.options.onClose.call(this);
        }
    };

    this.show = function(caller)
    {
        if(this.isOpen)
        {
            this.close();
            if(this.caller && this.caller == caller) return;
        }
        $(document).triggerHandler('mousedown.mttmenu'); //close any other open menu
        $(document).on('keydown.mttmenu', function(event) {
            if (event.keyCode == 27) {
                menu.close(); //close the menu on Esc pressed
            }
        });
        this.caller = caller;
        var $caller = $(caller);

        // beforeShow trigger
        if(this.options.beforeShow && this.options.beforeShow.call) {
            this.options.beforeShow.call(this);
        }

        // adjust width
        if (this.options.adjustWidth) {
            this.$container.width('');
            this.$container.removeClass('mtt-left-adjusted mtt-right-adjusted');
            if ( this.$container.outerWidth(true) > $(window).width() ) {
                this.$container.addClass('mtt-left-adjusted mtt-right-adjusted');
                this.$container.width( $(window).width() - (this.$container.outerWidth(true) - this.$container.width()) );
            }
        }

        //round the width to avoid overflow issues
        this.$container.width( Math.ceil(this.$container.width()) );

        $caller.addClass('mtt-menu-button-active');
        var offset = $caller.offset();
        var containerWidth = this.$container.outerWidth(true);
        var alignRight = this.options.isRTL ^ this.options.alignRight; //alignRight is not for submenu

        var x2 = $(window).width() + $(document).scrollLeft() - containerWidth - 1; // TODO: rtl?
        var x = alignRight ? offset.left + $caller.outerWidth() - containerWidth : offset.left;

        if (x > x2) {
            x = x2; //move left if container overflows right edge
            this.$container.addClass('mtt-right-adjusted');
        }
        if (x < 0) {
            x = 0; //do not cross left edge
            this.$container.addClass('mtt-left-adjusted');
        }

        var y = offset.top + caller.offsetHeight - 1;
        if(y + this.$container.outerHeight(true) > $(window).height() + $(document).scrollTop()) y = offset.top - this.$container.outerHeight();
        if(y<0) y=0;

        this.$container.css({ position: 'absolute', top: y, left: x, width:this.$container.width() /*, 'min-width': $caller.width()*/ }).show();
        var menu = this;
        $(document).on('mousedown.mttmenu', function(e) { menu.close(e) });
        this.isOpen = true;
    };

    this.showSub = function()
    {
        // adjust width
        if (this.options.adjustWidth) {
            this.$container.width('');
            this.$container.removeClass('mtt-left-adjusted mtt-right-adjusted');
            if ( this.$container.outerWidth(true) > $(window).width() ) {
                this.$container.addClass('mtt-left-adjusted mtt-right-adjusted');
                this.$container.width( $(window).width() - (this.$container.outerWidth(true) - this.$container.width()) );
            }
        }

        //round the width to avoid overflow issues
        this.$container.width( Math.ceil(this.$container.width()) );

        this.$caller.addClass('mtt-menu-item-active');
        var offset = this.$caller.offset();
        var containerWidth = this.$container.outerWidth(true);

        var x = 0;
        if (this.options.isRTL) {
            x = offset.left - containerWidth - 1;
            if (x < 0) {
                x = offset.left + this.$caller.outerWidth();
            }
            if ( x + containerWidth > $(window).width() + $(document).scrollLeft() ) {
                x = $(window).width() + $(document).scrollLeft() - containerWidth; // TODO: rtl?
                this.$container.addClass('mtt-right-adjusted');
            }
        }
        else {
            x = offset.left + this.$caller.outerWidth();
            if ( x + containerWidth > $(window).width() + $(document).scrollLeft() ) { // TODO: rtl?
                x = offset.left - containerWidth - 1;
            }
            if (x < 0) {
                x = 0;
                this.$container.addClass('mtt-left-adjusted');
            }
        }

        var y = offset.top + this.parent.$container.offset().top-this.parent.$container.find('li:first').offset().top;
        if(y +  this.$container.outerHeight(true) > $(window).height() + $(document).scrollTop()) y = $(window).height() + $(document).scrollTop()- this.$container.outerHeight(true) - 1;
        if(y<0) y=0;

        this.$container.css({ position: 'absolute', top: y, left: x, width:this.$container.width() /*, 'min-width': this.$caller.outerWidth()*/ }).show();
        this.isOpen = true;
    };

    this.destroy = function()
    {
        for(var i in this.submenu) {
            this.submenu[i].destroy();
            delete this.submenu[i];
        }
        this.$container.find('li').unbind(); //'click mouseenter mouseleave'
    };
};


function taskContextMenu(el, id)
{
    if(!_mtt.menus.cmenu) _mtt.menus.cmenu = new mttMenu('taskcontextcontainer', {
        onclick: taskContextClick,
        beforeShow: function() {
            var taskId = this.tag;
            $('#taskrow_'+taskId).addClass('menu-active');
            $('#cmenupriocontainer li').removeClass('mtt-item-checked');
            $('#cmenu_prio\\:'+ taskList[taskId].prio).addClass('mtt-item-checked');
        },
        onClose: function() {
            $('#tasklist li').removeClass('menu-active');
        },
        alignRight: true
    });
    _mtt.menus.cmenu.tag = id;
    _mtt.menus.cmenu.show(el);
};

function taskContextClick(el, menu)
{
    if(!el.id) return;
    var taskId = parseInt(_mtt.menus.cmenu.tag);
    var id = el.id, value;
    var a = id.split(':');
    if(a.length == 2) {
        id = a[0];
        value = a[1];
    }
    switch(id) {
        case 'cmenu_edit': editTask(taskId); break;
        /*case 'cmenu_note': toggleTaskNote(taskId); break;*/
        case 'cmenu_delete': deleteTask(taskId); break;
        case 'cmenu_prio': setTaskPrio(taskId, parseInt(value)); break;
        case 'cmenu_list':
            if(menu.$caller && menu.$caller.attr('id')=='cmenu_move') moveTaskToList(taskId, value);
            break;
    }
};


function moveTaskToList(taskId, listId)
{
    if(curList.id == listId) return;
    _mtt.db.request('moveTask', {id:taskId, from:curList.id, to:listId}, function(json){
        if(!parseInt(json.total)) return;
        if(curList.id == -1)
        {
            // leave the task in current tab (all tasks tab)
            var item = json.list[0];
            changeTaskCnt(item, 0, taskList[item.id]);
            taskList[item.id] = item;
            var noteExpanded = (item.note != '' && $('#taskrow_'+item.id).is('.task-expanded')) ? 1 : 0;
            $('#taskrow_'+item.id).replaceWith(_mtt.prepareTaskStr(item, noteExpanded));
            if (curList.sort != 0 && curList.sort != 100) {
                changeTaskOrder(item.id);
            }
            refreshTaskCnt();
            $('#taskrow_'+item.id).effect("highlight", {color:_mtt.theme.editTaskFlashColor}, 'normal', function(){$(this).css('display','')});
        }
        else {
            // remove the task from currrent tab
            changeTaskCnt(taskList[taskId], -1)
            delete taskList[taskId];
            taskOrder.splice($.inArray(taskId,taskOrder), 1);
            $('#taskrow_'+taskId).fadeOut('normal', function(){ $(this).remove() });
            refreshTaskCnt();
        }
    });

    flag.tagsChanged = true;
};


function cmenuOnListsLoaded()
{
    if(_mtt.menus.cmenu) _mtt.menus.cmenu.destroy();
    _mtt.menus.cmenu = null;
    var s = '';
    var all = tabLists.getAll();
    for(var i in all) {
        s += '<li id="cmenu_list:'+all[i].id+'" class="'+(all[i].hidden?'mtt-list-hidden':'')+'">'+all[i].name+'</li>';
    }
    $('#cmenulistscontainer ul').html(s);
};

function cmenuOnListAdded(list)
{
    if(_mtt.menus.cmenu) _mtt.menus.cmenu.destroy();
    _mtt.menus.cmenu = null;
    $('#cmenulistscontainer ul').append('<li id="cmenu_list:'+list.id+'">'+list.name+'</li>');
};

function cmenuOnListRenamed(list)
{
    $('#cmenu_list\\:'+list.id).text(list.name);
};

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');
};

function cmenuOnListOrderChanged()
{
    cmenuOnListsLoaded();
    $('#cmenu_list\\:'+curList.id).addClass('mtt-item-disabled');
};

function cmenuOnListHidden(list)
{
    if (list.id == -1) return;
    $('#cmenu_list\\:'+list.id).addClass('mtt-list-hidden');
};


function tabmenuOnListSelected(a)
{
    const list = a.list;
    if (list.published) {
        $('#btnPublish').addClass('mtt-item-checked');
        $('#btnRssFeed').removeClass('mtt-item-disabled');
    }
    else {
        $('#btnPublish').removeClass('mtt-item-checked');
        $('#btnRssFeed').addClass('mtt-item-disabled');
    }
    if (list.showCompl) {
        $('#btnShowCompleted').addClass('mtt-item-checked');
    }
    else {
        $('#btnShowCompleted').removeClass('mtt-item-checked');
    }
    if (list.feedKey !== undefined && list.feedKey !== '') {
        $('#btnFeedKey').addClass('mtt-item-checked');
        $('#btnShowFeedKey').removeClass('mtt-item-disabled');
    }
    else {
        $('#btnFeedKey').removeClass('mtt-item-checked');
        $('#btnShowFeedKey').addClass('mtt-item-disabled');
    }
};


function listOrderChanged(event, ui)
{
    var a = $(this).sortable("toArray");
    var order = [];
    for(var i in a) {
        order.push(a[i].split('_')[1]);
    }
    tabLists.reorder(order);
    _mtt.db.request('changeListOrder', {order:order});
    _mtt.doAction('listOrderChanged', {order:order});
};

function showCompletedToggle()
{
    var act = curList.showCompl ? 0 : 1;
    curList.showCompl = tabLists.get(curList.id).showCompl = act;
    if(act) $('#btnShowCompleted').addClass('mtt-item-checked');
    else $('#btnShowCompleted').removeClass('mtt-item-checked');
    loadTasks({setCompl:1});
};

function clearCompleted()
{
    if (!curList) return false;
    mttConfirm( _mtt.lang.get('clearCompleted'), function()
    {
        _mtt.db.request('clearCompletedInList', {list:curList.id}, function(json){
            if(!parseInt(json.total)) return;
            flag.tagsChanged = true;
            if(curList.showCompl) loadTasks();
        });
    });
};

function showhide(a,b)
{
    a.show();
    b.hide();
};

function findParentNode(el, node)
{
    // in html nodename is in uppercase, in xhtml nodename in in lowercase
    if (el.nodeName.toUpperCase() == node) return el;
    while (el.parentNode) {
        el = el.parentNode;
        if (el.nodeName.toUpperCase() == node) return el;
    }
    return null;
};

function getLiTaskId(el)
{
    var li = findParentNode(el, 'LI');
    if(!li || !li.id) return 0;
    return li.id.split('_',2)[1];
};

function isParentId(el, id)
{
    if(el.id && $.inArray(el.id, id) != -1) return true;
    if(!el.parentNode) return null;
    return isParentId(el.parentNode, id);
};

function dehtml(str)
{
    return str.replace(/&quot;/g, '"').replace(/&#039;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
};

function escapeHtml(str) {
    const map = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#039;'
    };
    return str.replace(/[&<>"']/g, (m) => map[m]);
}


function slmenuOnListsLoaded()
{
    if (_mtt.menus.selectlist) {
        _mtt.menus.selectlist.destroy();
        _mtt.menus.selectlist = null;
    }

    let s = '';
    tabLists.getAll().forEach( (list) => {
        const classChecked = (list.id == curList.id) ? 'mtt-item-checked' : '';
        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>`;
    })
    $('#slmenucontainer ul>.slmenu-lists-begin').nextAll().remove();
    $('#slmenucontainer ul>.slmenu-lists-begin').after(s);
};

function slmenuOnListRenamed(list)
{
    $('#slmenucontainer li.list-id-'+list.id).find('a').html(list.name);
};

function slmenuOnListAdded(list)
{
    if(_mtt.menus.selectlist) {
        _mtt.menus.selectlist.destroy();
        _mtt.menus.selectlist = null;
    }
    $('#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><div class="counter hidden"></div></li>`);
};

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');

};

function slmenuOnListHidden(list)
{
    if (list.id == -1) return;
    $('#slmenucontainer li.list-id-'+list.id).addClass('mtt-list-hidden');
};

function slmenuSelect(el, menu)
{
    if(!el.id) return;
    var id = el.id, value;
    var a = id.split(':');
    if(a.length == 2) {
        id = a[0];
        value = a[1];
    }
    if(id == 'slmenu_list') {
        tabSelect(parseInt(value));
    }
    return false;
};

function hideList(listId)
{
    if (typeof listId === 'string') {
        listId = parseInt(listId);
    }
    else if (typeof listId !== 'number') {
        return;
    }

    if(!tabLists.get(listId)) return false;

    // if we hide current tab
    var listIdToSelect = 0;
    if(curList.id == listId) {
        var all = tabLists.getAll();
        for(var i in all) {
            if(all[i].id != curList.id && !all[i].hidden) {
                listIdToSelect = all[i].id;
                break;
            }
        }
        // do not hide the tab if others are hidden
        if(!listIdToSelect) return false;
    }

    if(listId == -1) {
        $('#list_all').addClass('mtt-tab-hidden').removeClass('mtt-tab-selected');
    }
    else {
        $('#list_'+listId).addClass('mtt-tab-hidden').removeClass('mtt-tab-selected');
    }

    tabLists.get(listId).hidden = true;

    _mtt.db.request('setHideList', {list:listId, hide:1});
    _mtt.doAction('listHidden', tabLists.get(listId));

    if(listIdToSelect) {
        tabSelect(listIdToSelect);
    }
}

function getLocalStorageItem(key)
{
    try {
        return localStorage.getItem(key);
    }
    catch (e) {
        console.log(e);
    }
    return null;
}

function setLocalStorageItem(key, value)
{
    try {
        localStorage.setItem(key, value);
    }
    catch (e) {
        console.log(e);
    }
}

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,
        showCompl: curList.showCompl,
        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
        });
    });

    _mtt.db.request("newTaskCounter", params, json => {
        if (json && json.ok) {
            let counters = {};
            let curCounter = 0;
            if (Array.isArray(json.tasks)) {
                json.tasks.forEach((id) => {
                    if (!taskList[id]) {
                        curCounter++;
                    }
                });
            }
            counters[curList.id] = curCounter;

            if (Array.isArray(json.lists)) {
                json.lists.forEach((item) => {
                    counters["" + item.listId] = +item.counter;
                });
            }

            tabLists.getAll().forEach( (list) => {
                if (!list.hidden || list.id != -1) {
                    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');
        $('#slmenucontainer li.list-id-' + listId).find('.counter').text(counter).removeClass('hidden');
        list.newTaskCounter = counter;
    } else {
        $('#list_' + listId).find('.counter').text('').addClass('hidden');
        $('#slmenucontainer li.list-id-' + 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
*/

function flashError(str, details)
{
    if (details === undefined) details = '';
    $("#msg>.msg-text").text(dehtml(str))
    $("#msg>.msg-details").text(dehtml(details));
    $("#loading").hide();
    $("#msg").addClass('mtt-error').effect("highlight", {color:_mtt.theme.msgFlashColor}, 700);
}

function flashInfo(str, details)
{
    if (details === undefined) details = '';
    $("#msg>.msg-text").text(dehtml(str))
    $("#msg>.msg-details").text(dehtml(details));
    $("#loading").hide();
    $("#msg").addClass('mtt-info').effect("highlight", {color:_mtt.theme.msgFlashColor}, 700);
}

function hideAlert()
{
    $("#msg>.msg-text").text('');
    $("#msg>.msg-details").text('');
    $("#msg").hide().removeClass('mtt-error mtt-info').find('.msg-details').hide();
}


/*
    Authorization
*/
function updateAccessStatus()
{
    // flag.needAuth is not changed after pageload
    if(flag.needAuth)
    {
        if (flag.isLogged) {
            showhide( $("#logout_btn"), $("#login_btn") );
        }
        else {
            showhide( $("#login_btn"), $("#logout_btn") );
        }
    }
    else {
        $('#mtt').addClass('no-need-auth');
    }
    if(flag.needAuth && !flag.isLogged) {
        flag.readOnly = true;
        $("#bar_public").show();
        $('#mtt').addClass('readonly')
        liveSearchToggle(1);
        // remove some tab menu items
        $('#btnRenameList,#btnDeleteList,#btnClearCompleted,#btnPublish').remove();
    }
    else {
        flag.readOnly = false;
        $('#mtt').removeClass('readonly')
        $("#bar_public").hide();
        liveSearchToggle(0);
    }
    $('#page_ajax').hide();
}

function showLogin()
{
    if (_mtt.pages.current && _mtt.pages.current.page == 'login') {
        return false;
    }
    _mtt.pageSet('login', '');
    $('#password').val('').focus();
}

function doAuth(form)
{
    _mtt.db.request( 'login', { password: form.password.value }, function(json) {
        form.password.value = '';
        if(json.logged)
        {
            flag.isLogged = true;
            window.location.hash = '';
            window.location.reload();
        }
        else {
            flashError(_mtt.lang.get('invalidpass'));
            $('#password').focus();
        }
    });
}

function logout()
{
    _mtt.db.request( 'logout', {}, function(json) {
        flag.isLogged = false;
        window.location.hash = '';
        window.location.reload();
    });
    return false;
}


/*
    Settings
*/

function showSettings(json = 0)
{
    let reload = false;
    if (_mtt.pages.current && _mtt.pages.current.page == 'ajax' && _mtt.pages.current.pageClass == 'settings') {
        reload = true;
    }
    const jsonParam = (json == 1) ? '&json=1' : '';
    $('#page_ajax').load(_mtt.mttUrl + 'settings.php?ajax=yes' + jsonParam, null, function(){
        if (!reload) {
            _mtt.pageSet('ajax','settings');
            const newTitle = _mtt.lang.get('set_header') + ' - ' + _mtt.options.title;
            updateHistoryState( { settings:1, settingsJson:json }, _mtt.urlForSettings(json), newTitle );
            _mtt.doAction('settingsLoaded');
        }
    })
}

function saveSettings(frm)
{
    if(!frm) return false;
    var params = { save:'ajax' };
    $(frm).find("input:hidden,input:text,input:password,input:checked,select,textarea").filter(":enabled").each(function() { params[this.name || '__'] = this.value; });
    $(frm).find(":submit").attr('disabled','disabled').blur();
    $.post(_mtt.mttUrl+'settings.php', params, function(json){
        if(json.saved) {
            flashInfo(_mtt.lang.get('settingsSaved'));
            setTimeout( function(){
                window.location.assign(_mtt.homeUrl); //window.location.reload();
            }, 1000);
        }
    }, 'json');
}

function activateExtension(activate, ext)
{
    var params = {
        'activate': activate ? 1 : 0,
        'ext': ext
    }
    $.post(_mtt.mttUrl+'settings.php', params, function(json){
        if(json.saved) {
            flashInfo(_mtt.lang.get('settingsSaved'));
            showSettings(0);
        }
    }, 'json');
}

function showExtensionSettings(ext, callback, reload)
{
    if (_mtt.pages.current && _mtt.pages.current.page == 'ajax' && _mtt.pages.current.pageClass == 'settings') {
        $('#page_ajax').load(_mtt.apiUrl + 'ext-settings/' + ext, null, function() {
            if (callback) callback();
            if (!reload) {
                _mtt.pageSet('ajax','settings');
                const newTitle = `${ext} - ${_mtt.lang.get('set_header')} - ${_mtt.options.title}`;
                replaceHistoryState('extSettings', { extSettings:true, ext:ext }, _mtt.urlForExtSettings(ext), newTitle );
            }
        });
    }
}

function saveExtensionSettings(frm)
{
    if (!frm) return false;
    var ext = frm.dataset.ext;
    var params = {};
    $(frm).find("input:hidden,input:text,input:password,input:checked,select,textarea").filter(":enabled").each(function() { params[this.name || '__'] = this.value; });
    $.ajax({
        url: _mtt.apiUrl + 'ext-settings/' + ext,
        method: 'PUT',
        contentType : 'application/json',
        data: JSON.stringify(params),
        dataType: 'json',
        success: function(json) {
            if (json.saved) {
                if (json.msg) showExtensionSettings(ext, function(){ flashInfo(json.msg); }, true);
                else showExtensionSettings(ext, null, true);
            }
            else if (json.msg) {
                flashError(json.msg);
            }
        }
    });
}

function extensionSettingsAction(actionString, ext, formData)
{
    if (actionString === undefined || ext === undefined) return false;
    const a = actionString.split(':', 2);
    if (a.length !== 2) return false;
    const method = a[0],
          action = a[1];
    const success = function(json) {
        if (json.total && json.total > 0) {
            if (json.redirect) {
                window.location.assign(json.redirect);
                return;
            }
            if (json.html) {
                $('#page_ajax .mtt-settings-table').html(json.html); //FIXME: maybe whole page?
                return;
            }
            if (json.alertText) {
                mttAlert(json.alertText);
                return;
            }
            const callback = function() {
                if (json.alertTextOnLoad) {
                    mttAlert(json.alertTextOnLoad);
                }
                else if (json.msg) {
                    flashInfo(json.msg, json.details);
                }
                if (json.reload) {
                    setTimeout( function(){
                        //window.location.hash = '';
                        window.location.reload();
                    }, 1000);
                }
            }
            showExtensionSettings(ext, callback, true);
        }
        else if (json.msg) {
            flashInfo(json.msg, json.details);
        }
    };
    if (formData === undefined) {
        $.ajax({
            url: _mtt.apiUrl + 'ext/' + ext + '/' + action,
            method: method.toUpperCase(),
            contentType : 'application/json',
            data: '{}',
            dataType: 'json',
            success: success
        });
    }
    else {
        $.ajax({
            url: _mtt.apiUrl + 'ext/' + ext + '/' + action,
            method: method.toUpperCase(),
            contentType : false,
            data: formData,
            processData: false,
            success: success
        });
    }
}
_mtt.extensionSettingsAction = extensionSettingsAction;

/*
 *  Dialogs
 */

function mttConfirm(msg, callbackOk, callbackCancel)
{
    mttModalDialog('confirm').message(msg).ok(callbackOk).cancel(callbackCancel).show();
}

function mttPrompt(msg, defaultValue, callbackOk, callbackCancel)
{
    mttModalDialog('prompt').message(msg).default(defaultValue).ok(callbackOk).cancel(callbackCancel).show();
}

function mttAlert(msg, callbackOk)
{
    mttModalDialog().ok(callbackOk).message(msg).show();
}

function mttModalDialog(dialogType = 'alert')
{
    if ( ! (this instanceof mttModalDialog) ) return new mttModalDialog(dialogType);
    let dialog = this;
    this.type = dialogType;
    let lastScrollTop = 0;

    this.close = function() {
        //restore scrolling
        $('body').css({
            'position': '',
            'top': ''
        });
        window.scrollTo(window.pageXOffset,  lastScrollTop);
        $("html").removeClass('mtt-modal-dialog-active');
        $("#modal_overlay, #modal").hide();
        $("#btnModalOk").off('click');
        $("#btnModalCancel").off('click');
        $("#modalMessage").text('');
        $("#modalTextInput").val('').off('keyup.mttmodal');
        $(document).off('keydown.mttmodal');
    } ;

    this.ok = function(callback) {
        $("#btnModalOk").on('click', function() {
            const value = $("#modalTextInput").val();
            dialog.close();
            if (typeof callback === 'function')
                callback( dialog.type === 'prompt' ? value : null );
        });
        return dialog;
    };

    this.cancel = function(callback) {
        $("#btnModalCancel").on('click', function() {
            dialog.close();
            if (typeof callback === 'function')
                callback();
        });
        return dialog;
    };

    this.message = function(msg = '') {
        $("#modalMessage").text(msg);
        return dialog;
    };

    this.default = function(value = '') {
        $("#modalTextInput").val(value);
        return dialog;
    }

    this.show = function() {
        let modalOverlay = document.getElementById("modal_overlay");
        if (!modalOverlay) {
            modalOverlay = document.createElement("div");
            modalOverlay.id = "modal_overlay";
            modalOverlay.style.cssText = "position: fixed; z-index: 999; left: 0; top: 0; width: 100%; height: 100%; background-color: black; opacity: 0.6; display:none;";
            document.getElementsByTagName('body')[0].appendChild(modalOverlay);
        }

        if (dialog.type === 'confirm') {
            $("#btnModalCancel").show();
            $("#modalTextInput").hide();
        }
        else if(dialog.type === 'prompt') {
            $("#btnModalCancel").show();
            $("#modalTextInput").show();
            $("#modalTextInput").on("keyup.mttmodal", function(e) {
                if (e.keyCode == 13) {
                    $("#btnModalOk").click();
                }
            });
        }
        else {
            $("#btnModalCancel").hide();
            $("#modalTextInput").hide();
        }

        $(document).on('keydown.mttmodal', function(event) {
            if (event.keyCode == 27) {
                dialog.close();
            }
        });

        //disable background scrolling
        lastScrollTop = window.pageYOffset;
        $('body').css({
            'position': 'fixed',
            'top': `-${lastScrollTop}px`
        })

        $("html").addClass('mtt-modal-dialog-active');
        $("#modal_overlay, #modal").show();
        $("#modalTextInput").focus();
        return dialog;
    };
}

/*
 *  History and Hash change
 */


/**
 * Manipulate browser history manually.
 * //TODO: use window.location and hashchange event ?
 * @param {object} state History Api state data
 * @param {string} url   document url. appended to the state.
 * @param {string} title document title to set. appended to the state.
 */
function updateHistoryState(state, url, title)
{
    if (!_mtt.options.history) {
        document.title = title;
        return;
    }
    if (flag.dontChangeHistoryOnce) {
        flag.dontChangeHistoryOnce = false;
    }
    else {
        if (_mtt.lastHistoryState) {
            //_mtt.lastHistoryState.title = document.title;
            window.history.pushState(_mtt.lastHistoryState, _mtt.lastHistoryState.title, _mtt.lastHistoryState.url);
        }
        state.url = url;
        state.title = title;
        window.history.replaceState(state, title, url); //also refresh visible URL
    }
    _mtt.lastHistoryState = history.state;
    flag.firstLoad = false;
    document.title = title;
}

function replaceHistoryState(param, _state, url, title)
{
    if (!_mtt.options.history) {
        document.title = title;
        return;
    }
    if (flag.dontChangeHistoryOnce) {
        flag.dontChangeHistoryOnce = false;
    }
    const state = history.state;
    if (state && state[param]) {
        _state.url = url;
        _state.title = title;
        history.replaceState(_state, '', url);
        document.title = title;
        _mtt.lastHistoryState = history.state;
    }
    else {
        updateHistoryState(_state, url, title);
    }
}

function historyOnPopState(event)
{
    if (!event.state) return;
    if (event.state.list && _mtt.pages.current &&
        ((_mtt.pages.current.page == 'ajax' && _mtt.pages.current.pageClass == 'settings') || _mtt.pages.current.page == 'taskviewer') ) {
        // Here we go back to tasklist from settings or view task, no reload.
        // Just show and hide pages without history actions.
        _mtt.pageBack();
        flag.dontChangeHistoryOnce = true;
        updateHistoryState( { list:event.state.list }, event.state.url, event.state.title );
    }
    else if (event.state.task) {
        flag.dontChangeHistoryOnce = true;
        viewTask(event.state.task);
    }
    else if (event.state.list) {
        flag.dontChangeHistoryOnce = true;
        tabSelect(event.state.list);
    }
    else if (event.state.settings) {
        _mtt.pageBack(); // will do nothing if back from tasks
        flag.dontChangeHistoryOnce = true;
        _mtt.lastHistoryState = event.state;
        // will pageSet() if back from tasks
        // will not pageSet() if back from extSettings
        showSettings(event.state.settingsJson);
    }
    else if (event.state.extSettings) {
        flag.dontChangeHistoryOnce = true;
        showExtensionSettings(event.state.ext);
    }
    else {
        console.log("unexpected: nothing to pop");
    }
}


})();


================================================
FILE: src/content/mytinytodo_api.js
================================================
/*
    This file is a part of myTinyTodo.
    (C) Copyright 2010,2020-2025 Max Pozdeev <maxpozdeev@gmail.com>
    Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/

"use strict";

/**
 * @class
 */
function MytinytodoAjaxApi(props)
{
    if (typeof mytinytodo !== 'object') {
        throw "mytinytodo global object is not found!";
    }
    this.useREST = true;

    if (props.hasOwnProperty('useREST')) {
        this.useREST = !!props.useREST;
    }
}

MytinytodoAjaxApi.prototype = {

    /**
     * required method
     * @param {string} action
     * @param {Object.<string, any>} [params]
     * @param {ApiDriverCallback} [callback]
     *
     * @callback ApiDriverCallback
     * @param {object} json
     */
    request(action, params, callback) {
        if (typeof this[action] !== 'function') {
            throw "Unknown ApiDriver action: " + action;
        }
        this[action](params, function(json){
            if (json.denied) {
                mytinytodo.errorDenied();
            }
            if (callback) {
                callback.call(mytinytodo, json)
            }
        });
    },


    loadTasks(params, callback) {
        let q = '';
        if (params.search && params.search != '') q += '&s=' + encodeURIComponent(params.search);
        if (params.tag && params.tag != '') q += '&t=' + encodeURIComponent(params.tag);
        if (params.setCompl && params.setCompl != 0) q += '&setCompl=1';
        if (params.saveSort && params.saveSort != 0) q += '&saveSort=1';

        $.getJSON(mytinytodo.apiUrl + 'tasks' + (mytinytodo.apiUrl.indexOf('?') > -1 ? '&' : '?') +
            'list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q,
            callback);
    },


    newTask(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks',
            method: 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'newSimple',
                list: params.list,
                title: params.title,
                tag: params.tag,
            }),
            success: callback,
            dataType: 'json'
        });
    },


    fullNewTask(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks',
            method: 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'newFull',
                list: params.list,
                title: params.title,
                note: params.note,
                prio: params.prio,
                tags: params.tags,
                duedate: params.duedate,
                /* tag: params.tag, // We do not send current tag filter, autotag should set it in the form and include in tags */
            }),
            success: callback,
            dataType: 'json'
        });
    },


    editTask(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'edit',
                title: params.title,
                note: params.note,
                prio: params.prio,
                tags: params.tags,
                duedate: params.duedate,
            }),
            success: callback,
            dataType: 'json'
        });
    },


    editNote(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'note',
                note: params.note
            }),
            success: callback,
            dataType: 'json'
        });
    },


    completeTask(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'complete',
                compl: params.compl
            }),
            success: callback,
            dataType: 'json'
        });
    },


    deleteTask(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
            method: this.useREST ? 'DELETE' : 'POST',
            contentType : 'application/json', // contentType and data are required if method is POST
            data: JSON.stringify({
                action: 'delete',
            }),
            success: callback,
            dataType: 'json'
        });
    },


    setTaskPriority(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'priority',
                prio: params.priority,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    changeOrder(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks',
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'order',
                order:  params.order,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    suggestTags(params, callback) {
        $.getJSON(mytinytodo.apiUrl + 'suggestTags', {list:params.list, q:params.q}, callback);
    },

    tagCloud(params, callback) {
        $.getJSON(mytinytodo.apiUrl + 'tagCloud/' + encodeURIComponent(params.list), callback);
    },

    moveTask(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'move',
                from: params.from,
                to: params.to
            }),
            success: callback,
            dataType: 'json'
        });
    },

    parseTaskStr(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'tasks/parseTitle',
            method: 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                list: params.list,
                title: params.title,
                tag: params.tag ,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    newTaskCounter(params, callback) {
        fetch(mytinytodo.apiUrl + 'tasks/newCounter', {
            method: 'POST',
            credentials: 'same-origin', // old browsers
            headers: {
                'Content-Type': 'application/json',
                'MTT-Token': mytinytodo.options.token,
            },
            body: JSON.stringify(params)
        })
        .then((response) => {
            if (!response.ok) throw response;
            return response.json();
        })
        .catch((e) => {
            if (e instanceof Error) console.log("newTaskCounter fetch error: ", e);
            else console.log("newTaskCounter fetch error, HTTP status code: " + e.status);
        })
        .then(json => {
            callback(json)
        });
    },


    // Lists
    loadLists(params, callback) {
        $.getJSON(mytinytodo.apiUrl + 'lists', callback);
    },

    addList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists',
            method: 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'new',
                name: params.name,
            }),
            success: callback,
            dataType: 'json'
        });

    },

    deleteList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'DELETE' : 'POST',
            contentType : 'application/json', // contentType and data are required if method is POST
            data: JSON.stringify({
                action: 'delete',
            }),
            success: callback,
            dataType: 'json'
        });
    },

    renameList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'rename',
                name: params.name,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    setSort(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'sort',
                name: params.name,
                sort: params.sort
            }),
            success: callback,
            dataType: 'json'
        });
    },

    publishList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'publish',
                publish: params.publish,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    enableFeedKey(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'enableFeedKey',
                enable: params.enable,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    setShowNotesInList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'showNotes',
                shownotes: params.shownotes,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    setHideList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'hide',
                hide: params.hide,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    changeListOrder(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists',
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'order',
                order: params.order
            }),
            success: callback,
            dataType: 'json'
        });
    },

    clearCompletedInList(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
            method: this.useREST ? 'PUT' : 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                action: 'clearCompleted',
            }),
            success: callback,
            dataType: 'json'
        });
    },

    /* Auth */

    login(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'login',
            method: 'POST',
            contentType : 'application/json',
            data: JSON.stringify({
                password: params.password,
            }),
            success: callback,
            dataType: 'json'
        });
    },

    logout(params, callback) {
        $.ajax({
            url: mytinytodo.apiUrl + 'logout',
            method: 'POST',
            success: callback,
            dataType: 'json'
        });
    }

};


================================================
FILE: src/content/theme/dark.css
================================================
/*
  This file is a part of myTinyTodo.
*/


/* Dark mode */

/* prefers-color-scheme media query is used in html link tag */
:root {

    color-scheme: dark;

    --color-bg: #151515;
    --color-text-default: #eee;
    --color-text-reduced: #e0e0e0;
    --color-text-reduced2: #999;
    --color-text-reduced3: #666;
    --color-link: #6495ED; /* CornflowerBlue */
    --color-btn-reduced: #999;
    --color-btn-reduced-hover: #ddd;
    --color-btn-default: #fff;
    --color-btn-hover: #444;
    --color-submit: #444;
    --color-submit-hover: #555;
    --color-row-underlinig: #303030;
    --color-border-default: #555;
    --color-border-focus: #5a8df0;
    --color-error: #ff3333;
    --color-error-bg: var(--color-bg);
    --color-info: #EFC300;
    --color-info-bg: var(--color-bg);
    --color-input-bg: #1e1e1e;

    --color-toolbar: #3b3b3b;
    --color-btn-toolbar-hover: #555;
    --color-content-delimiter: #676767;
    --color-footer: var(--color-bg);

    /* Tabs */
    --color-tab: #1b1b1b;
    --color-tab-selected: var(--color-toolbar);
    --color-tab-hover: #262626;
    --color-tab-border: #676767;
    --color-tab-text: #ddd;
    --color-btn-tab: #ccc;
    --color-btn-tab-hover: #fff;
    --color-btn-tab-hover-bg: #5a5a5a;

    /* Menu */
    --color-menu: #252525;
    --color-menu-border: #555;
    --color-menu-hover: #5a8df0;
    --color-menu-text: #eaeaea;
    --color-menu-text-hover: #ebf0f8;
    --color-menu-text-disabled: #696969;
    --color-popup-shadow: 1px 2px 6px 1px rgba(85,85,85,0.1);

    /* Tasklist */
    --color-tasklist-row: var(--color-bg);
    --color-tasklist-row-border: var(--color-row-underlinig);
    --color-tasklist-row-inter-border: var(--color-tasklist-row-border);
    --color-tasklist-row-expanded-border: #555;
    --color-tasklist-tag: var(--color-tab-text);
    --color-tasklist-note-link: #999;
    --color-tasklist-link-hover: var(--color-link);
    --color-tasklisk-hover-shadow: rgba(255,255,255,0.4);
    --color-taglist-tag: var(--color-text-reduced);
    --color-taglist-tag-bg: #444;
    --color-taglist-tag-hover: var(--color-taglist-tag);
    --color-taglist-tag-hover-bg: var(--color-taglist-tag-bg);
    --color-tasklist-listname: #bbb;
    --color-tasklist-listname-bg: #333;


    /* Priority */
    --color-priority-none: #676767;
    --color-priority-text: var(--color-text-default);

    /* DueDates */
    --color-duedate-default: var(--color-tab-text);
    --color-duedate-soon: #008000;
    --color-duedate-today: #FF0000;
    --color-duedate-past: #B52D2D;

    /* Markdown */
    --color-md-border: #333;
    --color-md-bg-highlighted: #222;
    --color-md-text-blockquote: #777;

    /* Settings */
    --color-settings-row: #222;

    /* */
    --color-placeholder: #444;
    --color-placeholder-border: #555;

    --svg-select: var(--svg-select-dark);

}


================================================
FILE: src/content/theme/images/COPYRIGHT
================================================
rss.svg, rss-disabled.svg - are (or based on) icons by Icons8 from https://icons8.com/icon/13841/rss
calendar.svg - icon by Icons8 from https://icons8.com/icon/__LA9wZgJaqd/calendar
loading48.gif - generated at Preloaders.net

Other images in this directory were made by Max Pozdeev the author of myTinyTodo
and licensed under the terms of GNU GPL version 2 or any later.


================================================
FILE: src/content/theme/images/index.html
================================================
Place for Images

================================================
FILE: src/content/theme/images/svg2base64.php
================================================
<?php

/*
    This file is a part of myTinyTodo.
    (C) Copyright 2023 Max Pozdeev <maxpozdeev@gmail.com>
    Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/

// call like: php -f svg2base64.php > ../images.css

if (php_sapi_name() != 'cli') {
    error_log("Supports cli only");
    exit(-1);
}

if (isset($argv[1])) {
    print  base64file($argv[1]);
    exit();
}

$files = [];
$h = opendir(__DIR__);
while ( false !== ($file = readdir($h)) )
{
    if ( preg_match('/(.+)\.svg$/', $file, $m) ) {
        $files[] = $m[1];
    }
}
closedir($h);

if (!$files) {
    exit;
}
sort($files);

print ":root {\n";
foreach ($files as $name) {
    $b64 = base64file(__DIR__. "/$name.svg");
    print "  --svg-{$name}: url('data:image/svg+xml;base64,$b64');\n";
}
print "}\n";

function base64file(string $filename): string
{
    $content = file_get_contents($filename);
    //$content = str_replace(["\n","\r\n"], ['',''], $content);
    $content = cleanXml($content);
    return base64_encode($content);
}

function cleanXml(string $data): string
{
    $dom = new DOMDocument;
    $dom->preserveWhiteSpace = false;
    $dom->loadXML($data);

    $xpath = new DOMXPath($dom);
    foreach ($xpath->query('//comment()') as $comment) {
        $comment->parentNode->removeChild($comment);
    }
    return $dom->saveXML();
}


================================================
FILE: src/content/theme/index.html
================================================


================================================
FILE: src/content/theme/markdown.css
================================================
/* Markdown notes */

.markdown-note {
  line-height: 1.3;
}

.markdown-note > *:first-child { margin-top: 0 !important; }
.markdown-note > *:last-child { margin-bottom: 0 !important; }

.markdown-note h1 { font-size:2rem; }
.markdown-note h2 { font-size:1.5rem; }
.markdown-note h3 { font-size:1.2rem; }
.markdown-note h4 { font-size:1rem; }
.markdown-note h5 { font-size:1rem; }
.markdown-note h6 { font-size:1rem; }

.markdown-note hr {
  height: 1px;
  border: 0;
  background-color: var(--color-border-default);
}

.markdown-note p,
.markdown-note blockquote,
.markdown-note ul,
.markdown-note ol,
.markdown-note dl,
.markdown-note table,
.markdown-note pre {
  margin: 12px 0;
}
.markdown-note ul,
.markdown-note ol {
  padding-left: 2.3rem;
}
.markdown-note ul.no-list,
.markdown-note ol.no-list {
  list-style-type: none;
  padding: 0;
}

.markdown-note blockquote {
  margin:15px 0;
  border-left: 4px solid var(--color-md-border);
  padding: 0 15px;
  color: var(--color-md-text-blockquote, #777);
}
.markdown-note blockquote > :first-child {
  margin-top: 0px;
}
.markdown-note blockquote > :last-child {
  margin-bottom: 0px;
}

.markdown-note table {
  width: 100%;
  overflow: auto;
  display: block;
  border-spacing: 0;
  border-collapse: collapse;
}
.markdown-note table th {
  font-weight: bold;
}
.markdown-note table th,
.markdown-note table td {
  border: 1px solid var(--color-md-border);
  padding: 6px 13px;
}
.markdown-note table tr {
  border-top: 1px solid var(--color-border-default);
  background-color: var(--color-tasklist-row);
}
.markdown-note table tr:nth-child(2n) {
  background-color: var(--color-md-bg-highlighted);
}


.markdown-note code, /* inline code */
.markdown-note tt {
  font-size: 13px;  /* if main font is 14px */
  font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
  padding: 0px 5px;
  background-color: var(--color-md-bg-highlighted);
  border-radius: 3px;
}
.markdown-note code {
  white-space: break-spaces;
}
.markdown-note pre {
  font-size: 13px;
  font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
  line-height: 1.2rem;
  padding: 10px;
  background-color: var(--color-md-bg-highlighted);
  overflow: auto;
  border-radius: 3px;
}
.markdown-note pre code, /* block of code */
.markdown-note pre tt {
  margin: 0;
  padding: 0;
  background-color: transparent;
  border: none;
}
.markdown-note pre > code {
  white-space: pre;
}

.markdown-note img {
  max-width: 100%;
}


/* narrow screens */
@media only screen and (max-width: 600px) {

  .markdown-note pre,
  .markdown-note code,
  .markdown-note tt {
    font-size: 14px;  /* if main font is 16px */
  }
}


================================================
FILE: src/content/theme/print.css
================================================
@media print {

  html,body { height:auto; }
  h2 { display: none; }
  h3 { border-bottom:2px solid #777777; }
  #toolbar { display: none; }

  .topblock { display:none; }
  .mtt-tab { display:none; }
  .mtt-tab.mtt-tab-selected { display:block; border:none; background:none; margin:0; }
  .mtt-tab.mtt-tab-selected a { padding:0; display:inline-block; height:auto; }
  .mtt-tab.mtt-tab-selected .title-block { display:inline-block; }
  .mtt-tab.mtt-tab-selected .list-action { display:none; }
  .mtt-tab.mtt-tab-selected .title { text-align:left; padding:0; margin:0; max-width:none; font-size:1.3rem; color:#000; }

  .mtt-tabs-new-button { display:none; }
  #tabs_buttons { display:none; }
  #taskview { padding-left:0; font-weight:normal; }
  #taskview .arrdown { display:none; }

  #tasklist { list-style-type: decimal; list-style-position: outside; }
  #tasklist li.task-row {
    border-bottom:none;
    margin-left:2.5rem;
    /*border-bottom:1px solid #f0f0f0;*/
    border: none;
  }
  #tasklist li.task-row:hover { box-shadow: none; }
  #tasklist li.task-row.task-has-note.task-expanded .task-block,
  #tasklist li.task-row.task-has-note.clicked .task-block,
  #tasklist li.task-row.task-expanded { border: none; }

  div.task-note-block {
    border-left:1px solid #777777;
    padding-left:10px;
    margin-left:3px;
    padding-top:0px;
    font-size:9pt;
    color:#333333;
  }
  .task-middle { margin-left:0px; margin-right:3px; }
  .task-left { display:none; }
  .task-actions { display:none; }
  .task-date { white-space:nowrap; margin-left:10px; }
  #tasklist li.today, #tasklist li.past { background-color:#ffffff; border-color:#dedede; }
  .task-prio { font-weight:bold; }

  li.task-completed  { opacity:1; }

  #footer_content { border-top:1px solid #777777; background:none; }
  #footer_content a { text-decoration:none; color:#000000; }

  #tagcloudbtn { display:none; }
  .mtt-notes-showhide { display:none; }
  #taskview img { display:none; }

}


================================================
FILE: src/content/theme/style.css
================================================
/*
  This file is a part of myTinyTodo.
*/

/*
  Browsers support:
  Flexbox layout - https://caniuse.com/flexbox
  CSS masks from SVG images - https://caniuse.com/mdn-css_properties_mask-image_svg_masks
  CSS variables - https://caniuse.com/css-variables
*/


/* light colors */

:root {
  --color-bg: #fff;
  --color-text-default: #000;
  --color-text-reduced: #222;
  --color-text-reduced2: #666;
  --color-text-reduced3: #999;
  --color-link: blue;
  --color-btn-reduced: #707070;
  --color-btn-reduced-hover: #4c4c4c;
  --color-btn-default: #000;
  --color-btn-hover: #efefef;
  --color-submit: #eee;
  --color-submit-hover: #ddd;
  --color-row-underlinig: #dedede;
  --color-border-default: #ccc;
  --color-border-focus: #5a8df0;
  --color-border-focus-shadow: rgba(90,141,240,0.7);
  --color-error: var(--color-text-reduced);
  --color-error-bg: #ff3333;
  --color-info: var(--color-text-reduced);
  --color-info-bg: #EFC300;
  --color-input-bg: #fff;

  --color-toolbar: #ededed;
  --color-btn-toolbar-hover: #ddd;
  --color-content-delimiter: #b5d5ff;
  --color-footer: #b5d5ff;

  /* Tabs */
  --color-tab: #fbfbfb;
  --color-tab-selected: var(--color-toolbar);
  --color-tab-hover: #ddd;
  --color-tab-border: #ededed;
  --color-tab-text: #333333;
  --color-btn-tab: #888;
  --color-btn-tab-hover: #fff;
  --color-btn-tab-hover-bg: #999;

  /* Menu */
  --color-menu: #f9f9f9;
  --color-menu-border: var(--color-border-default);
  --color-menu-hover: #5a8df0;
  --color-menu-text: #000;
  --color-menu-text-hover: #fff;
  --color-menu-text-disabled: #ACA899;
  --color-popup-shadow: 1px 2px 5px rgba(0,0,0,0.5);

  /* Tasklist */
  --color-tasklist-row: var(--color-bg);;
  --color-tasklist-row-border: var(--color-row-underlinig);
  --color-tasklist-row-inter-border: #f0f0f0;
  --color-tasklist-row-expanded-border: var(--color-tasklist-row-border);
  --color-tasklist-tag: var(--color-tab-text);
  --color-tasklist-note-link: #777;
  --color-tasklist-link-hover: var(--color-link); /* #af0000 */
  --color-tasklisk-hover-shadow: rgba(0,0,0,0.3);
  --color-taglist-tag: var(--color-text-reduced);
  --color-taglist-tag-bg: #e0e0e0;
  --color-taglist-tag-hover: var(--color-taglist-tag);
  --color-taglist-tag-hover-bg: var(--color-taglist-tag-bg);
  --color-tasklist-listname: #555;
  --color-tasklist-listname-bg: #eee;

  /* Priority */
  --color-priority-none: #dedede;
  --color-priority-low: #3377ff;
  --color-priority-high: #ff7700;
  --color-priority-urgent: #ff3333;
  --color-priority-text: #fff;

  /* DueDates */
  --color-duedate-default: var(--color-tab-text);
  --color-duedate-soon: #008000;
  --color-duedate-today: #FF0000;
  --color-duedate-past: #A52A2A;


  /* Markdown */
  --color-md-border: #ddd;
  --color-md-bg-highlighted: #f8f8f8;
  --color-md-text-blockquote: #777;

  /* Settings */
  --color-settings-row: #f5f5f5;

  /* */
  --color-placeholder: #ddd;
  --color-placeholder-border: #aaa;

  /* svg images */
  --svg-add: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwb2x5bGluZSBwb2ludHM9IjE1IDQsIDE1IDksIDIgOSIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iIzAwMCIgZmlsbD0ibm9uZSIvPjxwb2x5bGluZSBwb2ludHM9IjYgNSwgMSA5LCA2IDEzIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSIjMDAwIiBmaWxsPSJub25lIi8+PC9zdmc+Cg==');
  --svg-arr-left: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwb2x5Z29uIHBvaW50cz0iMTAgMywgNCA4LCAxMCAxMyIgZmlsbD0iIzAwMCIvPjwvc3ZnPgo=');
  --svg-arr-right: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwb2x5Z29uIHBvaW50cz0iNiAzLCAxMiA4LCA2IDEzIiBmaWxsPSIjMDAwIi8+PC9zdmc+Cg==');
  --svg-arrdown2: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgOCA4Ij48cG9seWdvbiBwb2ludHM9IjAgMiwgOCAyLCA0IDYiIGZpbGw9ImJsYWNrIi8+PC9zdmc+Cg==');
  --svg-back: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxwb2x5bGluZSBwb2ludHM9IjkgMiwgNCA3LCA5IDEyIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSIjMDAwIiBmaWxsPSJub25lIi8+PC9zdmc+Cg==');
  --svg-calendar: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0wLjUgMS41SDE1LjVWMTQuNUgwLjV6Ii8+PHBhdGggZmlsbD0iIzc4OGI5YyIgZD0iTTE1LDJ2MTJIMVYySDE1IE0xNiwxSDB2MTRoMTZWMUwxNiwxeiIvPjxwYXRoIGZpbGw9IiNmNzhmOGYiIGQ9Ik0wLjUgMS41SDE1LjVWNC41SDAuNXoiLz48cGF0aCBmaWxsPSIjYzc0MzQzIiBkPSJNMTUsMnYySDFWMkgxNSBNMTYsMUgwdjRoMTZWMUwxNiwxeiIvPjxwYXRoIGZpbGw9IiNjNWQ0ZGUiIGQ9Ik01IDdINlY4SDV6TTcgN0g4VjhIN3pNOSA3SDEwVjhIOXpNMTEgN0gxMlY4SDExek0zIDlINFYxMEgzek01IDlINlYxMEg1ek03IDlIOFYxMEg3ek05IDlIMTBWMTBIOXpNMTEgOUgxMlYxMEgxMXpNMyAxMUg0VjEySDN6TTUgMTFINlYxMkg1ek03IDExSDhWMTJIN3pNOSAxMUgxMFYxMkg5eiIvPjwvc3ZnPgo=');
  --svg-checkmark: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwb2x5bGluZSBwb2ludHM9IjQgOCwgNyAxMiwgMTMgNSIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiMwMDAiLz48L3N2Zz4K');
  --svg-closetag: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxnIHN0cm9rZS13aWR0aD0iMS41IiBmaWxsPSJub25lIj48cGF0aCBkPSJNMTIuMCAxMi4wbC04LTgiIHN0cm9rZT0iIzAwMCIvPjxwYXRoIGQ9Ik00LjAgMTIuMGwrOC04IiBzdHJva2U9IiMwMDAiLz48L2c+PC9zdmc+Cg==');
  --svg-newtask-ext: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxnIHN0cm9rZT0iIzAwMCIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxLjAiPjxwb2x5bGluZSBwb2ludHM9IjEwIDAuNSwgMC41IDAuNSwgMC41IDE1LjUsIDEzLjUgMTUuNSwgMTMuNSA3LjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cG9seWxpbmUgcG9pbnRzPSI3IDcsIDYgMTAsIDkgOSwgMTUgMywgMTMgMSwgNyA3LCA2IDEwIi8+PGxpbmUgeDE9IjciIHkxPSI3IiB4Mj0iOSIgeTI9IjkiLz48L2c+PC9zdmc+Cg==');
  --svg-note-toggle: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxwb2x5bGluZSBwb2ludHM9IjUgMiwgMTAgNywgNSAxMiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iIzAwMCIgZmlsbD0ibm9uZSIvPjwvc3ZnPgo=');
  --svg-plus: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxsaW5lIHgxPSIyIiB5MT0iOCIgeDI9IjE0IiB5Mj0iOCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iIzAwMCIvPjxsaW5lIHgxPSI4IiB5MT0iMiIgeDI9IjgiIHkyPSIxNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iIzAwMCIvPjwvc3ZnPgo=');
  --svg-rss: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4IDgiPjx0aXRsZT5SU1MgZmVlZCBpY29uPC90aXRsZT48c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLmJ1dHRvbiB7c3Ryb2tlOiBub25lOyBmaWxsOiBvcmFuZ2U7fQogICAgLnN5bWJvbCB7c3Ryb2tlOiBub25lOyBmaWxsOiB3aGl0ZTt9CiAgPC9zdHlsZT48cmVjdCBjbGFzcz0iYnV0dG9uIiB3aWR0aD0iOCIgaGVpZ2h0PSI4IiByeD0iMS41Ii8+PGNpcmNsZSBjbGFzcz0ic3ltYm9sIiBjeD0iMiIgY3k9IjYiIHI9IjEiLz48cGF0aCBjbGFzcz0ic3ltYm9sIiBkPSJtIDEsNCBhIDMsMyAwIDAgMSAzLDMgaCAxIGEgNCw0IDAgMCAwIC00LC00IHoiLz48cGF0aCBjbGFzcz0ic3ltYm9sIiBkPSJtIDEsMiBhIDUsNSAwIDAgMSA1LDUgaCAxIGEgNiw2IDAgMCAwIC02LC02IHoiLz48L3N2Zz4K');
  --svg-rss-disabled: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4IDgiPjx0aXRsZT5SU1MgZmVlZCBpY29uPC90aXRsZT48c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLmJ1dHRvbiB7c3Ryb2tlOiBub25lOyBmaWxsOiAjYmJiO30KICAgIC5zeW1ib2wge3N0cm9rZTogbm9uZTsgZmlsbDogd2hpdGU7fQogIDwvc3R5bGU+PHJlY3QgY2xhc3M9ImJ1dHRvbiIgd2lkdGg9IjgiIGhlaWdodD0iOCIgcng9IjEuNSIvPjxjaXJjbGUgY2xhc3M9InN5bWJvbCIgY3g9IjIiIGN5PSI2IiByPSIxIi8+PHBhdGggY2xhc3M9InN5bWJvbCIgZD0ibSAxLDQgYSAzLDMgMCAwIDEgMywzIGggMSBhIDQsNCAwIDAgMCAtNCwtNCB6Ii8+PHBhdGggY2xhc3M9InN5bWJvbCIgZD0ibSAxLDIgYSA1LDUgMCAwIDEgNSw1IGggMSBhIDYsNiAwIDAgMCAtNiwtNiB6Ii8+PC9zdmc+Cg==');
  --svg-search: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxnIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2U9IiMwMDAiIGZpbGw9Im5vbmUiPjxwYXRoIGQ9Ik0xNCAxNGwtNC00Ii8+PGNpcmNsZSBjeD0iNyIgY3k9IjciIHI9IjQuNSIvPjwvZz48L3N2Zz4K');
  --svg-search-cancel: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im04IDFhNyA3IDAgMCAwLTcgNyA3IDcgMCAwIDAgNyA3IDcgNyAwIDAgMCA3LTcgNyA3IDAgMCAwLTctN3ptLTIuNDY4OCAzLjQ2ODggMi40Njg4IDIuNDY4OCAyLjQ2ODgtMi40Njg4IDEuMDYyNSAxLjA2MjUtMi40Njg4IDIuNDY4OCAyLjQ2ODggMi40Njg4LTEuMDYyNSAxLjA2MjUtMi40Njg4LTIuNDY4OC0yLjQ2ODggMi40Njg4LTEuMDYyNS0xLjA2MjUgMi40Njg4LTIuNDY4OC0yLjQ2ODgtMi40Njg4IDEuMDYyNS0xLjA2MjV6IiBmaWxsPSIjMDAwIi8+PC9zdmc+Cg==');
  --svg-select: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxwb2x5bGluZSBwb2ludHM9IjIgNiwgNyAxMSwgMTIgNiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiM0NDQiIGZpbGw9Im5vbmUiLz48L3N2Zz4K');
  --svg-select-dark: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxwb2x5bGluZSBwb2ludHM9IjIgNiwgNyAxMSwgMTIgNiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiNhYWEiIGZpbGw9Im5vbmUiLz48L3N2Zz4K');
  --svg-selectlist: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxjaXJjbGUgY3g9IjMiIGN5PSI4IiByPSIxLjUiIGZpbGw9IiMzMzMiLz48Y2lyY2xlIGN4PSI4IiBjeT0iOCIgcj0iMS41IiBmaWxsPSIjMzMzIi8+PGNpcmNsZSBjeD0iMTMiIGN5PSI4IiByPSIxLjUiIGZpbGw9IiMzMzMiLz48L3N2Zz4K');
  --svg-selectlist2: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PGxpbmUgeDE9IjIiIHkxPSIzIiB4Mj0iMTQiIHkyPSIzIi8+PGxpbmUgeDE9IjIiIHkxPSI4IiB4Mj0iMTQiIHkyPSI4Ii8+PGxpbmUgeDE9IjQiIHkxPSIxMyIgeDI9IjE0IiB5Mj0iMTMiLz48L2c+PC9zdmc+Cg==');
  --svg-task-menu: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxwb2x5bGluZSBwb2ludHM9IjIgNiwgNyAxMSwgMTIgNiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiMwMDAiIGZpbGw9Im5vbmUiLz48L3N2Zz4K');
  --svg-task-menu2: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTQgMTQiPjxnIGZpbGw9IiMwMDAiPjxjaXJjbGUgY3g9IjciIGN5PSIyIiByPSIxLjUiLz48Y2lyY2xlIGN4PSI3IiBjeT0iNyIgcj0iMS41Ii8+PGNpcmNsZSBjeD0iNyIgY3k9IjEyIiByPSIxLjUiLz48L2c+PC9zdmc+Cg==');
}

/*	default style	*/

html {
  height:100%;
  /*overflow-y:hidden; /* for modal overlay to disable scrolling, but breaks position:absolute */
  font-size:14px; /* =1rem */
}
body {
  margin: 0px;
  padding: 0px;
  width: 100%;
  height: 100%;
  background-color: var(--color-bg);
  color: var(--color-text-default);
  display:flex;
  flex-direction:column;
  align-items:center;
  overflow-y: scroll; /* always show scrollbar */
}
body, td, th, input, textarea, select, button {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",/*Roboto,*/ Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  font-size: 1rem;
}
#mtt {
  flex: 1 0 auto;
  width:100%;
  max-width:1100px;
  padding:8px;
  margin-bottom: 1rem;
  box-sizing: border-box;
}

form { display: inline; }
.topblock h2 {
  margin:0;
  font-size:1.5rem;
  padding-left: 30px;
  padding-right: 10px;
  background: url(images/logo.gif) no-repeat top left;
  background-size: 24px 24px;
}
#mtt.ajax-loading .topblock h2 {
  background-image: url(images/logo-loading.gif);
}

/* preload images */
body::after {
  position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
  content:url(images/logo-loading.gif);
}

h3.page-title {
  margin:0;
  border-bottom:2px solid var(--color-content-delimiter);
  margin-bottom:1rem;
  padding:0.6rem 0;
  font-size:1.1rem;
}
a { color: var(--color-link); cursor:pointer; text-decoration:underline; }

.topblock { display:flex; align-items:flex-start; margin-bottom:1rem; }
#footer {
  flex-shrink:0;
  width:100%;
  max-width:1100px;
}
#footer_content {
  background-color: var(--color-footer);
  border-top: 1px solid var(--color-content-delimiter);
  padding:0.7rem;
  font-size:0.9rem;
  display:flex;
  justify-content:space-between;
}
#footer_content span:last-child { text-align:center; }
#footer_content a {
  color: var(--color-text-default);
}
#footer_content a.powered-by-link { font-weight:bold; }

.topblock-title {
  display:flex;
  align-items:center;
  min-width: 0; /* required for h2 ellipsis */
}
.topblock-bar { flex-grow:1; display:flex; justify-content:flex-start; border-bottom:1px solid var(--color-content-delimiter); padding-bottom:5px; }
.bar-menu {
  flex-grow:1;
  display:flex;
  justify-content:flex-end;
  text-align:right;
  white-space: nowrap;
}
.bar-menu > * {
  margin-left: 10px;
}


#mtt.no-need-auth .mtt-need-auth-enabled { display:none; }

.form-input {
  padding: 3px;
  border: 1px solid var(--color-border-default);
  background-color: var(--color-input-bg);
  border-radius: 2px;
  transition: box-shadow .1s ease-in-out;
}
select.form-input {
  appearance: none;
  -webkit-appearance: none; -moz-appearance: none;
  background: var(--color-input-bg) var(--svg-select) no-repeat top 4px right 5px;
  background-size: 1rem 1rem;
  padding-right: calc(1rem + 10px);
}
.form-input:focus {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: 0 0 0 2px var(--color-border-focus-shadow);
}
.form-bottom-buttons {
  display: flex;
  justify-content: center;
  padding: 1rem 0;
}
.form-bottom-buttons > * {
  min-width: 4rem;
  padding: 4px 6px;
  margin: 0 6px;
  background-color: var(--color-submit);
  border: 1px solid var(--color-border-default);
  border-radius: 3px;
}
.form-bottom-buttons > *:hover {
  background-color: var(--color-submit-hover);
}


#page_login {
  max-width: 250px;
  margin:0 auto; /* center */
  margin-top: 100px;
}
#authform {
  background-color: var(--color-menu);
  border: 1px solid var(--color-menu-border);
  border-radius: 5px;
}
#authform .auth-content {
  padding: 1.5rem;
}
#authform div { padding:2px 0px; }
#authform .form-bottom-buttons {
  border-top: 1px solid var(--color-border-default);
  padding: 0.8rem 0;
}

#authform div.h { font-weight:bold; }
#authform input[type=password] { width: 100%; box-sizing: border-box; }

#msg .msg-text {  font-weight:bold; cursor:pointer; padding:0 1px; }
#msg .msg-details {
  display:none;
  padding:1px 4px;
  background-color: var(--color-bg);
  max-width:800px;
  position:absolute;
  z-index:2;
  white-space: pre-line;
}
#msg.mtt-error .msg-text { background-color: var(--color-error-bg) }
#msg.mtt-error .msg-details { border:1px solid var(--color-error-bg);  }
#msg.mtt-error .msg-text, #msg.mtt-error .msg-details { color: var(--color-error); }
#msg.mtt-info .msg-text { background-color: var(--color-info-bg); }
#msg.mtt-info .msg-details { border:1px solid var(--color-info-bg); }
#msg.mtt-info .msg-text, #msg.mtt-info .msg-details { color: var(--color-info); }

#lists { font-size:0.95rem; display:flex; align-items:flex-start; justify-content:flex-end; }
#mtt.readonly.no-lists #lists > * { visibility: hidden; }
.tabs-n-button { flex-grow:1; display:flex; align-items:flex-start; }
.tab-height-wrapper { box-sizing:border-box; height:2.2rem; display:flex; align-items:center; }
.mtt-tabs { list-style:none; padding:0; margin:0; display:flex; justify-content:flex-start; flex-wrap:wrap; }
.mtt-tab {
  margin:1px 3px 0 0;
  background-color: var(--color-tab);
  border:1px solid var(--color-tab-border);
  border-bottom:none;
  border-top-right-radius:8px;
  transition:background-color 0.1s ease-in;
  max-width:230px;
}
.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;
}
.mtt-tab a:active {
  outline:0;
  text-decoration:none;
}
.mtt-tab .title-block {
  display: flex;
  align-items: baseline;
  min-width: 75px;
}
.mtt-tab .title {
  display:inline-block;
  text-align:center;
  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,
.mtt-tab .mtt-img-button.mtt-menu-button-active {
  background-color:var(--color-btn-tab-hover-bg);
}
.mtt-tab .mtt-img-button > span {
  width: 8px; height: 8px;
  mask: url(images/arrdown2.svg) no-repeat; -webkit-mask: url(images/arrdown2.svg) no-repeat;
  background-color: var(--color-btn-tab);
  transition: background-color 0.1s ease-in; /* animate together with button background */
}
.mtt-tab .mtt-img-button:hover > span,
.mtt-tab .mtt-img-button.mtt-menu-button-active > span {
  background-color: var(--color-btn-tab-hover);
}
.mtt-tab.mtt-tab-selected .list-action { display:block; }
.mtt-tab.mtt-tab-selected { border-color: var(--color-tab-selected); background-color: var(--color-tab-selected); }
.mtt-tab:not(.mtt-tab-selected):hover { background-color: var(--color-tab-hover); }
.mtt-tab.mtt-tab-selected a { color: var(--color-text-default); }

.mtt-tab-hidden { display:none; }
.mtt-tab-sort-placeholder {
  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;
  border-top:1px solid transparent;
  margin-top:1px;
}

.mtt-tabs-new-button { display:inline-block; margin-top:1px; border:1px solid var(--color-tab-border); border-bottom:none; border-top-right-radius: 8px;
  background-color: var(--color-tab);
  padding-left:3px; padding-right:3px;
}
.mtt-tabs-new-button span {
  display:block;
  width:16px; height:16px;
  mask: url(images/plus.svg) no-repeat; -webkit-mask: url(images/plus.svg) no-repeat;
  background-color: var(--color-tab-text);
}
.mtt-tabs-new-button:hover { cursor:pointer; background-color: var(--color-tab-hover); }
#mtt.readonly .mtt-tabs-new-button { display:none; }

.mtt-tabs-select-button > span {
  mask: url(images/selectlist2.svg) no-repeat; -webkit-mask: url(images/selectlist2.svg) no-repeat;
  background-color: var(--color-tab-text);
}

.mtt-img-button {
  padding: 5px;
  display:block;
  border-radius:4px;
  transition:background-color 0.1s ease-in;
  cursor:pointer;
}
.mtt-img-button:hover,
.mtt-img-button.mtt-menu-button-active {
  background-color: var(--color-btn-hover);
}
.mtt-img-button span { display:block; width:16px; height:16px; }

#mtt.no-lists         #toolbar > * { visibility:hidden; }
#mtt.no-list-selected #toolbar > * { visibility:hidden; }
#mtt.readonly.no-lists #toolbar { visibility: hidden; }
#toolbar  {
  padding:8px;
  border-bottom:1px solid var(--color-row-underlinig);
  background: var(--color-toolbar);
}
#toolbar .mtt-img-button:hover {
  background-color: var(--color-btn-toolbar-hover);
}

.arrdown, .arrdown2  {
  display: inline-block;
  height: 9px; width:9px;
  mask: url(images/arrdown2.svg) no-repeat; -webkit-mask: url(images/arrdown2.svg) no-repeat;
  background-color: var(--color-btn-default);
}
.arrdown2 {
  height:7px; width:7px;
}

.newtask-n-search-container {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

/* Quick Task Add */
.taskbox-c { flex-grow:1; display:flex; align-items:center; }
.mtt-taskbox { position:relative; padding-left:22px; /*input padding+border*/ flex-grow:1; max-width:430px; }
#task {
  color: var(--color-text-reduced);
  background: var(--color-bg);
  height:1.35rem; padding:2px 4px; padding-right:20px; border:1px solid var(--color-border-default); border-radius:3px; width:100%; margin-left:-24px;
}
#mtt.show-all-tasks .taskbox-c,
#mtt.readonly .taskbox-c {
  visibility: hidden;
}

.mtt-taskbox-icon {
  width:16px; height:16px;
  position:absolute;
  top:50%;
  right:2px;
  margin-top:-8px;
  cursor:pointer;
  mask: url(images/add.svg) no-repeat; -webkit-mask: url(images/add.svg) no-repeat;
  background-color: var(--color-btn-reduced);
  transition: background-color 0.1s ease-in;
}
.mtt-taskbox-icon:hover {
  background-color: var(--color-btn-reduced-hover);
}
#newtask_adv { margin-left:0.5rem; }
#newtask_adv span {
  mask: url(images/newtask-ext.svg) no-repeat; -webkit-mask: url(images/newtask-ext.svg) no-repeat;
  background-color: var(--color-btn-reduced);
}


/* Live Search */
#search {
 color: var(--color-text-reduced);
 background: var(--color-bg);
 height:1.35rem; padding:2px 20px; width:100%; margin-left:-42px; /*padding+border*/
 border:1px solid var(--color-border-default); border-radius:10px;
}
#search_close { display:none; }

.mtt-searchbox { position:relative; padding-left:42px; /*input padding+border*/ }
.mtt-searchbox-icon { width:16px; height:16px; position:absolute; top:50%; margin-top:-8px; }
.mtt-searchbox-icon.mtt-icon-search {
  left:4px;
  mask: url(images/search.svg) no-repeat; -webkit-mask: url(images/search.svg) no-repeat;
  background-color: var(--color-btn-reduced);
}
.mtt-searchbox-icon.mtt-icon-cancelsearch {
  right:4px;
  cursor:pointer;
  mask: url(images/search-cancel.svg) no-repeat; -webkit-mask: url(images/search-cancel.svg) no-repeat;
  background-color: var(--color-btn-reduced);
  transition: background-color 0.1s ease-in;
Download .txt
gitextract_6m_aa8ts/

├── .editorconfig
├── .gitignore
├── README.md
├── buildtar.php
├── composer.json
├── composer.sh
└── src/
    ├── .htaccess
    ├── COPYRIGHT
    ├── LICENSE
    ├── api.php
    ├── config-sample.php
    ├── content/
    │   ├── index.html
    │   ├── js/
    │   │   ├── index.html
    │   │   └── jquery.ui.touch-punch.js
    │   ├── mytinytodo.js
    │   ├── mytinytodo_api.js
    │   └── theme/
    │       ├── dark.css
    │       ├── images/
    │       │   ├── COPYRIGHT
    │       │   ├── index.html
    │       │   └── svg2base64.php
    │       ├── index.html
    │       ├── markdown.css
    │       ├── print.css
    │       ├── style.css
    │       └── style_rtl.css
    ├── db/
    │   └── .htaccess
    ├── docker-config.php
    ├── export.php
    ├── ext/
    │   ├── .htaccess
    │   ├── CustomCSS/
    │   │   ├── .htaccess
    │   │   ├── extension.json
    │   │   ├── lang/
    │   │   │   ├── en.json
    │   │   │   ├── pl.json
    │   │   │   └── ru.json
    │   │   └── loader.php
    │   ├── _examples/
    │   │   └── CustomSmartSyntax/
    │   │       ├── .htaccess
    │   │       ├── extension.json
    │   │       └── loader.php
    │   ├── backup/
    │   │   ├── .htaccess
    │   │   ├── class.backup.php
    │   │   ├── class.check.php
    │   │   ├── class.controller.php
    │   │   ├── class.download.php
    │   │   ├── class.restore.php
    │   │   ├── extension.json
    │   │   ├── lang/
    │   │   │   ├── en.json
    │   │   │   └── ru.json
    │   │   └── loader.php
    │   ├── index.html
    │   ├── notifications/
    │   │   ├── .htaccess
    │   │   ├── class.controller.php
    │   │   ├── class.observer.php
    │   │   ├── class.sender.php
    │   │   ├── class.telegramapi.php
    │   │   ├── cli-notify.php
    │   │   ├── extension.json
    │   │   ├── lang/
    │   │   │   ├── de.json
    │   │   │   ├── en.json
    │   │   │   └── ru.json
    │   │   └── loader.php
    │   └── updater/
    │       ├── .htaccess
    │       ├── class.controller.php
    │       ├── class.updater.php
    │       ├── extension.json
    │       ├── lang/
    │       │   ├── de.json
    │       │   ├── en.json
    │       │   └── ru.json
    │       └── loader.php
    ├── feed.php
    ├── includes/
    │   ├── .htaccess
    │   ├── api/
    │   │   ├── AuthController.php
    │   │   ├── ExtSettingsController.php
    │   │   ├── ListsController.php
    │   │   ├── TagsController.php
    │   │   └── TasksController.php
    │   ├── class.config.php
    │   ├── class.db.mysql.php
    │   ├── class.db.mysqli.php
    │   ├── class.db.postgres.php
    │   ├── class.db.sqlite3.php
    │   ├── class.dbconnection.php
    │   ├── class.dbcore.php
    │   ├── class.lang.php
    │   ├── class.sessionhandler.php
    │   ├── classes.php
    │   ├── common.php
    │   ├── filters.php
    │   ├── index.html
    │   ├── lang/
    │   │   ├── _percent.php
    │   │   ├── ar.json
    │   │   ├── bg.json
    │   │   ├── ca.json
    │   │   ├── cz.json
    │   │   ├── da.json
    │   │   ├── de.json
    │   │   ├── el.json
    │   │   ├── en-rtl.json
    │   │   ├── en.json
    │   │   ├── es-mx.json
    │   │   ├── es.json
    │   │   ├── et.json
    │   │   ├── fa.json
    │   │   ├── fr.json
    │   │   ├── he.json
    │   │   ├── hu.json
    │   │   ├── it.json
    │   │   ├── ja.json
    │   │   ├── lt.json
    │   │   ├── mk.json
    │   │   ├── nl.json
    │   │   ├── no.json
    │   │   ├── pl.json
    │   │   ├── pt-br.json
    │   │   ├── pt-pt.json
    │   │   ├── readme.md
    │   │   ├── ro.json
    │   │   ├── ru.json
    │   │   ├── sk.json
    │   │   ├── sl.json
    │   │   ├── sr.json
    │   │   ├── sv.json
    │   │   ├── th.json
    │   │   ├── tr.json
    │   │   ├── uk.json
    │   │   ├── vi.json
    │   │   ├── zh-cn.json
    │   │   └── zh-tw.json
    │   ├── markup.commonmark.php
    │   ├── markup.parsedown.php
    │   ├── markup.php
    │   ├── notifications.php
    │   ├── smartsyntax.php
    │   ├── theme.php
    │   └── version.php
    ├── index.php
    ├── init.php
    ├── mtt-edit-settings.php
    ├── mtt-emergency.php
    ├── settings.php
    └── setup.php
Download .txt
SYMBOL INDEX (663 symbols across 54 files)

FILE: buildtar.php
  function deleteTreeIfDir (line 108) | function deleteTreeIfDir($dir)

FILE: src/api.php
  function myErrorHandler (line 149) | function myErrorHandler($errno, $errstr, $errfile, $errline)
  function myExceptionHandler (line 164) | function myExceptionHandler(Throwable $e)
  function checkReadAccess (line 193) | function checkReadAccess(?int $listId = null)
  function checkWriteAccess (line 207) | function checkWriteAccess(?int $listId = null)
  function haveWriteAccess (line 215) | function haveWriteAccess(?int $listId = null) : bool

FILE: src/content/js/jquery.ui.touch-punch.js
  function getTouchCoords (line 65) | function getTouchCoords (event) {
  function simulateMouseEvent (line 77) | function simulateMouseEvent (event, simulatedType) {
  function startDelayTimer (line 109) | function startDelayTimer (event) {
  function fireMouseDown (line 119) | function fireMouseDown () {

FILE: src/content/mytinytodo.js
  function ac_split (line 451) | function ac_split( val ) {
  function ac_extractLast (line 454) | function ac_extractLast( term ) {
  method clear (line 984) | clear() {
  method addTag (line 990) | addTag(tagId, tag, exclude)
  method cancelTag (line 1014) | cancelTag(tagId)
  method getTags (line 1029) | getTags(withExcluded)
  method prepareTagHtml (line 1043) | prepareTagHtml(tagId, tag, classes)
  function addList (line 1111) | function addList()
  function renameCurList (line 1131) | function renameCurList()
  function deleteCurList (line 1147) | function deleteCurList()
  function publishCurList (line 1159) | function publishCurList()
  function enableFeedKeyInCurList (line 1176) | function enableFeedKeyInCurList()
  function showFeedKeyInCurList (line 1198) | function showFeedKeyInCurList()
  function loadTasks (line 1206) | function loadTasks(opts)
  function prepareListHtml (line 1246) | function prepareListHtml(list, isSelected)
  function prepareTaskStr (line 1259) | function prepareTaskStr(item, noteExp)
  function prepareTaskBlocks (line 1267) | function prepareTaskBlocks(item)
  function prepareTaskTitleInlineHtml (line 1305) | function prepareTaskTitleInlineHtml(s)
  function prepareListNameInline (line 1312) | function prepareListNameInline(item)
  function prepareTaskNoteInlineHtml (line 1320) | function prepareTaskNoteInlineHtml(s, rawText)
  function preparePrio (line 1327) | function preparePrio(prio,id)
  function prepareTagsStr (line 1337) | function prepareTagsStr(item, delimiter = ', ')
  function prepareDomClassOfTags (line 1351) | function prepareDomClassOfTags(ids)
  function prepareDueDate (line 1363) | function prepareDueDate(item)
  function prepareInlineDate (line 1370) | function prepareInlineDate(item)
  function submitNewTask (line 1386) | function submitNewTask(form)
  function changeTaskOrder (line 1407) | function changeTaskOrder(id)
  function prioPopup (line 1509) | function prioPopup(act, el, id)
  function prioClick (line 1522) | function prioClick(prio, el)
  function setTaskPrio (line 1530) | function setTaskPrio(id, prio)
  function setSort (line 1540) | function setSort(v, init)
  function updateSortUI (line 1550) | function updateSortUI(v)
  function changeTaskCnt (line 1567) | function changeTaskCnt(task, dir, old)
  function refreshTaskCnt (line 1587) | function refreshTaskCnt()
  function setTaskview (line 1598) | function setTaskview(v)
  function toggleAllNotes (line 1622) | function toggleAllNotes(show, event)
  function tabSelect (line 1637) | function tabSelect(elementOrId)
  function listMenu (line 1713) | function listMenu(el)
  function listMenuClick (line 1719) | function listMenuClick(el, menu)
  function listMenuHover (line 1745) | function listMenuHover(el, menu)
  function deleteTask (line 1755) | function deleteTask(id)
  function completeTask (line 1773) | function completeTask(id, ch)
  function toggleTaskNote (line 1802) | function toggleTaskNote(id)
  function cancelTaskNote (line 1818) | function cancelTaskNote(id)
  function saveTaskNote (line 1826) | function saveTaskNote(id)
  function fillTaskViewer (line 1841) | function fillTaskViewer(id)
  function viewTask (line 1862) | function viewTask(id)
  function editTask (line 1871) | function editTask(id)
  function clearEditForm (line 1904) | function clearEditForm()
  function showEditForm (line 1916) | function showEditForm(isAdd)
  function saveTask (line 1952) | function saveTask(form)
  function toggleEditAllTags (line 1992) | function toggleEditAllTags(show)
  function fillEditAllTags (line 2012) | function fillEditAllTags()
  function addEditTag (line 2023) | function addEditTag(tag)
  function loadTags (line 2034) | function loadTags(listId, callback)
  function setTagcloudContent (line 2047) | function setTagcloudContent(tags, isFiltered = false)
  function cancelTagFilter (line 2064) | function cancelTagFilter(tagId, dontLoadTasks)
  function addFilterTag (line 2071) | function addFilterTag(tag, tagId, exclude)
  function searchTags (line 2078) | function searchTags()
  function liveSearchToggle (line 2094) | function liveSearchToggle(toSearch, dontLoad)
  function searchTasks (line 2115) | function searchTasks(force)
  function submitFullTask (line 2130) | function submitFullTask(form)
  function tasklistSortStart (line 2170) | function tasklistSortStart(event, ui)
  function tasklistSortUpdated (line 2176) | function tasklistSortUpdated(event, ui)
  function mttMenu (line 2229) | function mttMenu(container, options)
  function taskContextMenu (line 2470) | function taskContextMenu(el, id)
  function taskContextClick (line 2489) | function taskContextClick(el, menu)
  function moveTaskToList (line 2511) | function moveTaskToList(taskId, listId)
  function cmenuOnListsLoaded (line 2544) | function cmenuOnListsLoaded()
  function cmenuOnListAdded (line 2556) | function cmenuOnListAdded(list)
  function cmenuOnListRenamed (line 2563) | function cmenuOnListRenamed(list)
  function cmenuOnListSelected (line 2568) | function cmenuOnListSelected(a)
  function cmenuOnListOrderChanged (line 2575) | function cmenuOnListOrderChanged()
  function cmenuOnListHidden (line 2581) | function cmenuOnListHidden(list)
  function tabmenuOnListSelected (line 2588) | function tabmenuOnListSelected(a)
  function listOrderChanged (line 2616) | function listOrderChanged(event, ui)
  function showCompletedToggle (line 2628) | function showCompletedToggle()
  function clearCompleted (line 2637) | function clearCompleted()
  function showhide (line 2650) | function showhide(a,b)
  function findParentNode (line 2656) | function findParentNode(el, node)
  function getLiTaskId (line 2667) | function getLiTaskId(el)
  function isParentId (line 2674) | function isParentId(el, id)
  function dehtml (line 2681) | function dehtml(str)
  function escapeHtml (line 2686) | function escapeHtml(str) {
  function slmenuOnListsLoaded (line 2698) | function slmenuOnListsLoaded()
  function slmenuOnListRenamed (line 2716) | function slmenuOnListRenamed(list)
  function slmenuOnListAdded (line 2721) | function slmenuOnListAdded(list)
  function slmenuOnListSelected (line 2731) | function slmenuOnListSelected(a)
  function slmenuOnListHidden (line 2739) | function slmenuOnListHidden(list)
  function slmenuSelect (line 2745) | function slmenuSelect(el, menu)
  function hideList (line 2760) | function hideList(listId)
  function getLocalStorageItem (line 2802) | function getLocalStorageItem(key)
  function setLocalStorageItem (line 2813) | function setLocalStorageItem(key, value)
  function newTaskCounterStart (line 2823) | function newTaskCounterStart()
  function newTaskCounter (line 2829) | function newTaskCounter()
  function setNewTaskCounterForList (line 2876) | function setNewTaskCounterForList(listId, counter)
  function newTaskCounterOnListSelected (line 2894) | function newTaskCounterOnListSelected(a)
  function newTaskCounterUpdated (line 2904) | function newTaskCounterUpdated()
  function flashError (line 2979) | function flashError(str, details)
  function flashInfo (line 2988) | function flashInfo(str, details)
  function hideAlert (line 2997) | function hideAlert()
  function updateAccessStatus (line 3008) | function updateAccessStatus()
  function showLogin (line 3040) | function showLogin()
  function doAuth (line 3049) | function doAuth(form)
  function logout (line 3066) | function logout()
  function showSettings (line 3081) | function showSettings(json = 0)
  function saveSettings (line 3098) | function saveSettings(frm)
  function activateExtension (line 3114) | function activateExtension(activate, ext)
  function showExtensionSettings (line 3128) | function showExtensionSettings(ext, callback, reload)
  function saveExtensionSettings (line 3142) | function saveExtensionSettings(frm)
  function extensionSettingsAction (line 3166) | function extensionSettingsAction(actionString, ext, formData)
  function mttConfirm (line 3234) | function mttConfirm(msg, callbackOk, callbackCancel)
  function mttPrompt (line 3239) | function mttPrompt(msg, defaultValue, callbackOk, callbackCancel)
  function mttAlert (line 3244) | function mttAlert(msg, callbackOk)
  function mttModalDialog (line 3249) | function mttModalDialog(dialogType = 'alert')
  function updateHistoryState (line 3360) | function updateHistoryState(state, url, title)
  function replaceHistoryState (line 3383) | function replaceHistoryState(param, _state, url, title)
  function historyOnPopState (line 3405) | function historyOnPopState(event)

FILE: src/content/mytinytodo_api.js
  function MytinytodoAjaxApi (line 12) | function MytinytodoAjaxApi(props)
  method request (line 35) | request(action, params, callback) {
  method loadTasks (line 50) | loadTasks(params, callback) {
  method newTask (line 63) | newTask(params, callback) {
  method fullNewTask (line 80) | fullNewTask(params, callback) {
  method editTask (line 101) | editTask(params, callback) {
  method editNote (line 120) | editNote(params, callback) {
  method completeTask (line 135) | completeTask(params, callback) {
  method deleteTask (line 150) | deleteTask(params, callback) {
  method setTaskPriority (line 164) | setTaskPriority(params, callback) {
  method changeOrder (line 178) | changeOrder(params, callback) {
  method suggestTags (line 192) | suggestTags(params, callback) {
  method tagCloud (line 196) | tagCloud(params, callback) {
  method moveTask (line 200) | moveTask(params, callback) {
  method parseTaskStr (line 215) | parseTaskStr(params, callback) {
  method newTaskCounter (line 230) | newTaskCounter(params, callback) {
  method loadLists (line 255) | loadLists(params, callback) {
  method addList (line 259) | addList(params, callback) {
  method deleteList (line 274) | deleteList(params, callback) {
  method renameList (line 287) | renameList(params, callback) {
  method setSort (line 301) | setSort(params, callback) {
  method publishList (line 316) | publishList(params, callback) {
  method enableFeedKey (line 330) | enableFeedKey(params, callback) {
  method setShowNotesInList (line 344) | setShowNotesInList(params, callback) {
  method setHideList (line 358) | setHideList(params, callback) {
  method changeListOrder (line 372) | changeListOrder(params, callback) {
  method clearCompletedInList (line 386) | clearCompletedInList(params, callback) {
  method login (line 401) | login(params, callback) {
  method logout (line 414) | logout(params, callback) {

FILE: src/content/theme/images/svg2base64.php
  function base64file (line 43) | function base64file(string $filename): string
  function cleanXml (line 51) | function cleanXml(string $data): string

FILE: src/export.php
  function printCSV (line 37) | function printCSV(array $listData, array $data)
  function escape_csv (line 55) | function escape_csv(string $v)
  function printICal (line 66) | function printICal(array $listData, array $data)
  function utf8chunks (line 131) | function utf8chunks($text, $chunklen=75, $delimiter="\r\n\t")

FILE: src/ext/CustomCSS/loader.php
  function mtt_ext_customcss_instance (line 13) | function mtt_ext_customcss_instance(): MTTExtension
  class CustomCssExtension (line 18) | class CustomCssExtension extends MTTExtension implements MTTExtensionSet...
    method init (line 28) | function init()
    method settingsPage (line 40) | function settingsPage(): string
    method settingsPageType (line 57) | function settingsPageType(): int
    method saveSettings (line 62) | function saveSettings(array $params, ?string &$outMessage): bool
    method preferences (line 83) | static function preferences(): array

FILE: src/ext/_examples/CustomSmartSyntax/loader.php
  function mtt_ext_customsmartsyntax_instance (line 13) | function mtt_ext_customsmartsyntax_instance(): MTTExtension
  class CustomSmartSyntaxExtension (line 18) | class CustomSmartSyntaxExtension extends MTTExtension implements MTTFilt...
    method init (line 23) | function init() {
    method filter (line 28) | function filter($title, &$out)

FILE: src/ext/backup/class.backup.php
  class Backup (line 13) | class Backup
    method __construct (line 21) | function __construct(?string $filename)
    method isFileWritable (line 26) | function isFileWritable()
    method makeBackup (line 37) | function makeBackup()
    method writeTable (line 82) | function writeTable(string $table, string $group, string $itemName)
    method writeItem (line 101) | function writeItem(string $entity, $r)
    method getTableAutoIncrement (line 121) | function getTableAutoIncrement($table): string
    method writeOpeningTag (line 145) | function writeOpeningTag(string $tag, ?array $attrs = null)
    method writeClosingTag (line 173) | function writeClosingTag(string $tag)
    method writeTagContent (line 188) | function writeTagContent(?string $content)
    method write (line 195) | function write(string $data)

FILE: src/ext/backup/class.check.php
  class Check (line 13) | class Check
    method check (line 18) | function check(): bool
    method repair (line 82) | function repair(): bool

FILE: src/ext/backup/class.controller.php
  class Controller (line 16) | class Controller extends \ApiController
    method postMakeBackup (line 18) | function postMakeBackup()
    method postDownload (line 40) | function postDownload()
    method getDownload (line 60) | function getDownload()
    method postRestore (line 79) | function postRestore()
    method postCheckInconsistency (line 109) | function postCheckInconsistency()
    method postRepairInconsistency (line 135) | function postRepairInconsistency()

FILE: src/ext/backup/class.download.php
  class Download (line 13) | class Download
    method __construct (line 19) | function __construct(?string $filename)
    method checkFileAccess (line 24) | function checkFileAccess(?string $tokenHash = null): bool
    method downloadUrl (line 50) | function downloadUrl()
    method printFile (line 58) | function printFile()

FILE: src/ext/backup/class.restore.php
  class Restore (line 15) | class Restore
    method __construct (line 23) | function __construct()
    method isUploaded (line 35) | function isUploaded(): bool
    method restore (line 50) | function restore(): bool
    method moveNextElement (line 98) | function moveNextElement(?string $el = null): ?bool
    method moveNextElementSameLevel (line 114) | function moveNextElementSameLevel(?string $el = null)
    method readTable (line 119) | function readTable(string $table, string $itemName): ?int
    method insertToTable (line 168) | private function insertToTable(string $table, \SimpleXMLElement $xml):...
    method updateAutoinc (line 210) | private function updateAutoinc(string $table, int $autoinc)
    method beginRestore (line 228) | private function beginRestore()
    method endRestore (line 247) | private function endRestore()

FILE: src/ext/backup/loader.php
  function mtt_ext_backup_instance (line 15) | function mtt_ext_backup_instance(): MTTExtension
  class BackupExtension (line 22) | class BackupExtension extends MTTExtension implements MTTExtensionSettin...
    method init (line 30) | function init() {
    method extendHttpApi (line 34) | function extendHttpApi(): array
    method settingsPage (line 57) | function settingsPage(): string
    method settingsPageType (line 116) | function settingsPageType(): int
    method saveSettings (line 121) | function saveSettings(array $params, ?string &$outMessage): bool
    method backupFilePath (line 133) | static function backupFilePath()

FILE: src/ext/notifications/class.controller.php
  class Controller (line 14) | class Controller extends \ApiController
    method postDeactivateAll (line 16) | function postDeactivateAll()
    method postCheck (line 26) | function postCheck()

FILE: src/ext/notifications/class.observer.php
  class NotificationObserver (line 17) | class NotificationObserver implements \MTTNotificationObserverInterface
    method notification (line 22) | public function notification(string $notification, $object)
    method processDelayed (line 49) | private function processDelayed()
    method init (line 59) | private function init()

FILE: src/ext/notifications/class.sender.php
  class Sender (line 15) | class Sender
    method __construct (line 20) | function __construct(array $prefs, bool $useCli = false)
    method notify (line 28) | function notify(array $item)
    method notifyTaskCreated (line 41) | private function notifyTaskCreated($task)
    method notifyListCreated (line 83) | private function notifyListCreated($list)
    method sendEmails (line 107) | private function sendEmails(string $text, string $subject)
    method sendTelegrams (line 137) | private function sendTelegrams(string $text)
    method sendTelegramsWithApi (line 148) | function sendTelegramsWithApi(string $text)
    method sendTelegramsInBackground (line 177) | private function sendTelegramsInBackground(string $text)
    method suggestedMailFrom (line 191) | public static function suggestedMailFrom(): string

FILE: src/ext/notifications/class.telegramapi.php
  class TelegramApi (line 11) | class TelegramApi
    method __construct (line 19) | function __construct(string $token)
    method getMe (line 24) | function getMe(): ?array
    method getUpdates (line 29) | function getUpdates(?array $params = null): ?array
    method sendMessage (line 34) | function sendMessage(array $params): ?array
    method makeGetRequest (line 39) | private function makeGetRequest(string $method): ?array
    method makePostRequest (line 70) | private function makePostRequest(string $method, array $params): ?array
    method decodeBody (line 105) | private function decodeBody(string $body, string $method = ''): array

FILE: src/ext/notifications/loader.php
  function mtt_ext_notifications_instance (line 31) | function mtt_ext_notifications_instance(): MTTExtension
  class NotificationsExtension (line 40) | class NotificationsExtension extends MTTExtension implements MTTHttpApiE...
    method init (line 48) | function init()
    method extendHttpApi (line 58) | function extendHttpApi(): array
    method settingsPage (line 70) | function settingsPage(): string
    method settingsPageType (line 161) | function settingsPageType(): int
    method saveSettings (line 166) | function saveSettings(array $params, ?string &$outMessage): bool
    method preferences (line 233) | static function preferences(): array

FILE: src/ext/updater/class.controller.php
  class Controller (line 14) | class Controller extends \ApiController
    method postCheck (line 16) | function postCheck()
    method postUpdate (line 37) | function postUpdate()

FILE: src/ext/updater/class.updater.php
  class Updater (line 11) | class Updater
    method requestJson (line 15) | public function requestJson(string $url): ?string
    method lastVersionInfo (line 41) | public function lastVersionInfo(): ?array
    method download (line 88) | public function download(string $url, string $outfile): bool
    method extractAndReplace (line 112) | public function extractAndReplace(string $filename): bool

FILE: src/ext/updater/loader.php
  function mtt_ext_updater_instance (line 16) | function mtt_ext_updater_instance(): MTTExtension
  class UpdaterExtension (line 24) | class UpdaterExtension extends MTTExtension implements MTTExtensionSetti...
    method init (line 32) | function init()
    method extendHttpApi (line 37) | function extendHttpApi(): array
    method settingsPage (line 49) | function settingsPage(): string
    method settingsPageType (line 113) | function settingsPageType(): int
    method saveSettings (line 118) | function saveSettings(array $params, ?string &$outMessage): bool
    method preferences (line 124) | static function preferences(): array

FILE: src/feed.php
  function fillData (line 65) | function fillData(array &$data, int $listId, string $field, string $sqlW...
  function printRss (line 99) | function printRss(array $data, array $listData)

FILE: src/includes/api/AuthController.php
  class AuthController (line 9) | class AuthController extends ApiController {
    method postAction (line 11) | function postAction($action)
    method login (line 21) | private function login(): ?array
    method logout (line 38) | private function logout(): ?array
    method createSession (line 48) | private function createSession(): ?array

FILE: src/includes/api/ExtSettingsController.php
  class ExtSettingsController (line 3) | class ExtSettingsController extends ApiController {
    method get (line 10) | function get(string $ext)
    method put (line 71) | function put(string $ext)
    method extInstance (line 89) | private function extInstance(string $ext): ?MTTExtensionSettingsInterface

FILE: src/includes/api/ListsController.php
  class ListsController (line 9) | class ListsController extends ApiController {
    method get (line 16) | function get()
    method post (line 48) | function post()
    method put (line 64) | function put()
    method getId (line 83) | function getId($id)
    method deleteId (line 102) | function deleteId($id)
    method putId (line 116) | function putId($id)
    method prepareAllTasksList (line 138) | private function prepareAllTasksList(): array
    method getListRowById (line 162) | private function getListRowById(int $id)
    method prepareList (line 171) | private function prepareList($row, bool $haveWriteAccess): array
    method createList (line 196) | private function createList(): ?array
    method renameList (line 213) | private function renameList(int $id): ?array
    method sortList (line 228) | private function sortList(int $listId): ?array
    method setListSortingById (line 235) | static function setListSortingById(int $listId, int $sort)
    method setListShowCompletedById (line 251) | static function setListShowCompletedById(int $listId, bool $showComple...
    method publishList (line 265) | private function publishList(int $listId): ?array
    method enableFeedKey (line 273) | private function enableFeedKey(int $listId): ?array
    method showNotes (line 300) | private function showNotes(int $listId): ?array
    method hideList (line 309) | private function hideList(int $listId): ?array
    method clearCompleted (line 325) | private function clearCompleted(int $listId): ?array
    method changeListOrder (line 345) | private function changeListOrder(): ?array
    method deleteList (line 369) | private function deleteList(int $id)

FILE: src/includes/api/TagsController.php
  class TagsController (line 9) | class TagsController extends ApiController {
    method getCloud (line 16) | function getCloud($listId)
    method getSuggestions (line 69) | function getSuggestions($listId)
    method tagWeight (line 89) | private function tagWeight(int $qmin, int $q, float $step): float

FILE: src/includes/api/TasksController.php
  class TasksController (line 11) | class TasksController extends ApiController {
    method get (line 19) | function get()
    method post (line 166) | function post()
    method put (line 190) | function put()
    method deleteId (line 207) | function deleteId($id)
    method putId (line 219) | function putId($id)
    method postTitleParse (line 247) | function postTitleParse()
    method postNewCounter (line 270) | function postNewCounter()
    method newTaskInList (line 330) | private function newTaskInList(int $listId): ?array
    method fullNewTaskInList (line 379) | private function fullNewTaskInList(int $listId): ?array
    method editTask (line 417) | private function editTask(int $id): ?array
    method moveTask (line 449) | private function moveTask(int $id): ?array
    method doMoveTask (line 475) | private function doMoveTask(int $id, int $listId, &$listName): bool
    method completeTask (line 499) | private function completeTask(int $id): ?array
    method editNote (line 518) | private function editNote(int $id): ?array
    method priorityTask (line 537) | private function priorityTask(int $id): ?array
    method changeTaskOrder (line 558) | private function changeTaskOrder(): ?array
    method deleteTask (line 584) | private function deleteTask(int $id)
    method getUserListsSimple (line 607) | private function getUserListsSimple(bool $readOnly = false): array
    method getTaskRowById (line 622) | private function getTaskRowById(int $id, bool $getListName = false): ?...
    method prepareTaskRow (line 635) | private function prepareTaskRow(array $r): array
    method prepareDuedate (line 687) | private function prepareDuedate($duedate): array
    method date2int (line 758) | private function date2int($d) : int
    method getTagId (line 770) | private function getTagId($tag)
    method getOrCreateTag (line 777) | private function getOrCreateTag($name): array
    method prepareTags (line 791) | private function prepareTags(string $tagsStr): ?array
    method addTaskTags (line 811) | private function addTaskTags(int $taskId, array $tagIds, int $listId)

FILE: src/includes/class.config.php
  class Config (line 9) | class Config
    method loadConfigV14 (line 110) | public static function loadConfigV14(array $config)
    method load (line 135) | public static function load()
    method get (line 154) | public static function get($key)
    method getUrl (line 167) | public static function getUrl($key)
    method set (line 183) | public static function set($key, $value)
    method save (line 197) | public static function save()
    method requestDomain (line 227) | public static function requestDomain(string $key): array
    method requestDefaultDomain (line 246) | public static function requestDefaultDomain(): array
    method saveDomain (line 259) | public static function saveDomain($key, $array)
    method defineDbConstants (line 275) | public static function defineDbConstants()
    method dbConfigAsFileContents (line 288) | public static function dbConfigAsFileContents(): string
    method prepareDbDefine (line 304) | private static function prepareDbDefine(string $key, string $value): s...
    method saveDbConfig (line 319) | public static function saveDbConfig()

FILE: src/includes/class.db.mysql.php
  class DatabaseResult_Mysql (line 10) | class DatabaseResult_Mysql extends DatabaseResult_Abstract
    method __construct (line 18) | function __construct(PDO $dbh, string $query, bool $resultless = false)
    method fetchRow (line 33) | function fetchRow(): ?array
    method fetchAssoc (line 42) | function fetchAssoc(): ?array
    method rowsAffected (line 51) | function rowsAffected(): int
  class Database_Mysql (line 58) | class Database_Mysql extends Database_Abstract
    method __construct (line 70) | function __construct()
    method connect (line 74) | function connect(array $params): void
    method sq (line 94) | function sq(string $query, ?array $values = null)
    method sqa (line 110) | function sqa(string $query, ?array $values = null): ?array
    method dq (line 120) | function dq(string $query, ?array $values = null) : DatabaseResult_Abs...
    method ex (line 128) | function ex(string $query, ?array $values = null): void
    method _dq (line 133) | private function _dq(string $query, ?array $values = null, bool $resul...
    method affected (line 156) | function affected(): int
    method quote (line 161) | function quote($value): string
    method quoteForLike (line 169) | function quoteForLike(string $format, string $string): string
    method like (line 175) | function like(string $column, string $format, string $string): string
    method ciEquals (line 181) | function ciEquals(string $column, string $value): string
    method lastInsertId (line 187) | function lastInsertId(?string $name = null): ?string
    method tableExists (line 196) | function tableExists(string $table): bool
    method tableFieldExists (line 204) | function tableFieldExists(string $table, string $field): bool

FILE: src/includes/class.db.mysqli.php
  class DatabaseResult_Mysqli (line 10) | class DatabaseResult_Mysqli extends DatabaseResult_Abstract
    method __construct (line 15) | function __construct(mysqli $dbh, string $query, bool $resultless = fa...
    method fetchRow (line 20) | function fetchRow(): ?array
    method fetchAssoc (line 29) | function fetchAssoc(): ?array
  class Database_Mysqli (line 40) | class Database_Mysqli extends Database_Abstract
    method __construct (line 48) | function __construct()
    method connect (line 54) | function connect(array $params): void
    method lastInsertId (line 64) | function lastInsertId(?string $name = null): ?string
    method sq (line 69) | function sq(string $query, ?array $values = null)
    method sqa (line 85) | function sqa(string $query, ?array $values = null): ?array
    method dq (line 95) | function dq(string $query, ?array $values = null) : DatabaseResult_Abs...
    method ex (line 103) | function ex(string $query, ?array $values = null): void
    method _dq (line 108) | private function _dq(string $query, ?array $values = null, bool $resul...
    method affected (line 129) | function affected(): int
    method quote (line 134) | function quote($value): string
    method quoteForLike (line 142) | function quoteForLike(string $format, string $string): string
    method like (line 148) | function like(string $column, string $format, string $string): string
    method ciEquals (line 154) | function ciEquals(string $column, string $value): string
    method tableExists (line 160) | function tableExists(string $table): bool
    method tableFieldExists (line 168) | function tableFieldExists(string $table, string $field): bool

FILE: src/includes/class.db.postgres.php
  class DatabaseResult_Postgres (line 10) | class DatabaseResult_Postgres extends DatabaseResult_Abstract
    method __construct (line 18) | function __construct(PDO $dbh, string $query, bool $resultless = false)
    method fetchRow (line 33) | function fetchRow(): ?array
    method fetchAssoc (line 42) | function fetchAssoc(): ?array
    method rowsAffected (line 51) | function rowsAffected(): int
  class Database_Postgres (line 58) | class Database_Postgres extends Database_Abstract
    method __construct (line 73) | function __construct()
    method connect (line 77) | function connect(array $params): void
    method sq (line 97) | function sq(string $query, ?array $values = null)
    method sqa (line 113) | function sqa(string $query, ?array $values = null): ?array
    method dq (line 123) | function dq(string $query, ?array $values = null) : DatabaseResult_Abs...
    method ex (line 131) | function ex(string $query, ?array $values = null): void
    method _dq (line 136) | private function _dq(string $query, ?array $values = null, bool $resul...
    method affected (line 159) | function affected(): int
    method quote (line 164) | function quote($value): string
    method quoteForLike (line 172) | function quoteForLike(string $format, string $string): string
    method like (line 178) | function like(string $column, string $format, string $string): string
    method ciEquals (line 184) | function ciEquals(string $column, string $value): string
    method lastInsertId (line 190) | function lastInsertId(?string $name = null): ?string
    method tableExists (line 199) | function tableExists(string $table): bool
    method tableFieldExists (line 207) | function tableFieldExists(string $table, string $field): bool

FILE: src/includes/class.db.sqlite3.php
  class DatabaseResult_Sqlite3 (line 9) | class DatabaseResult_Sqlite3 extends DatabaseResult_Abstract
    method __construct (line 17) | function __construct(PDO $dbh, string $query, bool $resultless = false)
    method fetchRow (line 32) | function fetchRow(): ?array
    method fetchAssoc (line 41) | function fetchAssoc(): ?array
    method rowsAffected (line 50) | function rowsAffected(): int
  class Database_Sqlite3 (line 57) | class Database_Sqlite3 extends Database_Abstract
    method __construct (line 70) | function __construct(?array $params = null)
    method connect (line 79) | function connect(array $params): void
    method sq (line 105) | function sq(string $query, ?array $values = null)
    method sqa (line 121) | function sqa(string $query, ?array $values = null): ?array
    method dq (line 134) | function dq(string $query, ?array $values = null) : DatabaseResult_Abs...
    method ex (line 142) | function ex(string $query, ?array $values = null): void
    method _dq (line 147) | private function _dq(string $query, ?array $values = null, bool $resul...
    method affected (line 170) | function affected(): int
    method quote (line 175) | function quote($value): string
    method quoteForLike (line 183) | function quoteForLike(string $format, string $string): string
    method like (line 192) | function like(string $column, string $format, string $string): string
    method ciEquals (line 201) | function ciEquals(string $column, string $value): string
    method lastInsertId (line 210) | function lastInsertId(?string $name = null): ?string
    method tableExists (line 219) | function tableExists(string $table): bool
    method tableFieldExists (line 232) | function tableFieldExists(string $table, string $field): bool
    method utf8_lower (line 241) | public function utf8_lower($value): string
    method utf8_normalized_lower (line 247) | public function utf8_normalized_lower($value): string
    method collate_utf8ci (line 254) | public function collate_utf8ci(string $str1, string $str2): int
    method collate_utf8ci_normalized (line 259) | public function collate_utf8ci_normalized(string $str1, string $str2):...
    method normalizeValue (line 266) | public static function normalizeValue(string $str): string

FILE: src/includes/class.dbconnection.php
  class DBConnection (line 9) | class DBConnection
    method init (line 17) | public static function init(Database_Abstract $instance) : Database_Ab...
    method instance (line 23) | public static function instance() : Database_Abstract
    method setTablePrefix (line 31) | public static function setTablePrefix($prefix)
  class Database_Abstract (line 38) | abstract class Database_Abstract
    method connect (line 52) | abstract function connect(array $params): void;
    method sq (line 53) | abstract function sq(string $query, ?array $values = null);
    method sqa (line 54) | abstract function sqa(string $query, ?array $values = null): ?array;
    method dq (line 55) | abstract function dq(string $query, ?array $values = null): DatabaseRe...
    method ex (line 56) | abstract function ex(string $query, ?array $values = null): void;
    method affected (line 57) | abstract function affected(): int;
    method quote (line 58) | abstract function quote($value): string;
    method quoteForLike (line 59) | abstract function quoteForLike(string $format, string $string): string;
    method like (line 60) | abstract function like(string $column, string $format, string $string)...
    method ciEquals (line 61) | abstract function ciEquals(string $column, string $value): string;
    method lastInsertId (line 62) | abstract function lastInsertId(?string $name = null): ?string;
    method tableExists (line 63) | abstract function tableExists(string $table): bool;
    method tableFieldExists (line 64) | abstract function tableFieldExists(string $table, string $field): bool;
    method __get (line 66) | function __get(string $propName) {
    method setPrefix (line 73) | function setPrefix(string $prefix): void {
    method setLogQueryToFile (line 80) | function setLogQueryToFile(?string $path) {
    method setLastQuery (line 85) | function setLastQuery(string $lastQuery) {
  class DatabaseResult_Abstract (line 97) | abstract class DatabaseResult_Abstract
    method fetchRow (line 99) | abstract function fetchRow(): ?array;
    method fetchAssoc (line 100) | abstract function fetchAssoc(): ?array;

FILE: src/includes/class.dbcore.php
  class DBCore (line 12) | class DBCore
    method __construct (line 25) | public function __construct(Database_Abstract $db) {
    method connection (line 34) | public function connection()
    method default (line 47) | public static function default() : DBCore
    method setDefaultInstance (line 60) | public static function setDefaultInstance(DBCore $instance)
    method getListIdByTaskId (line 70) | public function getListIdByTaskId(int $id): int
    method getListById (line 78) | public function getListById(int $id): ?array
    method taskExists (line 86) | public function taskExists(int $id): bool
    method getTaskById (line 94) | public function getTaskById(int $id): ?array
    method getTasksByListId (line 123) | public function getTasksByListId(int $listId, string $sqlWhere, /* int...
    method createListWithName (line 181) | function createListWithName(string $name): ?int
    method getTagIdsByName (line 202) | function getTagIdsByName(string $name): array

FILE: src/includes/class.lang.php
  class Lang (line 13) | class Lang
    method instance (line 21) | public static function instance(): Lang
    method loadLangOrDie (line 30) | public static function loadLangOrDie($code, $die = 1)
    method loadLang (line 49) | public static function loadLang($code)
    method langExists (line 54) | public static function langExists($code)
    method loadJsonString (line 59) | function loadJsonString($code, $jsonString)
    method loadDefaultStrings (line 78) | function loadDefaultStrings()
    method get (line 90) | function get($key)
    method hasKey (line 98) | function hasKey(string $key): bool
    method rtl (line 103) | function rtl()
    method jsStrings (line 112) | function jsStrings(bool $escape = true)
    method fillWithValues (line 152) | protected function fillWithValues(array &$a, array $keys)
    method langDir (line 159) | function langDir()
    method langCode (line 164) | function langCode()
    method getExtensionLang (line 169) | public function getExtensionLang(string $ext): ?array
    method loadExtensionLang (line 201) | public function loadExtensionLang(string $ext)

FILE: src/includes/class.sessionhandler.php
  class MTTSessionHandler (line 9) | class MTTSessionHandler implements SessionHandlerInterface, SessionUpdat...
    method open (line 24) | public function open($path, $name): bool
    method close (line 33) | public function close(): bool
    method read (line 43) | #[\ReturnTypeWillChange]
    method write (line 78) | public function write($id, $data): bool
    method destroy (line 106) | public function destroy($id): bool
    method gc (line 116) | #[\ReturnTypeWillChange]
    method validateId (line 131) | public function validateId($id): bool
    method updateTimestamp (line 145) | public function updateTimestamp($id, $data): bool

FILE: src/includes/classes.php
  class ApiRequest (line 9) | class ApiRequest
    method __construct (line 16) | function __construct() {
    method decodeJsonBody (line 27) | function decodeJsonBody() {
  class ApiResponse (line 33) | class ApiResponse
    method content (line 39) | function content(string $contentType, string $content, int $code = 200)
    method htmlContent (line 47) | function htmlContent(string $content, int $code = 200): ApiResponse
    method cssContent (line 52) | function cssContent(string $content, int $code = 200): ApiResponse
    method exit (line 57) | function  exit()
  class ApiController (line 74) | abstract class ApiController
    method __construct (line 82) | function __construct(ApiRequest $req, ApiResponse $response) {
  class MTTExtension (line 90) | abstract class MTTExtension
    method init (line 94) | function init() {
    method extMetaInfo (line 97) | public static function extMetaInfo(string $ext): ?array
    method extApiActionUrl (line 118) | public static function extApiActionUrl(string $action, ?string $params...
    method getFileVer (line 132) | public static function getFileVer(string $filename): string
    method getFileUri (line 137) | public static function getFileUri(string $filename, bool $versioned = ...
  type MTTHttpApiExtender (line 145) | interface MTTHttpApiExtender
    method extendHttpApi (line 147) | function extendHttpApi(): array;
  type MTTExtensionSettingsInterface (line 150) | interface MTTExtensionSettingsInterface
    method settingsPage (line 152) | function settingsPage(): string;
    method settingsPageType (line 153) | function settingsPageType(): int;
    method saveSettings (line 154) | function saveSettings(array $array, ?string &$outMesssage): bool;
  class MTTExtensionLoaderException (line 157) | class MTTExtensionLoaderException extends Exception {}
  class MTTExtensionLoader (line 159) | class MTTExtensionLoader
    method loadExtension (line 167) | public static function loadExtension(string $ext): bool
    method loadedExtensions (line 212) | public static function loadedExtensions(): array
    method bundles (line 224) | public static function bundles(): array
    method extensionInstance (line 250) | public static function extensionInstance(string $ext): ?MTTExtension
    method isLoaded (line 255) | public static function isLoaded(string $ext): bool

FILE: src/includes/common.php
  function htmlarray (line 9) | function htmlarray($a, $exclude=null)
  function htmlarray_ref (line 15) | function htmlarray_ref(&$a, $exclude=null)
  function _post (line 33) | function _post($param,$defvalue = '')
  function _get (line 43) | function _get($param,$defvalue = '')
  function _server (line 53) | function _server($param, $defvalue = '')
  function formatDate3 (line 63) | function formatDate3($format, $ay, $am, $ad, $lang)
  function daysInMonth (line 83) | function daysInMonth(int $m, int $y = 0): int
  function getRequestUri (line 93) | function getRequestUri()
  function url_dir (line 111) | function url_dir(string $url, bool $onlyPath = true)
  function removeNewLines (line 132) | function removeNewLines($s)
  function generateUUID (line 141) | function generateUUID(): string
  function passwordHash (line 154) | function passwordHash(string $p): string
  function isPasswordEqualsToHash (line 166) | function isPasswordEqualsToHash(string $p, string $hash): bool
  function idSignature (line 178) | function idSignature(string $id, string $key, string $salt): string
  function isValidSignature (line 184) | function isValidSignature(string $signature, string $id, string $key, st...
  function randomString (line 191) | function randomString(int $len = 16, string $chars = '0123456789abcdefgh...
  function array_is_list (line 208) | function array_is_list(array $array): bool

FILE: src/includes/filters.php
  class MTTFilterCenter (line 9) | class MTTFilterCenter
    method addFilterCallbackForAction (line 13) | public static function addFilterCallbackForAction(string $action, call...
    method addFilterForAction (line 27) | public static function addFilterForAction(string $action, MTTFilterInt...
    method hasFiltersForAction (line 41) | public static function hasFiltersForAction(string $action): bool
    method filter (line 50) | public static function filter(string $action, $in, &$out): bool
  type MTTFilterInterface (line 67) | interface MTTFilterInterface
    method filter (line 69) | function filter($in, &$out);
  function add_filter (line 72) | function add_filter(string $action, MTTFilterInterface $filter) {
  function add_filter_callback (line 76) | function add_filter_callback(string $action, callable $callback) {
  function do_filter (line 80) | function do_filter(string $action, $in, &$out): bool {

FILE: src/includes/lang/_percent.php
  function checkLang (line 76) | function checkLang(array $src, string $file) : int
  function checkArray (line 84) | function checkArray(string $file, array $src, ?array $a) : int

FILE: src/includes/markup.commonmark.php
  class MTTCommonmarkWrapper (line 18) | class MTTCommonmarkWrapper implements MTTMarkdownInterface
    method __construct (line 25) | function __construct()
    method convert (line 56) | public function convert(string $s, bool $toExternal = false)

FILE: src/includes/markup.parsedown.php
  class MTTParsedownWrapper (line 12) | class MTTParsedownWrapper implements MTTMarkdownInterface
    method __construct (line 17) | function __construct()
    method convert (line 24) | public function convert(string $s, bool $toExternal = false)
  class MTTParsedown (line 32) | class MTTParsedown extends Parsedown
    method __construct (line 37) | function __construct()
    method setToExternal (line 45) | public function setToExternal(bool $v)
    method inlineTaskId (line 50) | protected function inlineTaskId($excerpt)
    method inlineLink (line 77) | protected function inlineLink($Excerpt) {
    method inlineUrl (line 85) | protected function inlineUrl($Excerpt) {

FILE: src/includes/markup.php
  type MTTMarkdownInterface (line 12) | interface MTTMarkdownInterface
    method convert (line 14) | public function convert(string $s, bool $toExternal = false);
  class MTTMarkdown (line 17) | final class MTTMarkdown
    method instance (line 30) | public static function instance() : MTTMarkdownInterface
    method setInstanceClass (line 43) | public static function setInstanceClass(string $class)
  type MTTTitleMarkupInterface (line 53) | interface MTTTitleMarkupInterface
    method convert (line 55) | public function convert(string $title): string;
  class MTTTitleMarkupConverter (line 58) | class MTTTitleMarkupConverter implements MTTTitleMarkupInterface
    method convert (line 60) | public function convert(string $title): string
  class MTTTitleMarkup (line 82) | final class MTTTitleMarkup
    method instance (line 90) | public static function instance() : MTTTitleMarkupInterface
    method setInstanceClass (line 99) | public static function setInstanceClass(string $class)
  function noteMarkup (line 109) | function noteMarkup($note, $toExternal = false)
  function markdownToHtml (line 120) | function markdownToHtml($s, $toExternal = false)
  function mttMarkup_v1 (line 127) | function mttMarkup_v1($s)
  function titleMarkup (line 158) | function titleMarkup($title)

FILE: src/includes/notifications.php
  class MTTNotificationCenter (line 9) | class MTTNotificationCenter
    method addObserverForNotification (line 21) | public static function addObserverForNotification(string $notification...
    method addObserverForNotifications (line 37) | public static function addObserverForNotifications(array $notification...
    method addCallbackForNotification (line 50) | public static function addCallbackForNotification(string $notification...
    method hasObserversForNotification (line 63) | public static function hasObserversForNotification(string $notificatio...
    method postNotification (line 71) | public static function postNotification(string $notification, $object)
    method postDidFinishRequestNotification (line 90) | public static function postDidFinishRequestNotification()
  type MTTNotificationObserverInterface (line 105) | interface MTTNotificationObserverInterface
    method notification (line 107) | function notification(string $notification, $object);
  class MTTNotification (line 111) | abstract class MTTNotification
  function add_action (line 123) | function add_action(string $notification, callable $callback)
  function do_action (line 128) | function do_action(string $notification, $object = null)

FILE: src/includes/smartsyntax.php
  class MTTSmartSyntax (line 9) | class MTTSmartSyntax implements MTTSmartSyntaxInterface
    method instance (line 18) | public static function instance(): MTTSmartSyntaxInterface
    method parse (line 26) | public function parse(string $title): array
    method findDuedate (line 67) | private function findDuedate(string $s): ?string
    method parseDuedate (line 145) | public static function parseDuedate(string $s): ?string
  type MTTSmartSyntaxInterface (line 195) | interface MTTSmartSyntaxInterface
    method parse (line 197) | public function parse(string $title): array;
  function parseSmartSyntax (line 200) | function parseSmartSyntax(string $title): ?array

FILE: src/includes/version.php
  class Version (line 5) | class Version

FILE: src/index.php
  function parseRoute (line 43) | function parseRoute($queryString)
  function redirectWithHashRoute (line 61) | function redirectWithHashRoute(array $hash, array $q = [])
  function js_options (line 74) | function js_options()

FILE: src/init.php
  function requireConfig (line 78) | function requireConfig()
  function configureDbConnection (line 92) | function configureDbConnection()
  function need_auth (line 173) | function need_auth(): bool
  function is_logged (line 178) | function is_logged(): bool
  function is_readonly (line 186) | function is_readonly(): bool
  function updateSessionLogged (line 192) | function updateSessionLogged(bool $logged)
  function access_token (line 204) | function access_token(): string
  function check_token (line 222) | function check_token()
  function update_token (line 231) | function update_token(): string
  function setup_and_start_session (line 254) | function setup_and_start_session()
  function timestampToDatetime (line 288) | function timestampToDatetime($timestamp, $forceTime = false) : string
  function formatTime (line 297) | function formatTime($format, $timestamp=0) : string
  function _e (line 316) | function _e(string $s)
  function __ (line 321) | function __(string $s, bool $escape = false, ?string $arg = null)
  function mttinfo (line 330) | function mttinfo($v)
  function get_mttinfo (line 335) | function get_mttinfo($v)
  function get_unsafe_mttinfo (line 344) | function get_unsafe_mttinfo($v)
  function reset_mttinfo (line 410) | function reset_mttinfo($key)
  function is_https (line 416) | function is_https(): bool
  function set_nocache_headers (line 431) | function set_nocache_headers()
  function jsonExit (line 439) | function jsonExit($data)
  function logAndDie (line 447) | function logAndDie($userText, $errText = null)
  function loadExtensions (line 459) | function loadExtensions()
  function get_filever (line 487) | function get_filever(string $dir, string $filename, ?string $ext = null)
  function filever (line 517) | function filever(string $dir, string $filename)

FILE: src/mtt-edit-settings.php
  function cmd_read (line 30) | function cmd_read($param) {
  function cmd_write (line 34) | function cmd_write($param, $value) {
  function cmd_password (line 44) | function cmd_password($value) {

FILE: src/mtt-emergency.php
  function exitmsg (line 22) | function exitmsg(?string $text = '') {

FILE: src/settings.php
  function _c (line 108) | function _c($key)
  function getLangs (line 113) | function getLangs()
  function cmpLangs (line 144) | function cmpLangs($a, $b) : int
  function selectOptions (line 150) | function selectOptions($a, $value, $default=null)
  function selectOptionsA (line 166) | function selectOptionsA($a, $key, $default=null)
  function timezoneIdentifiers (line 185) | function timezoneIdentifiers()
  function listExtensions (line 195) | function listExtensions()

FILE: src/setup.php
  function setupToken (line 220) | function setupToken()
  function setSetupToken (line 225) | function setSetupToken() : string
  function checkSetupToken (line 243) | function checkSetupToken()
  function askPasswordV14 (line 251) | function askPasswordV14(
  function generateTokenV14 (line 287) | function generateTokenV14(
  function validateTokenV14 (line 300) | function validateTokenV14(
  function createAllTables (line 327) | function createAllTables($db, $dbtype)
  function createMysqlTables (line 341) | function createMysqlTables(Database_Abstract $db)
  function createPostgresTables (line 426) | function createPostgresTables(Database_Abstract $db)
  function createSqliteTables (line 499) | function createSqliteTables(Database_Abstract $db)
  function databaseVersion (line 576) | function databaseVersion(Database_Abstract $db): string
  function hasMysqlUnicode520 (line 598) | function hasMysqlUnicode520(Database_Abstract $db): bool
  function exitMessage (line 604) | function exitMessage($s)
  function printFooter (line 611) | function printFooter()
  function tryToSaveDbConfig (line 616) | function tryToSaveDbConfig()
  function testConnect (line 632) | function testConnect(&$error)
  function debugExceptionHandler (line 755) | function debugExceptionHandler(Throwable $e)
  function myExceptionHandler (line 762) | function myExceptionHandler(Throwable $e)
  function databaseTypeName (line 775) | function databaseTypeName(Database_Abstract $db)
  function update_14_17 (line 787) | function update_14_17(Database_Abstract $db, $dbtype)
  function update_17_18 (line 864) | function update_17_18(Database_Abstract $db, $dbtype)
Condensed preview — 140 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (850K chars).
[
  {
    "path": ".editorconfig",
    "chars": 583,
    "preview": "# top-most EditorConfig file\nroot = true\n\n# 'insert_final_newline = false' should not remove last empty line\n\n[*]\ncharse"
  },
  {
    "path": ".gitignore",
    "chars": 156,
    "preview": ".DS_Store\nsrc/db/todolist.db*\nsrc/db/config.php\nsrc/db/config-*\nsrc/db/backup.xml*\nsrc/config.php\nsrc/includes/vendor/\ns"
  },
  {
    "path": "README.md",
    "chars": 522,
    "preview": "# myTinyTodo\n\nYour tiny todo list\n\nOriginal website - http://www.mytinytodo.net/\n\n### System requirements\n- PHP 7.2 or g"
  },
  {
    "path": "buildtar.php",
    "chars": 3128,
    "preview": "#!/usr/bin/env php\n<?php\n\n// PHP 5.4 is required\n\nif ( !isset($argv) || !isset($argv[1]) ) {\n    die(\"Usage: buildtar.ph"
  },
  {
    "path": "composer.json",
    "chars": 560,
    "preview": "{\n    \"name\": \"maxpozdeev/mytinytodo\",\n    \"type\": \"project\",\n    \"license\": \"GPL-2.0-or-later\",\n    \"homepage\": \"https:"
  },
  {
    "path": "composer.sh",
    "chars": 180,
    "preview": "#!/bin/sh\n\n#dir=\"$( dirname -- \"$( readlink -f -- \"$0\"; )\"; )\"\ndir=\"$PWD\"\n\napp=$(which podman)\nif [ -z $app ]; then\n  ap"
  },
  {
    "path": "src/.htaccess",
    "chars": 569,
    "preview": "# For REST API in Apache\n#<IfModule mod_rewrite.c>\n#    RewriteEngine On\n#    RewriteCond %{REQUEST_FILENAME} !-f\n#    R"
  },
  {
    "path": "src/COPYRIGHT",
    "chars": 2080,
    "preview": "This program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public Licens"
  },
  {
    "path": "src/LICENSE",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "src/api.php",
    "chars": 7574,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/config-sample.php",
    "chars": 529,
    "preview": "<?php\n\n/*\n  Uncomment the line with MTT_DB_TYPE if you make clean install only.\n  Leave it commented (with # at start) i"
  },
  {
    "path": "src/content/index.html",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/content/js/index.html",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/content/js/jquery.ui.touch-punch.js",
    "chars": 8625,
    "preview": "/*!\n * jQuery UI Touch Punch 1.1.5 as modified by RWAP Software\n * based on original touchpunch v0.2.3 which has not bee"
  },
  {
    "path": "src/content/mytinytodo.js",
    "chars": 110458,
    "preview": "/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2010,2020-2025 Max Pozdeev <maxpozdeev@gmail.com>\n    L"
  },
  {
    "path": "src/content/mytinytodo_api.js",
    "chars": 12499,
    "preview": "/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2010,2020-2025 Max Pozdeev <maxpozdeev@gmail.com>\n    Licens"
  },
  {
    "path": "src/content/theme/dark.css",
    "chars": 2845,
    "preview": "/*\n  This file is a part of myTinyTodo.\n*/\n\n\n/* Dark mode */\n\n/* prefers-color-scheme media query is used in html link t"
  },
  {
    "path": "src/content/theme/images/COPYRIGHT",
    "chars": 372,
    "preview": "rss.svg, rss-disabled.svg - are (or based on) icons by Icons8 from https://icons8.com/icon/13841/rss\ncalendar.svg - icon"
  },
  {
    "path": "src/content/theme/images/index.html",
    "chars": 16,
    "preview": "Place for Images"
  },
  {
    "path": "src/content/theme/images/svg2base64.php",
    "chars": 1354,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev@gmail.com>\n    Licensed "
  },
  {
    "path": "src/content/theme/index.html",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/content/theme/markdown.css",
    "chars": 2715,
    "preview": "/* Markdown notes */\n\n.markdown-note {\n  line-height: 1.3;\n}\n\n.markdown-note > *:first-child { margin-top: 0 !important;"
  },
  {
    "path": "src/content/theme/print.css",
    "chars": 1973,
    "preview": "@media print {\n\n  html,body { height:auto; }\n  h2 { display: none; }\n  h3 { border-bottom:2px solid #777777; }\n  #toolba"
  },
  {
    "path": "src/content/theme/style.css",
    "chars": 51451,
    "preview": "/*\n  This file is a part of myTinyTodo.\n*/\n\n/*\n  Browsers support:\n  Flexbox layout - https://caniuse.com/flexbox\n  CSS "
  },
  {
    "path": "src/content/theme/style_rtl.css",
    "chars": 1941,
    "preview": "body { direction:rtl; }\n.topblock h2 {\n  background-position: right;\n  padding-left: 10px;\n  padding-right: 30px;\n}\n#loa"
  },
  {
    "path": "src/db/.htaccess",
    "chars": 13,
    "preview": "deny from all"
  },
  {
    "path": "src/docker-config.php",
    "chars": 862,
    "preview": "<?php\n\n// Rename it to config.php before using in docker.\n\nif (getenv('MTT_DB_TYPE') == 'mysql' || getenv('MTT_DB_TYPE')"
  },
  {
    "path": "src/export.php",
    "chars": 5757,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2010-2011,2019-2021 Max Pozdeev <maxpozdeev@gmail.com"
  },
  {
    "path": "src/ext/.htaccess",
    "chars": 85,
    "preview": "<FilesMatch \"\\.(json|md|MD)$\">\n    Order deny,allow\n    Deny from all\n</FilesMatch>\n\n"
  },
  {
    "path": "src/ext/CustomCSS/.htaccess",
    "chars": 14,
    "preview": "deny from all\n"
  },
  {
    "path": "src/ext/CustomCSS/extension.json",
    "chars": 126,
    "preview": "{\n    \"bundleId\": \"CustomCSS\",\n    \"name\": \"Custom CSS\",\n    \"version\": \"1.0.1\",\n    \"description\": \"Add you own css rul"
  },
  {
    "path": "src/ext/CustomCSS/lang/en.json",
    "chars": 247,
    "preview": "{\n    \"ext.CustomCSS.name\": \"Custom CSS\",\n    \"customcss.h_css\": \"CSS\",\n    \"customcss.d_css\": \"Write you own CSS rules\""
  },
  {
    "path": "src/ext/CustomCSS/lang/pl.json",
    "chars": 285,
    "preview": "{\n    \"ext.CustomCSS.name\": \"Własny styl CSS\",\n    \"customcss.h_css\": \"CSS\",\n    \"customcss.d_css\": \"Dodaj własne reguły"
  },
  {
    "path": "src/ext/CustomCSS/lang/ru.json",
    "chars": 270,
    "preview": "{\n    \"ext.CustomCSS.name\": \"Дополнительные стили\",\n    \"customcss.h_css\": \"CSS\",\n    \"customcss.d_css\": \"Добавляйте соб"
  },
  {
    "path": "src/ext/CustomCSS/loader.php",
    "chars": 2539,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/_examples/CustomSmartSyntax/.htaccess",
    "chars": 14,
    "preview": "deny from all\n"
  },
  {
    "path": "src/ext/_examples/CustomSmartSyntax/extension.json",
    "chars": 166,
    "preview": "{\n    \"bundleId\": \"CustomSmartSyntax\",\n    \"name\": \"Custom Smart Syntax\",\n    \"version\": \"1.0\",\n    \"description\": \"Prot"
  },
  {
    "path": "src/ext/_examples/CustomSmartSyntax/loader.php",
    "chars": 1054,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/backup/.htaccess",
    "chars": 14,
    "preview": "deny from all\n"
  },
  {
    "path": "src/ext/backup/class.backup.php",
    "chars": 6257,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023-2025 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/backup/class.check.php",
    "chars": 4510,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/backup/class.controller.php",
    "chars": 4158,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/backup/class.download.php",
    "chars": 1948,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/backup/class.restore.php",
    "chars": 8101,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023-2025 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/backup/extension.json",
    "chars": 102,
    "preview": "{\n    \"bundleId\": \"backup\",\n    \"name\": \"Backup\",\n    \"version\": \"1.1\",\n    \"description\": \"Backup\"\n}\n"
  },
  {
    "path": "src/ext/backup/lang/en.json",
    "chars": 812,
    "preview": "{\n    \"ext.backup.name\": \"Backup\",\n    \"backup.h_make\": \"Make backup\",\n    \"backup.d_make\": \"Will create backup file in "
  },
  {
    "path": "src/ext/backup/lang/ru.json",
    "chars": 851,
    "preview": "{\n    \"ext.backup.name\": \"Резервное копирование\",\n    \"backup.h_make\": \"Сделать копию\",\n    \"backup.d_make\": \"Сохранит ф"
  },
  {
    "path": "src/ext/backup/loader.php",
    "chars": 4163,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/index.html",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/ext/notifications/.htaccess",
    "chars": 14,
    "preview": "deny from all\n"
  },
  {
    "path": "src/ext/notifications/class.controller.php",
    "chars": 3988,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/notifications/class.observer.php",
    "chars": 2028,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/ext/notifications/class.sender.php",
    "chars": 6706,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/notifications/class.telegramapi.php",
    "chars": 4501,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/notifications/cli-notify.php",
    "chars": 1421,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/notifications/extension.json",
    "chars": 166,
    "preview": "{\n    \"bundleId\": \"notifications\",\n    \"name\": \"Notifications\",\n    \"version\": \"1.2.1\",\n    \"description\": \"Notify about"
  },
  {
    "path": "src/ext/notifications/lang/de.json",
    "chars": 1829,
    "preview": "{\n    \"ext.notifications.name\": \"Benachrichtigungen\",\n    \"notifications.urlconfigwarning\": \"Aktivieren Sie die PHP-Dire"
  },
  {
    "path": "src/ext/notifications/lang/en.json",
    "chars": 1666,
    "preview": "{\n    \"ext.notifications.name\": \"Notifications\",\n    \"notifications.urlconfigwarning\": \"Enable PHP 'allow_url_fopen' dir"
  },
  {
    "path": "src/ext/notifications/lang/ru.json",
    "chars": 1785,
    "preview": "{\n    \"ext.notifications.name\": \"Уведомления\",\n    \"notifications.urlconfigwarning\": \"Для использования Telegram требует"
  },
  {
    "path": "src/ext/notifications/loader.php",
    "chars": 8044,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/updater/.htaccess",
    "chars": 14,
    "preview": "deny from all\n"
  },
  {
    "path": "src/ext/updater/class.controller.php",
    "chars": 2437,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/updater/class.updater.php",
    "chars": 5173,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023-2025 Max Pozdeev <maxpo"
  },
  {
    "path": "src/ext/updater/extension.json",
    "chars": 123,
    "preview": "{\n    \"bundleId\": \"updater\",\n    \"name\": \"Updates\",\n    \"version\": \"0.9.4\",\n    \"description\": \"myTinyTodo self-updater\""
  },
  {
    "path": "src/ext/updater/lang/de.json",
    "chars": 763,
    "preview": "{\n    \"ext.updater.name\": \"Updates\",\n    \"updater.urlconfigwarning\": \"Aktivieren Sie die PHP-Anweisung 'allow_url_fopen'"
  },
  {
    "path": "src/ext/updater/lang/en.json",
    "chars": 732,
    "preview": "{\n    \"ext.updater.name\": \"Updates\",\n    \"updater.urlconfigwarning\": \"Enable PHP 'allow_url_fopen' directive to be able "
  },
  {
    "path": "src/ext/updater/lang/ru.json",
    "chars": 784,
    "preview": "{\n    \"ext.updater.name\": \"Обновления\",\n    \"updater.urlconfigwarning\": \"Для получения обновлений требуется включить дир"
  },
  {
    "path": "src/ext/updater/loader.php",
    "chars": 4076,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/feed.php",
    "chars": 5344,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2011,2020-2021 Max Pozdeev <maxpozdeev@gmail.com"
  },
  {
    "path": "src/includes/.htaccess",
    "chars": 14,
    "preview": "deny from all\n"
  },
  {
    "path": "src/includes/api/AuthController.php",
    "chars": 1730,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/api/ExtSettingsController.php",
    "chars": 2923,
    "preview": "<?php declare(strict_types=1);\n\nclass ExtSettingsController extends ApiController {\n\n    /**\n     * Get extension settin"
  },
  {
    "path": "src/includes/api/ListsController.php",
    "chars": 13112,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/api/TagsController.php",
    "chars": 2852,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/api/TasksController.php",
    "chars": 31288,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/class.config.php",
    "chars": 11353,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2021-2022 Max Pozdeev <maxpozdeev@gmail.com>\n    Lice"
  },
  {
    "path": "src/includes/class.db.mysql.php",
    "chars": 5835,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2020-2022 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/class.db.mysqli.php",
    "chars": 5093,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2019-2022 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/class.db.postgres.php",
    "chars": 5974,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/class.db.sqlite3.php",
    "chars": 15260,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009,2019-2022 Max Pozdeev <"
  },
  {
    "path": "src/includes/class.dbconnection.php",
    "chars": 3123,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2021,2022 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/class.dbcore.php",
    "chars": 6775,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>\n    Lice"
  },
  {
    "path": "src/includes/class.lang.php",
    "chars": 5907,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2020-2022 Max Pozdeev <maxpozdeev@gmail.com>\n    Lice"
  },
  {
    "path": "src/includes/class.sessionhandler.php",
    "chars": 3981,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2021-2025 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/classes.php",
    "chars": 7302,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/common.php",
    "chars": 5572,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2010,2020-2022 Max Pozdeev <maxpozdeev@gmail.com"
  },
  {
    "path": "src/includes/filters.php",
    "chars": 2272,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/index.html",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/includes/lang/_percent.php",
    "chars": 2868,
    "preview": "#!/usr/bin/env php\n<?php\n\nif (php_sapi_name() != 'cli') {\n    die(\"Command-line only!\\n\");\n}\n\n$only = [];\nwhile ($arg = "
  },
  {
    "path": "src/includes/lang/ar.json",
    "chars": 5131,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.4.2\",\n        \"date\": \"2011-04-04\",\n        \"language\": \"Arabic\",\n        \"origina"
  },
  {
    "path": "src/includes/lang/bg.json",
    "chars": 5356,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-02-24\",\n        \"language\": \"Bulgarian\",\n        \"orig"
  },
  {
    "path": "src/includes/lang/ca.json",
    "chars": 5320,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-05-04\",\n        \"language\": \"Catalan\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/cz.json",
    "chars": 5109,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-04-09\",\n        \"language\": \"Czech\",\n        \"original"
  },
  {
    "path": "src/includes/lang/da.json",
    "chars": 5113,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.5\",\n        \"date\": \"2010-05-22\",\n        \"language\": \"Danish\",\n        \"origina"
  },
  {
    "path": "src/includes/lang/de.json",
    "chars": 7331,
    "preview": "{\n    \"_header\": {\n        \"language\": \"German\",\n        \"original_name\": \"Deutsch\",\n        \"author\": \"Franky, Tobias Q"
  },
  {
    "path": "src/includes/lang/el.json",
    "chars": 5465,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-08-29\",\n        \"language\": \"Greek\",\n        \"original"
  },
  {
    "path": "src/includes/lang/en-rtl.json",
    "chars": 267,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.6\",\n        \"date\": \"2020-09-04\",\n        \"language\": \"English (for RTL test)\",\n  "
  },
  {
    "path": "src/includes/lang/en.json",
    "chars": 6709,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.2\",\n        \"date\": \"2025-02-16\",\n        \"language\": \"English\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/es-mx.json",
    "chars": 5407,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-08-03\",\n        \"language\": \"Spanish (Mexico)\",\n      "
  },
  {
    "path": "src/includes/lang/es.json",
    "chars": 5432,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.4.0\",\n        \"date\": \"2011-01-27\",\n        \"language\": \"Spanish\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/et.json",
    "chars": 7009,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.1\",\n        \"date\": \"2025-03-03\",\n        \"language\": \"Estonian\",\n        \"origi"
  },
  {
    "path": "src/includes/lang/fa.json",
    "chars": 6390,
    "preview": "{\n    \"_header\": {\n        \"language\": \"Persian\",\n        \"original_name\": \"پارسی\",\n        \"author\": \"saeb khanzadeh\",\n"
  },
  {
    "path": "src/includes/lang/fr.json",
    "chars": 7403,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.1\",\n        \"date\": \"2024-12-18\",\n        \"language\": \"French\",\n        \"origina"
  },
  {
    "path": "src/includes/lang/he.json",
    "chars": 4869,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-08-01\",\n        \"language\": \"Hebrew\",\n        \"origina"
  },
  {
    "path": "src/includes/lang/hu.json",
    "chars": 5239,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.2\",\n        \"date\": \"2010-01-20\",\n        \"language\": \"Hungarian\",\n        \"orig"
  },
  {
    "path": "src/includes/lang/it.json",
    "chars": 5138,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.2\",\n        \"date\": \"2010-01-24\",\n        \"language\": \"Italian\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/ja.json",
    "chars": 4403,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-12-17\",\n        \"language\": \"Japanese\",\n        \"origi"
  },
  {
    "path": "src/includes/lang/lt.json",
    "chars": 5457,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.5\",\n        \"date\": \"2010-06-05\",\n        \"language\": \"Lithuanian\",\n        \"ori"
  },
  {
    "path": "src/includes/lang/mk.json",
    "chars": 5108,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.3\",\n        \"date\": \"2010-02-11\",\n        \"language\": \"Macedonian\",\n        \"ori"
  },
  {
    "path": "src/includes/lang/nl.json",
    "chars": 7008,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.2\",\n        \"date\": \"2025-05-15\",\n        \"language\": \"Dutch\",\n        \"original"
  },
  {
    "path": "src/includes/lang/no.json",
    "chars": 5245,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-10-30\",\n        \"language\": \"Norwegian\",\n        \"orig"
  },
  {
    "path": "src/includes/lang/pl.json",
    "chars": 6240,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.7.3\",\n        \"date\": \"2022-12-06\",\n        \"language\": \"Polish\",\n        \"origina"
  },
  {
    "path": "src/includes/lang/pt-br.json",
    "chars": 6504,
    "preview": "{\n    \"_header\": {\n        \"language\": \"Portuguese (Brazilian)\",\n        \"original_name\": \"Português (do Brasil)\",\n     "
  },
  {
    "path": "src/includes/lang/pt-pt.json",
    "chars": 5217,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-07-29\",\n        \"language\": \"Portuguese (Portugal)\",\n "
  },
  {
    "path": "src/includes/lang/readme.md",
    "chars": 1167,
    "preview": "# myTinyTodo Translations\n\n| Locale |  Lines  | % Done |\n|:-------|--------:|-------:|\n| ar     | 147/202 |    73% |\n| b"
  },
  {
    "path": "src/includes/lang/ro.json",
    "chars": 5324,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-09-08\",\n        \"language\": \"Romanian\",\n        \"origi"
  },
  {
    "path": "src/includes/lang/ru.json",
    "chars": 7137,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.2\",\n        \"date\": \"2025-02-16\",\n        \"language\": \"Russian\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/sk.json",
    "chars": 5138,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.6\",\n        \"date\": \"2010-12-16\",\n        \"language\": \"Slovak\",\n        \"origina"
  },
  {
    "path": "src/includes/lang/sl.json",
    "chars": 5113,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.2\",\n        \"date\": \"2010-01-08\",\n        \"language\": \"Slovenian\",\n        \"orig"
  },
  {
    "path": "src/includes/lang/sr.json",
    "chars": 5474,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-02-21\",\n        \"language\": \"Serbian\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/sv.json",
    "chars": 5087,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-05-25\",\n        \"language\": \"Swedish\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/th.json",
    "chars": 5103,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-11-15\",\n        \"language\": \"Thai\",\n        \"original_"
  },
  {
    "path": "src/includes/lang/tr.json",
    "chars": 5317,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-03-11\",\n        \"language\": \"Turkish\",\n        \"origin"
  },
  {
    "path": "src/includes/lang/uk.json",
    "chars": 5303,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-03-17\",\n        \"language\": \"Ukrainian\",\n        \"orig"
  },
  {
    "path": "src/includes/lang/vi.json",
    "chars": 5289,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.3.4\",\n        \"date\": \"2010-05-05\",\n        \"language\": \"Vietnamese\",\n        \"ori"
  },
  {
    "path": "src/includes/lang/zh-cn.json",
    "chars": 5583,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.2\",\n        \"date\": \"2025-07-23\",\n        \"language\": \"Chinese Simplified\",\n    "
  },
  {
    "path": "src/includes/lang/zh-tw.json",
    "chars": 5425,
    "preview": "{\n    \"_header\": {\n        \"ver\": \"v1.8.2\",\n        \"date\": \"2025-07-23\",\n        \"language\": \"Chinese Traditional\",\n   "
  },
  {
    "path": "src/includes/markup.commonmark.php",
    "chars": 2210,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/markup.parsedown.php",
    "chars": 2556,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022-2023 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/markup.php",
    "chars": 4794,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2021-2025 Max Pozdeev <maxpo"
  },
  {
    "path": "src/includes/notifications.php",
    "chars": 3965,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2022 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/smartsyntax.php",
    "chars": 6693,
    "preview": "<?php declare(strict_types=1);\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2023 Max Pozdeev <maxpozdeev"
  },
  {
    "path": "src/includes/theme.php",
    "chars": 15937,
    "preview": "<?php\n  if (!defined('MTTPATH')) die(\"Unexpected usage.\");\n  header(\"Content-type: text/html; charset=utf-8\");\n?>\n<!doct"
  },
  {
    "path": "src/includes/version.php",
    "chars": 107,
    "preview": "<?php\n\nnamespace mytinytodo;\n\nclass Version\n{\n    const VERSION = '1.8.4';\n    const DB_VERSION = '1.8';\n}\n"
  },
  {
    "path": "src/index.php",
    "chars": 3498,
    "preview": "<?php\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2010,2020-2022 Max Pozdeev <maxpozdeev@gmail.com>"
  },
  {
    "path": "src/init.php",
    "chars": 15742,
    "preview": "<?php\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2011,2019-2023 Max Pozdeev <maxpozdeev@gmail.com>"
  },
  {
    "path": "src/mtt-edit-settings.php",
    "chars": 1132,
    "preview": "<?php\n\nif ( !isset($argv) || !isset($argc) || isset($_SERVER['REMOTE_ADDR']) ) {\n    die(\"Run from command line only!\");"
  },
  {
    "path": "src/mtt-emergency.php",
    "chars": 650,
    "preview": "<?php\n\n$dontStartSession = true;\nrequire_once(__DIR__ . '/init.php');\n\nif (!need_auth()) {\n    exitmsg(\"No password prot"
  },
  {
    "path": "src/settings.php",
    "chars": 16014,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2011,2020-2023 Max Pozdeev <maxpozdeev@gmail.com"
  },
  {
    "path": "src/setup.php",
    "chars": 33347,
    "preview": "<?php\n\n/*\n    This file is a part of myTinyTodo.\n    (C) Copyright 2009-2011,2020-2025 Max Pozdeev <maxpozdeev@gmail.com"
  }
]

About this extraction

This page contains the full source code of the maxpozdeev/mytinytodo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 140 files (748.4 KB), approximately 219.2k tokens, and a symbol index with 663 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!