Compare commits

..

No commits in common. "master" and "v1.7.2" have entirely different histories.

160 changed files with 2916 additions and 7476 deletions

View file

@ -19,7 +19,7 @@ indent_size = 4
indent_style = space
tab_width = 4
[*.{css,yml,html,svg,xml}]
[*.{css,yml,html,svg}]
indent_style = space
indent_size = 2

3
.gitignore vendored
View file

@ -1,10 +1,7 @@
.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/

View file

@ -1,22 +1,3 @@
# myTinyTodo
Your tiny todo list
myTinyTodo
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.

View file

@ -43,10 +43,7 @@ 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
@ -61,7 +58,6 @@ if (false === system( "./composer.sh install --no-dev --no-interaction --optimiz
if (is_dir('src/ext')) {
mkdir('src/ext2');
chdir('src/ext');
deleteTreeIfDir('_examples');
$extCount = 0;
$exts = array_diff(scandir('.') ?? [], ['.', '..']);
foreach ($exts as $ext) {
@ -72,7 +68,7 @@ if (is_dir('src/ext')) {
}
chdir('../ext2');
if ($extCount > 0) {
`tar --no-xattrs -czf ../ext/extensions.tar.gz *`; #OS dep.!!!
`tar -czf ../ext/extensions.tar.gz *`; #OS dep.!!!
}
chdir('../..');
deleteTreeIfDir('src/ext2');
@ -82,7 +78,7 @@ if (is_dir('src/ext')) {
rename('src', 'mytinytodo') or die("Cant rename 'src'\n");
`tar --no-xattrs -czf mytinytodo.tar.gz mytinytodo`; #OS dep.!!!
`tar -czf mytinytodo.tar.gz mytinytodo`; #OS dep.!!!
if (!file_exists('mytinytodo.tar.gz')) {
die("Failed to pack files (no output tar.gz file)\n");
}

View file

@ -1,24 +1,11 @@
{
"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"
"erusev/parsedown": "^1.7"
},
"require-dev": {
"league/commonmark": "^2.6"
"league/commonmark": "^2.3"
}
}

259
composer.lock generated
View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c27d4b183be86ec7d95203f1a84b86e2",
"content-hash": "18661f8c2155d955e7962271812bbed0",
"packages": [
{
"name": "erusev/parsedown",
"version": "1.7.x-dev",
"version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "f7285e7"
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/f7285e7",
"reference": "f7285e7",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
@ -25,7 +25,7 @@
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.7|^6.5|^7.5|^8.5|^9.6"
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
@ -54,103 +54,22 @@
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
},
"time": "2024-07-12T14:59:16+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
"reference": "3833d7255cc303546435cb650316bff708a1c75c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
"time": "2019-12-30T22:54:17+00:00"
}
],
"packages-dev": [
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f"
"reference": "0992cc19268b259a39e86f296da5f0677841f42c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f",
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
"reference": "0992cc19268b259a39e86f296da5f0677841f42c",
"shasum": ""
},
"require": {
@ -161,7 +80,7 @@
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
"scrutinizer/ocular": "1.6.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.0.0"
"vimeo/psalm": "^3.14"
},
"type": "library",
"extra": {
@ -210,22 +129,22 @@
],
"support": {
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3"
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
},
"time": "2024-07-08T12:26:09+00:00"
"time": "2021-08-13T13:06:58+00:00"
},
{
"name": "league/commonmark",
"version": "2.7.0",
"version": "2.3.4",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
"reference": "155ec1c95626b16fda0889cf15904d24890a60d5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/155ec1c95626b16fda0889cf15904d24890a60d5",
"reference": "155ec1c95626b16fda0889cf15904d24890a60d5",
"shasum": ""
},
"require": {
@ -238,23 +157,22 @@
},
"require-dev": {
"cebe/markdown": "^1.0",
"commonmark/cmark": "0.31.1",
"commonmark/commonmark.js": "0.31.1",
"commonmark/cmark": "0.30.0",
"commonmark/commonmark.js": "0.30.0",
"composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4",
"erusev/parsedown": "^1.0",
"ext-json": "*",
"github/gfm": "0.29.0",
"michelf/php-markdown": "^1.4 || ^2.0",
"michelf/php-markdown": "^1.4",
"nyholm/psr7": "^1.5",
"phpstan/phpstan": "^1.8.2",
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
"phpstan/phpstan": "^0.12.88 || ^1.0.0",
"phpunit/phpunit": "^9.5.5",
"scrutinizer/ocular": "^1.8.1",
"symfony/finder": "^5.3 | ^6.0 | ^7.0",
"symfony/process": "^5.4 | ^6.0 | ^7.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
"unleashedtech/php-coding-standard": "^3.1.1",
"vimeo/psalm": "^4.24.0 || ^5.0.0"
"symfony/finder": "^5.3",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
"unleashedtech/php-coding-standard": "^3.1",
"vimeo/psalm": "^4.7.3"
},
"suggest": {
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
@ -262,7 +180,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.8-dev"
"dev-main": "2.4-dev"
}
},
"autoload": {
@ -319,20 +237,20 @@
"type": "tidelift"
}
],
"time": "2025-05-05T12:20:28+00:00"
"time": "2022-07-17T16:25:47+00:00"
},
{
"name": "league/config",
"version": "v1.2.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/config.git",
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
"url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
"shasum": ""
},
"require": {
@ -341,7 +259,7 @@
"php": "^7.4 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^1.8.2",
"phpstan/phpstan": "^0.12.90",
"phpunit/phpunit": "^9.5.5",
"scrutinizer/ocular": "^1.8.1",
"unleashedtech/php-coding-standard": "^3.1",
@ -401,35 +319,35 @@
"type": "github"
}
],
"time": "2022-12-11T20:36:23+00:00"
"time": "2021-08-14T12:15:32+00:00"
},
{
"name": "nette/schema",
"version": "v1.3.2",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "da801d52f0354f70a638673c4a0f04e16529431d"
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
"reference": "da801d52f0354f70a638673c4a0f04e16529431d",
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
"php": "8.1 - 8.4"
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
"php": ">=7.1 <8.2"
},
"require-dev": {
"nette/tester": "^2.5.2",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.8"
"nette/tester": "^2.3 || ^2.4",
"phpstan/phpstan-nette": "^0.12",
"tracy/tracy": "^2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
"dev-master": "1.2-dev"
}
},
"autoload": {
@ -461,36 +379,34 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.3.2"
"source": "https://github.com/nette/schema/tree/v1.2.2"
},
"time": "2024-10-06T23:10:23+00:00"
"time": "2021-10-15T11:40:02+00:00"
},
{
"name": "nette/utils",
"version": "v4.0.6",
"version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "ce708655043c7050eb050df361c5e313cf708309"
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
"reference": "ce708655043c7050eb050df361c5e313cf708309",
"url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99",
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99",
"shasum": ""
},
"require": {
"php": "8.0 - 8.4"
"php": ">=7.2 <8.2"
},
"conflict": {
"nette/finder": "<3",
"nette/schema": "<1.2.2"
"nette/di": "<3.0.6"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "dev-master",
"nette/tester": "^2.5",
"nette/tester": "~2.0",
"phpstan/phpstan": "^1.0",
"tracy/tracy": "^2.9"
"tracy/tracy": "^2.3"
},
"suggest": {
"ext-gd": "to use Image",
@ -498,12 +414,13 @@
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@ -547,9 +464,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.6"
"source": "https://github.com/nette/utils/tree/v3.2.7"
},
"time": "2025-03-30T21:06:30+00:00"
"time": "2022-01-24T11:29:14+00:00"
},
{
"name": "psr/event-dispatcher",
@ -603,16 +520,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.1",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"shasum": ""
},
"require": {
@ -620,12 +537,12 @@
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.5-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@ -650,7 +567,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
},
"funding": [
{
@ -666,30 +583,33 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.32.0",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"shasum": ""
},
"require": {
"php": ">=7.2"
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
},
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -730,7 +650,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
},
"funding": [
{
@ -746,20 +666,15 @@
"type": "tidelift"
}
],
"time": "2025-01-02T08:10:11+00:00"
"time": "2022-05-10T07:21:04+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"erusev/parsedown": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.2",
"ext-mbstring": "*"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

View file

@ -3,9 +3,4 @@
#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 $@
docker run -it --rm -v "$dir:/app" composer $@

1
docker/dev/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*/db_data

View file

@ -0,0 +1,4 @@
FROM mariadb:10.5
COPY my.cnf /etc/mysql/conf.d/

View file

@ -0,0 +1,4 @@
[mysqld]
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4
innodb_log_file_size=10M

View file

@ -0,0 +1,3 @@
FROM mariadb:10.8
COPY my.cnf /etc/mysql/conf.d/

View file

@ -0,0 +1,4 @@
[mysqld]
collation-server = utf8mb4_unicode_ci
character-set-server = utf8mb4
innodb_log_file_size=10M

View file

@ -0,0 +1,5 @@
FROM nginx:latest
COPY default.conf /etc/nginx/conf.d/
VOLUME /var/www/html

View file

@ -0,0 +1,58 @@
# format like 'main' excluding user agent
log_format mtt '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer"';
access_log off;
upstream fpm {
server php-fpm:9000;
}
server {
listen 80;
server_name localhost;
root /var/www/html; # same as php-fpm
access_log /var/log/nginx/access.log mtt;
index index.php index.html;
autoindex off;
location = /phpinfo {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/phpinfo/phpinfo.php;
fastcgi_pass fpm;
}
location /api/ {
rewrite ^/api/(.*) /api.php/$1 last;
}
location / {
try_files $uri $uri/ =404;
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_index index.php;
fastcgi_pass fpm;
}
location /db/ {
return 404; #deny all
}
location /includes/ {
return 404; #deny all
}
location ~ /\.ht {
return 404; #deny all
}
}

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php72-apache-mariadb105

View file

@ -0,0 +1,49 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../mtt-php72-apache/
dockerfile: Dockerfile-web
image: mytinytodo-dev:php7.2-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=mysql
- MTT_DB_HOST=db
- MTT_DB_NAME=mytinytodo
- MTT_DB_USER=mtt
- MTT_DB_PASSWORD=mtt
- MTT_DB_PREFIX=mtt_
- MTT_DB_DRIVER=mysqli
volumes:
- ../../../src:/var/www/html
- ../mtt-php72-apache/php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ../mtt-php72-apache/php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini
depends_on:
- db
networks:
- network
db:
build:
context: ../_mariadb105
dockerfile: Dockerfile-db
image: mytinytodo-dev:mariadb10.5
container_name: ${PLATFORM_NAME}-db
#restart: always
environment:
MARIADB_ROOT_PASSWORD: JustForDev
MARIADB_USER: mtt
MARIADB_PASSWORD: mtt
MARIADB_DATABASE: mytinytodo
volumes:
- ../_mariadb105/db_data:/var/lib/mysql
networks:
- network

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php72-apache

View file

@ -0,0 +1,16 @@
FROM php:7.2-apache
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php && \
echo 'Alias "/phpinfo" "/var/www/phpinfo/phpinfo.php"' > /etc/apache2/conf-enabled/phpinfo.conf && \
echo 'LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\"" combined' > /etc/apache2/conf-enabled/logformat.conf && \
a2enmod rewrite && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-8.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/

View file

@ -0,0 +1,17 @@
services:
web:
build:
context: .
dockerfile: Dockerfile-web
image: mytinytodo-dev:php7.2-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html
- ../../../tests:/var/www/tests
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ./php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini

View file

@ -0,0 +1,9 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
session.gc_divisor = 100

View file

@ -0,0 +1,34 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=0
opcache.enable_cli=0
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php74-apache

View file

@ -0,0 +1,15 @@
FROM php:7.4-apache
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php && \
echo 'Alias "/phpinfo" "/var/www/phpinfo/phpinfo.php"' > /etc/apache2/conf-enabled/phpinfo.conf && \
echo 'LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\"" combined' > /etc/apache2/conf-enabled/logformat.conf && \
a2enmod rewrite && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-9.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/

View file

@ -0,0 +1,16 @@
services:
web:
build:
context: .
dockerfile: Dockerfile-web
image: mytinytodo-dev:php7.4-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ./php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini

View file

@ -0,0 +1,9 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
session.gc_divisor = 100

View file

@ -0,0 +1,34 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=0
opcache.enable_cli=0
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php74-nginx

View file

@ -0,0 +1,11 @@
FROM php:7.4-fpm
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/
VOLUME /var/www/html

View file

@ -0,0 +1,38 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../_nginx
dockerfile: Dockerfile-nginx
image: mytinytodo-dev:nginx
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
volumes:
- ../../../src:/var/www/html
depends_on:
- fpm
networks:
- network
fpm:
build:
context: .
dockerfile: Dockerfile-fpm
image: mytinytodo-dev:php7.4-fpm
container_name: ${PLATFORM_NAME}-fpm
hostname: php-fpm
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ./php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini
networks:
- network

View file

@ -0,0 +1,10 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
session.gc_divisor = 100
cgi.fix_pathinfo=0

View file

@ -0,0 +1,42 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=1
opcache.enable_cli=1
; JIT will work only if opcache is enabled and jit_buffer_size is not zero.
; To disable JIT set the buffer size to 0.
opcache.jit_buffer_size=64M
; JIT mode. Read more at https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
; Default is tracing.
opcache.jit=tracing
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php80-apache-mariadb105

View file

@ -0,0 +1,47 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../mtt-php80-apache/
dockerfile: Dockerfile-web
image: mytinytodo-dev:php8.0-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=mysql
- MTT_DB_HOST=db
- MTT_DB_NAME=mytinytodo
- MTT_DB_USER=mtt
- MTT_DB_PASSWORD=mtt
- MTT_DB_PREFIX=mtt_
- MTT_DB_DRIVER=mysqli
volumes:
- ../../../src:/var/www/html
depends_on:
- db
networks:
- network
db:
build:
context: ../_mariadb105
dockerfile: Dockerfile-db
image: mytinytodo-dev:mariadb10.5
container_name: ${PLATFORM_NAME}-db
#restart: always
environment:
MARIADB_ROOT_PASSWORD: JustForDev
MARIADB_USER: mtt
MARIADB_PASSWORD: mtt
MARIADB_DATABASE: mytinytodo
volumes:
- ../_mariadb105/db_data:/var/lib/mysql
networks:
- network

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php80-apache

View file

@ -0,0 +1,15 @@
FROM php:8.0-apache
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php && \
echo 'Alias "/phpinfo" "/var/www/phpinfo/phpinfo.php"' > /etc/apache2/conf-enabled/phpinfo.conf && \
echo 'LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\"" combined' > /etc/apache2/conf-enabled/logformat.conf && \
a2enmod rewrite && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-9.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
COPY php-mtt.ini /usr/local/etc/php/conf.d/
COPY php-opcache.ini /usr/local/etc/php/conf.d/

View file

@ -0,0 +1,16 @@
version: "3.9"
services:
web:
build:
context: .
dockerfile: Dockerfile-web
image: mytinytodo-dev:php8.0-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html

View file

@ -0,0 +1,9 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
session.gc_divisor = 100

View file

@ -0,0 +1,42 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=0
opcache.enable_cli=0
; JIT will work only if opcache is enabled and jit_buffer_size is not zero.
; To disable JIT set the buffer size to 0.
opcache.jit_buffer_size=64M
; JIT mode. Read more at https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
; Default is tracing.
opcache.jit=tracing
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php81-apache-mariadb108

View file

@ -0,0 +1,56 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../mtt-php81-apache/
dockerfile: Dockerfile-web
image: mytinytodo-dev:php8.1-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=mysql
- MTT_DB_HOST=db
- MTT_DB_NAME=mytinytodo
- MTT_DB_USER=mtt
- MTT_DB_PASSWORD=mtt
- MTT_DB_PREFIX=mtt_
- MTT_DB_DRIVER=mysqli
volumes:
- ../../../src:/var/www/html
- ../../../tests:/var/www/tests
depends_on:
- db
networks:
- network
db:
build:
context: ../_mariadb108
dockerfile: Dockerfile-db
image: mytinytodo-dev:mariadb10.8
container_name: ${PLATFORM_NAME}-db
restart: always
environment:
MARIADB_ROOT_PASSWORD: JustForDev
MARIADB_USER: mtt
MARIADB_PASSWORD: mtt
MARIADB_DATABASE: mytinytodo
volumes:
- ../_mariadb108/db_data:/var/lib/mysql
networks:
- network
adminer:
image: adminer
container_name: ${PLATFORM_NAME}-adminer
ports:
- 8079:8080
networks:
- network

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php81-apache

View file

@ -0,0 +1,16 @@
FROM php:8.1-apache
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php && \
echo 'Alias "/phpinfo" "/var/www/phpinfo/phpinfo.php"' > /etc/apache2/conf-enabled/phpinfo.conf && \
a2enmod rewrite && \
echo 'LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\"" combined' > /etc/apache2/conf-enabled/logformat.conf && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-9.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
COPY php-mtt.ini /usr/local/etc/php/conf.d/
COPY php-opcache.ini /usr/local/etc/php/conf.d/

View file

@ -0,0 +1,16 @@
version: "3.9"
services:
web:
build:
context: .
dockerfile: Dockerfile-web
image: mytinytodo-dev:php8.1-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html

View file

@ -0,0 +1,9 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
session.gc_divisor = 100

View file

@ -0,0 +1,42 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=0
opcache.enable_cli=0
; JIT will work only if opcache is enabled and jit_buffer_size is not zero.
; To disable JIT set the buffer size to 0.
opcache.jit_buffer_size=64M
; JIT mode. Read more at https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
; Default is tracing.
opcache.jit=tracing
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php81-nginx

View file

@ -0,0 +1,11 @@
FROM php:8.1-fpm
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/
VOLUME /var/www/html

View file

@ -0,0 +1,38 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../_nginx
dockerfile: Dockerfile-nginx
image: mytinytodo-dev:nginx
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
volumes:
- ../../../src:/var/www/html
depends_on:
- fpm
networks:
- network
fpm:
build:
context: .
dockerfile: Dockerfile-fpm
image: mytinytodo-dev:php8.1-fpm
container_name: ${PLATFORM_NAME}-fpm
hostname: php-fpm
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ./php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini
networks:
- network

View file

@ -0,0 +1,10 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
cgi.fix_pathinfo=0
session.gc_divisor = 100

View file

@ -0,0 +1,42 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=1
opcache.enable_cli=1
; JIT will work only if opcache is enabled and jit_buffer_size is not zero.
; To disable JIT set the buffer size to 0.
opcache.jit_buffer_size=64M
; JIT mode. Read more at https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
; Default is tracing.
opcache.jit=tracing
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php82-apache

View file

@ -0,0 +1,15 @@
FROM php:8.2-rc-apache
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
a2enmod rewrite && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php && \
echo 'Alias "/phpinfo" "/var/www/phpinfo/phpinfo.php"' > /etc/apache2/conf-enabled/phpinfo.conf && \
echo 'LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\"" combined' > /etc/apache2/conf-enabled/logformat.conf && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-9.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
COPY php-mtt.ini /usr/local/etc/php/conf.d/
COPY php-opcache.ini /usr/local/etc/php/conf.d/

View file

@ -0,0 +1,18 @@
version: "3.9"
services:
web:
build:
context: .
dockerfile: Dockerfile-web
image: mytinytodo-dev:php8.2-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
volumes:
- ../../../src:/var/www/html
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ./php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini

View file

@ -0,0 +1,9 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
session.gc_divisor = 100

View file

@ -0,0 +1,42 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=0
opcache.enable_cli=0
; JIT will work only if opcache is enabled and jit_buffer_size is not zero.
; To disable JIT set the buffer size to 0.
opcache.jit_buffer_size=64M
; JIT mode. Read more at https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
; Default is tracing.
opcache.jit=tracing
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

8
docker/dev/readme.md Normal file
View file

@ -0,0 +1,8 @@
Docker containers for development and testing purposes.
Not recommended for production usage!
To run docker container:
cd mtt-php74-apache
docker-compose build --pull
docker-compose up

View file

@ -1,29 +1,12 @@
# For REST API in Apache
# For 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
# For Nginx set something like this:
# location /api/ {
# rewrite ^/api/(.*) /api.php/$1 last;
# }

View file

@ -16,7 +16,7 @@ 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>
Copyright: 2009-2011,2019-2022 Max Pozdeev <maxpozdeev@gmail.com>
License: GPL version 2 or any later (see LICENSE file)
@ -34,11 +34,9 @@ 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
jQuery UI Touch Punch
-----------------------
Url: https://github.com/furf/jquery-ui-touch-punch
Copyright: Copyright 20112014, Dave Furfero
License: Dual licensed under the MIT or GPL Version 2 licenses.
@ -48,12 +46,7 @@ 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.
See files content/themes/default/images/COPYRIGHT for details.

View file

@ -2,7 +2,7 @@
/*
This file is a part of myTinyTodo.
(C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
@ -32,7 +32,6 @@ $endpoints = array(
'GET' => [ ListsController::class , 'getId' ],
'PUT' => [ ListsController::class , 'putId' ],
'DELETE' => [ ListsController::class , 'deleteId' ],
'POST' => [ ListsController::class , 'putId' ], //compatibility
],
'/tasks' => [
'GET' => [ TasksController::class , 'get' ],
@ -42,14 +41,10 @@ $endpoints = array(
'/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' ],
],
@ -62,7 +57,6 @@ $endpoints = array(
'/ext-settings/(.+)' => [
'GET' => [ ExtSettingsController::class , 'get' ],
'PUT' => [ ExtSettingsController::class , 'put' ],
'POST' => [ ExtSettingsController::class , 'put' ], //compatibility
]
);
@ -73,7 +67,7 @@ foreach (MTTExtensionLoader::loadedExtensions() as $instance) {
foreach ($newRoutes as $endpoint => $methods) {
$endpoint = '/ext/'. $instance::bundleId. $endpoint;
foreach ($methods as $k => &$v) {
$v[3] = true; // Mark extension method
$v[2] = true; // Mark extension method
}
$endpoints[$endpoint] = $methods;
}
@ -91,22 +85,17 @@ foreach ($endpoints as $search => $methods) {
$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();
$response->htmlContent("Unknown method for resource", 500)->exit();
}
if ( !is_array($classDescr) || count($classDescr) < 2) {
$response->htmlContent("Incorrect method definition", 500)
->exit();
$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();
}
$isExt = $classDescr[2] ?? false;
if ($isExt) {
checkWriteAccess();
}
$param = null;
if (count($m) >= 2) {
@ -115,8 +104,7 @@ foreach ($endpoints as $search => $methods) {
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();
$response->htmlContent("Failed to parse JSON body", 500)->exit();
}
}
$instance = new $class($req, $response);
@ -126,21 +114,15 @@ foreach ($endpoints as $search => $methods) {
}
else {
if (MTT_DEBUG) {
$response->htmlContent("Class method $class:$classMethod() not found", 405)
->exit();
$response->htmlContent("Class method $class:$classMethod() not found", 405)->exit();
}
$response->htmlContent("Class method 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->htmlContent("Unknown command", 404);
}
$response->exit();
@ -151,7 +133,7 @@ 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) {
elseif ($errno==E_WARNING || $errno==E_CORE_WARNING || $errno==E_COMPILE_WARNING || $errno==E_USER_WARNING || $errno==E_STRICT) {
if (error_reporting() & $errno) $error = 'Warning'; else return;
}
elseif ($errno==E_NOTICE || $errno==E_USER_NOTICE || $errno==E_DEPRECATED || $errno==E_USER_DEPRECATED) {
@ -200,7 +182,6 @@ function checkReadAccess(?int $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) );
}

View file

@ -3,7 +3,7 @@
/*
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.
Select the database type: sqlite or mysql.
*/
#define("MTT_DB_TYPE", "sqlite");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/*!
* jQuery UI Touch Punch 1.1.5 as modified by RWAP Software
* jQuery UI Touch Punch 1.0.8 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
@ -22,7 +22,7 @@
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define([ "jquery", "jquery-ui" ], factory );
define([ "jquery", "jquery.ui" ], factory );
} else {
// Browser globals
@ -31,30 +31,31 @@
}(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
$.support.mspointer = window.navigator.msPointerEnabled;
$.support.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;
if ((!$.support.touch && !$.support.mspointer) || !$.ui.mouse) {
return;
}
let mouseProto = $.ui.mouse.prototype,
var mouseProto = $.ui.mouse.prototype,
_mouseInit = mouseProto._mouseInit,
_mouseDestroy = mouseProto._mouseDestroy,
touchHandled, lastClickTime = 0;
touchHandled;
let delay = 300,
var delay = 300,
delayTimer,
delayEvent,
delayStarted = false,
delayFinished = false,
lastClickTimeStamp = 0,
lastClickCoord;
@ -81,26 +82,32 @@
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
});
var touch = event.originalEvent.changedTouches[0],
simulatedEvent = document.createEvent('MouseEvents');
// Initialize the simulated mouse event using the touch event's coordinates
simulatedEvent.initMouseEvent(
simulatedType, // type
true, // bubbles
true, // cancelable
window, // view
1, // detail
touch.screenX, // screenX
touch.screenY, // screenY
touch.clientX, // clientX
touch.clientY, // clientY
false, // ctrlKey
false, // altKey
false, // shiftKey
false, // metaKey
0, // button
null // relatedTarget
);
// Dispatch the simulated event to the target element
event.target.dispatchEvent(simulatedEvent);
@ -118,7 +125,7 @@
function fireMouseDown () {
const self = this;
var self = this;
delayFinished = true;
@ -145,7 +152,7 @@
*/
mouseProto._touchStart = function (event) {
let self = this;
var self = this;
// Interaction time
this._startedMove = event.timeStamp;
@ -217,23 +224,22 @@
// 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;
var 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 &&
// Simulate the click event
simulateMouseEvent(event, 'click');
if (lastClickTimeStamp != 0 && event.timeStamp - lastClickTimeStamp < 500 &&
Math.abs(lastClickCoord.x - this._startPos.x) < 10 && Math.abs(lastClickCoord.y - this._startPos.y) < 10) {
// Simulate the dblclick event
simulateMouseEvent(event, 'dblclick');
}
// Simulate the click event
else
simulateMouseEvent(event, 'click');
lastClickTime = event.timeStamp
lastClickTimeStamp = event.timeStamp
lastClickCoord = this._startPos;
}
else {
let endPos = getTouchCoords(event);
var 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') {
@ -250,10 +256,6 @@
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
@ -262,22 +264,18 @@
*/
mouseProto._mouseInit = function () {
let self = this;
// Microsoft Surface Support = remove original touch Action
if ($.mspointer) {
self.element[0].style.msTouchAction = 'none';
}
var self = this;
_touchStartBound = mouseProto._touchStart.bind(self);
_touchMoveBound = mouseProto._touchMove.bind(self);
_touchEndBound = mouseProto._touchEnd.bind(self);
// Microsoft Surface Support = remove original touch Action
if ($.support.mspointer) {
self.element[0].style.msTouchAction = 'none';
}
// Delegate the touch handlers to the widget's element
self.element.on({
touchstart: _touchStartBound,
touchmove: _touchMoveBound,
touchend: _touchEndBound
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse init method
@ -289,13 +287,13 @@
*/
mouseProto._mouseDestroy = function () {
let self = this;
var self = this;
// Delegate the touch handlers to the widget's element
self.element.off({
touchstart: _touchStartBound,
touchmove: _touchMoveBound,
touchend: _touchEndBound
touchstart: $.proxy(self, '_touchStart'),
touchmove: $.proxy(self, '_touchMove'),
touchend: $.proxy(self, '_touchEnd')
});
// Call the original $.ui.mouse destroy method

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,72 +1,56 @@
/*
This file is a part of myTinyTodo.
(C) Copyright 2010,2020-2025 Max Pozdeev <maxpozdeev@gmail.com>
(C) Copyright 2010,2020,2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
(function(){
"use strict";
/**
* @class
*/
function MytinytodoAjaxApi(props)
{
if (typeof mytinytodo !== 'object') {
throw "mytinytodo global object is not found!";
}
this.useREST = true;
var mtt;
if (props.hasOwnProperty('useREST')) {
this.useREST = !!props.useREST;
}
function MytinytodoAjaxApi(amtt)
{
mtt = amtt;
}
MytinytodoAjaxApi.prototype = {
window.MytinytodoAjaxApi = MytinytodoAjaxApi;
/**
* 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)
}
MytinytodoAjaxApi.prototype =
{
/* required method */
request: function(action, params, callback)
{
if (!this[action]) throw "Unknown ApiDriver action: " + action;
this[action] (params, function(json){
if (json.denied) mtt.errorDenied();
if (callback) callback.call(mtt, json)
});
},
loadTasks(params, callback) {
let q = '';
loadTasks: function(params, callback)
{
var 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);
$.getJSON(mtt.apiUrl + 'tasks?list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q, callback);
},
newTask(params, callback) {
newTask: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks',
url: mtt.apiUrl + 'tasks',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
action: 'newSimple',
action: 'simple',
list: params.list,
title: params.title,
tag: params.tag,
@ -77,13 +61,14 @@ MytinytodoAjaxApi.prototype = {
},
fullNewTask(params, callback) {
fullNewTask: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks',
url: mtt.apiUrl + 'tasks',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
action: 'newFull',
action: 'full',
list: params.list,
title: params.title,
note: params.note,
@ -98,10 +83,11 @@ MytinytodoAjaxApi.prototype = {
},
editTask(params, callback) {
editTask: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'edit',
@ -117,10 +103,11 @@ MytinytodoAjaxApi.prototype = {
},
editNote(params, callback) {
editNote: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'note',
@ -132,10 +119,11 @@ MytinytodoAjaxApi.prototype = {
},
completeTask(params, callback) {
completeTask: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'complete',
@ -147,24 +135,22 @@ MytinytodoAjaxApi.prototype = {
},
deleteTask(params, callback) {
deleteTask: function(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',
}),
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'DELETE',
success: callback,
dataType: 'json'
});
},
setTaskPriority(params, callback) {
setTaskPriority: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'priority',
@ -175,10 +161,11 @@ MytinytodoAjaxApi.prototype = {
});
},
changeOrder(params, callback) {
changeOrder: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks',
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'tasks',
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'order',
@ -189,18 +176,21 @@ MytinytodoAjaxApi.prototype = {
});
},
suggestTags(params, callback) {
$.getJSON(mytinytodo.apiUrl + 'suggestTags', {list:params.list, q:params.q}, callback);
suggestTags: function(params, callback)
{
$.getJSON(mtt.apiUrl + 'suggestTags', {list:params.list, q:params.q}, callback);
},
tagCloud(params, callback) {
$.getJSON(mytinytodo.apiUrl + 'tagCloud/' + encodeURIComponent(params.list), callback);
tagCloud: function(params, callback)
{
$.getJSON(mtt.apiUrl + 'tagCloud/' + encodeURIComponent(params.list), callback);
},
moveTask(params, callback) {
moveTask: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'move',
@ -212,9 +202,10 @@ MytinytodoAjaxApi.prototype = {
});
},
parseTaskStr(params, callback) {
parseTaskStr: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'tasks/parseTitle',
url: mtt.apiUrl + 'tasks/parseTitle',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
@ -227,42 +218,20 @@ MytinytodoAjaxApi.prototype = {
});
},
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);
loadLists: function(params, callback)
{
$.getJSON(mtt.apiUrl + 'lists', callback);
},
addList(params, callback) {
addList: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists',
url: mtt.apiUrl + 'lists',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
action: 'new',
name: params.name,
}),
success: callback,
@ -271,23 +240,21 @@ MytinytodoAjaxApi.prototype = {
},
deleteList(params, callback) {
deleteList: function(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',
}),
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'DELETE',
success: callback,
dataType: 'json'
});
},
renameList(params, callback) {
renameList: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'rename',
@ -298,10 +265,11 @@ MytinytodoAjaxApi.prototype = {
});
},
setSort(params, callback) {
setSort: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'sort',
@ -313,10 +281,11 @@ MytinytodoAjaxApi.prototype = {
});
},
publishList(params, callback) {
publishList: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'publish',
@ -327,10 +296,11 @@ MytinytodoAjaxApi.prototype = {
});
},
enableFeedKey(params, callback) {
enableFeedKey: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'enableFeedKey',
@ -341,10 +311,11 @@ MytinytodoAjaxApi.prototype = {
});
},
setShowNotesInList(params, callback) {
setShowNotesInList: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'showNotes',
@ -355,10 +326,11 @@ MytinytodoAjaxApi.prototype = {
});
},
setHideList(params, callback) {
setHideList: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'hide',
@ -369,10 +341,11 @@ MytinytodoAjaxApi.prototype = {
});
},
changeListOrder(params, callback) {
changeListOrder: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists',
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists',
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'order',
@ -383,10 +356,11 @@ MytinytodoAjaxApi.prototype = {
});
},
clearCompletedInList(params, callback) {
clearCompletedInList: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: this.useREST ? 'PUT' : 'POST',
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'clearCompleted',
@ -398,9 +372,10 @@ MytinytodoAjaxApi.prototype = {
/* Auth */
login(params, callback) {
login: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'login',
url: mtt.apiUrl + 'login',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
@ -411,9 +386,10 @@ MytinytodoAjaxApi.prototype = {
});
},
logout(params, callback) {
logout: function(params, callback)
{
$.ajax({
url: mytinytodo.apiUrl + 'logout',
url: mtt.apiUrl + 'logout',
method: 'POST',
success: callback,
dataType: 'json'
@ -421,3 +397,5 @@ MytinytodoAjaxApi.prototype = {
}
};
})();

View file

@ -5,9 +5,9 @@
/* Dark mode */
/* prefers-color-scheme media query is used in html link tag */
:root {
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--color-bg: #151515;
@ -29,7 +29,6 @@
--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;
@ -47,13 +46,13 @@
--color-btn-tab-hover-bg: #5a5a5a;
/* Menu */
--color-menu: #252525;
--color-menu: #2b2b2b;
--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);
--color-popup-shadow: 1px 2px 6px 1px rgba(0,0,0,1);
/* Tasklist */
--color-tasklist-row: var(--color-bg);
@ -63,11 +62,8 @@
--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-taglist-text-hover: var(--color-bg);
--color-taglist-text-hover-bg: var(--color-text-default);
--color-tasklist-listname: #bbb;
--color-tasklist-listname-bg: #333;
@ -94,6 +90,5 @@
--color-placeholder: #444;
--color-placeholder-border: #555;
--svg-select: var(--svg-select-dark);
}
}

View file

@ -1,7 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" >
<g stroke="#000" fill="none" stroke-width="1.0">
<polyline points="10 0.5, 0.5 0.5, 0.5 15.5, 13.5 15.5, 13.5 7.5" stroke-linejoin="round"/>
<polyline points="7 7, 6 10, 9 9, 15 3, 13 1, 7 7, 6 10" />
<line x1="7" y1="7" x2="9" y2="9" />
<!-- <rect x="0" y="0" width="16" height="16" fill="lightpink" /> -->
<g stroke="#000" fill="none">
<polyline points="10.5 1, 1 1, 1 15, 13 15, 13 7.5" stroke-width="1.0" stroke-linejoin="round"/>
<g stroke-width="1.0" >
<polyline points="7 7, 6 10, 9 9, 15 3, 13 1, 7 7, 6 10" />
<line x1="7" y1="7" x2="9" y2="9" />
</g>
</g>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 403 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
<polyline points="2 6, 7 11, 12 6" stroke-width="2" stroke="#aaa" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 148 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
<polyline points="2 6, 7 11, 12 6" stroke-width="2" stroke="#444" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 148 B

View file

@ -1,62 +0,0 @@
<?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();
}

View file

@ -27,11 +27,11 @@
.markdown-note dl,
.markdown-note table,
.markdown-note pre {
margin: 12px 0;
margin: 10px 0;
}
.markdown-note ul,
.markdown-note ol {
padding-left: 2.3rem;
padding-left: 20px;
}
.markdown-note ul.no-list,
.markdown-note ol.no-list {
@ -79,20 +79,23 @@
.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;
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
margin: 0 2px;
padding: 0px 5px;
border: 1px solid var(--color-md-border);
background-color: var(--color-md-bg-highlighted);
border-radius: 3px;
}
.markdown-note code {
white-space: break-spaces;
white-space: nowrap;
}
.markdown-note pre {
font-size: 13px;
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
line-height: 1.2rem;
padding: 10px;
padding: 6px 10px;
background-color: var(--color-md-bg-highlighted);
border: 1px solid var(--color-md-border);
overflow: auto;
border-radius: 3px;
}

View file

@ -1,6 +1,7 @@
@media print {
html,body { height:auto; }
html { height:0; }
body { height:0; min-height:0; margin:0; }
h2 { display: none; }
h3 { border-bottom:2px solid #777777; }
#toolbar { display: none; }
@ -9,9 +10,8 @@
.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-tab a span { text-align:left; padding:0; max-width:none; font-size:1.3rem; color:#000; }
.mtt-tabs-new-button { display:none; }
#tabs_buttons { display:none; }

View file

@ -33,7 +33,6 @@
--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;
@ -67,11 +66,8 @@
--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-taglist-text-hover: var(--color-bg);
--color-taglist-text-hover-bg: var(--color-btn-tab-hover-bg);
--color-tasklist-listname: #555;
--color-tasklist-listname-bg: #eee;
@ -101,59 +97,31 @@
--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 */
}
html { height:100%; overflow-y:scroll; 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-family: -apple-system, BlinkMacSystemFont, "Segoe UI", /*Roboto,*/ Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-size: 1rem;
}
#mtt {
flex: 1 0 auto;
width:100%;
max-width:1100px;
max-width:1000px;
padding:8px;
margin-bottom: 1rem;
margin-bottom:8px;
box-sizing: border-box;
}
@ -173,24 +141,14 @@ form { display: inline; }
/* preload images */
body::after {
position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
content:url(images/logo-loading.gif);
content:url(images/logo-loading.gif) url(images/checkmark.svg) url(images/arr-right.svg) url(images/closetag.svg) url(images/rss.svg) url(images/rss-disabled.svg);
}
h3.page-title {
margin:0;
border-bottom:2px solid var(--color-content-delimiter);
margin-bottom:1rem;
padding:0.6rem 0;
font-size:1.1rem;
}
h3.page-title { margin:0; border-bottom:2px solid var(--color-content-delimiter); margin-bottom:10px; 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 { flex-shrink:0; width:100%; max-width:1000px; }
#footer_content {
background-color: var(--color-footer);
border-top: 1px solid var(--color-content-delimiter);
@ -228,16 +186,11 @@ a { color: var(--color-link); cursor:pointer; text-decoration:underline; }
.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;
transition: box-shadow .15s 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);
padding:2px;
}
.form-input:focus {
outline: none;
@ -306,45 +259,19 @@ select.form-input {
.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;
.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;
border-bottom:none; border-top-right-radius:8px; transition:background-color 0.1s ease-in;
}
.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;
box-sizing:border-box; height:2.2rem; padding:1px 0.3rem 0 0.3rem; display:flex; align-items:center;
}
.mtt-tab a:active { outline:0; text-decoration:none; }
.mtt-tab a>span { display:inline-block; text-align:center; min-width:75px; max-width:195px; cursor:pointer; padding:0; overflow:hidden; padding-left:0.3rem; padding-right:0.3rem;}
.mtt-tab .list-action { display:none; }
.mtt-tab .mtt-img-button:hover,
.mtt-tab .mtt-img-button.mtt-menu-button-active {
@ -370,19 +297,6 @@ select.form-input {
background-color: var(--color-placeholder);
border-color: var(--color-placeholder-border);
}
.mtt-tab .counter {
display: inline-block;
min-width: 1.2rem;
height: 1rem;
border-radius: 1rem;
font-size: 0.8rem;
text-align: center;
background-color: #686868; /* #de5141 */
color: white;
}
.mtt-tab .counter.hidden {
display: none;
}
#tabs_buttons {
padding-left:2px; padding-right:2px;
@ -443,11 +357,7 @@ select.form-input {
height:7px; width:7px;
}
.newtask-n-search-container {
display: flex;
justify-content: flex-end;
align-items: center;
}
.newtask-n-search-container { display:flex; justify-content:flex-end; }
/* Quick Task Add */
.taskbox-c { flex-grow:1; display:flex; align-items:center; }
@ -457,10 +367,7 @@ select.form-input {
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.show-all-tasks .taskbox-c, #mtt.readonly .taskbox-c { display:none; }
.mtt-taskbox-icon {
width:16px; height:16px;
@ -544,7 +451,7 @@ select.form-input {
margin-left:3px;
display:inline-block;
width:1em; height:1em; /* em! */
mask: var(--svg-closetag) no-repeat; -webkit-mask: var(--svg-closetag) no-repeat;
mask: url(images/closetag.svg) no-repeat; -webkit-mask: url(images/closetag.svg) no-repeat;
background-color: var(--color-btn-reduced);
transition: background-color 0.1s ease-in;
}
@ -552,36 +459,19 @@ select.form-input {
background-color: var(--color-btn-reduced-hover);
}
#mtt-tag-toolbar-close span {
mask: var(--svg-closetag) no-repeat; -webkit-mask: var(--svg-closetag) no-repeat;
mask: url(images/closetag.svg) no-repeat; -webkit-mask: url(images/closetag.svg) no-repeat;
background-color: var(--color-btn-reduced);
}
a.mtt-back-button {
font-size:1rem;
}
h3.page-title a.mtt-back-button {
display:inline-block;
width:1.3rem;
min-width:1.3rem;
height:1.3rem;
padding-right:0.4rem;
position: relative;
top:4px;
mask: url(images/back.svg) no-repeat; -webkit-mask: url(images/back.svg) no-repeat;
background-color: var(--color-btn-default);
}
/* Tasklist */
.task-toggle {
visibility: hidden;
cursor: pointer;
width: 1rem;
height: 1rem;
width: 1rem; height: 1rem;
display: inline-block;
margin-right: 4px;
mask: var(--svg-note-toggle) no-repeat; -webkit-mask: var(--svg-note-toggle) no-repeat;
margin-right: 2px;
mask: url(images/note-toggle.svg) no-repeat; -webkit-mask: url(images/note-toggle.svg) no-repeat;
background-color: var(--color-btn-reduced);
transition: transform .1s linear, background-color .1s ease-in;
}
@ -606,14 +496,9 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
background-color: var(--color-tasklist-row); /* ?? */
position:relative; /* for z-index */
}
/*#mtt:not(.touch-device) #tasklist li.task-row:hover,*/
#mtt:not(.touch-device) #tasklist li.task-row.menu-active {
#tasklist li.task-row:hover,
#tasklist li.task-row.menu-active {
z-index: 1;
box-shadow: 0 0 2px var(--color-tasklisk-hover-shadow);
border-radius: 5px;
}
#mtt:not(.touch-device) #tasklist li.task-row.menu-active:not(.task-expanded) {
border-bottom-color: transparent;
}
#tasklist .task-block {
display: flex;
@ -625,9 +510,6 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
border:1px solid var(--color-tasklist-row-expanded-border);
border-radius: 3px;
}
#tasklist li.task-row:not(.task-expanded):has(+ li.task-row.task-expanded) {
border-bottom-color: transparent;
}
#tasklist li.task-row.task-has-note.task-expanded .task-block {
border-bottom: 1px solid var(--color-tasklist-row-inter-border);
}
@ -635,12 +517,9 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
display: flex;
align-items: center;
height: 1.2rem; /* same as line-height of task-middle */
/* border-radius: 0.01px; overflow: hidden;*/ /* to remove outline glitch in firefox, see https://bugzilla.mozilla.org/show_bug.cgi?id=1671784 */
}
.task-middle {
flex-grow:1;
margin: -0.5rem 0px;
padding: 0.5rem 0px;
margin-left: 5px;
align-items: baseline;
line-height: 1.2rem;
@ -649,10 +528,7 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
.task-middle-top {
display: flex;
}
.task-actions {
flex:0 0 1rem;
margin-left:5px;
}
.task-actions { flex:0 0 1rem; margin-left:5px; }
.task-left label { min-width:18px; text-align:center; } /* Safari has small checkboxes */
.task-date {
@ -661,27 +537,13 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
margin-top: 0.2rem;
display: none;
}
.task-id::after {
content: ' · '; /* '&middot;' */
}
#mtt.show-date .task-date {
display: block;
}
#mtt.show-date.date-inline .task-date {
display: inline-block; /* for RTL */
margin:0;
margin-left:3px;
.task-id {
/*display: none;*/
}
#mtt.show-inline-date .task-date { display: block; }
.task-through { overflow:hidden; flex-grow:1; }
#mtt.view-task-on-click .task-title {
cursor: pointer;
}
.task-title a {
color: var(--color-text-default);
}
.task-title a:hover {
color: var(--color-tasklist-link-hover);
}
.task-title a { color: var(--color-text-default); }
.task-title a:hover { color: var(--color-tasklist-link-hover); }
#mtt.readonly #tasklist li.task-row .task-actions { display:none; }
.task-listname {
background-color: var(--color-tasklist-listname-bg);
@ -689,20 +551,8 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
padding: 0px 3px;
margin: 0px 2px;
}
.task-tags {
margin:0px 3px;
display: inline;
}
.task-tags:empty {
margin:0;
}
.task-tags .tag {
font-size:0.9rem;
font-weight:bold;
color: var(--color-tasklist-tag);
text-decoration:underline;
cursor: pointer;
}
.task-tags { padding:0px 2px; }
.task-tags .tag { font-size:0.9rem; font-weight:bold; color: var(--color-tasklist-tag); text-decoration:underline; }
.duedate { color:var(--color-duedate-default); padding:0px; padding-left:1px; margin-left:5px; white-space:nowrap; }
.duedate:before {
content:'\2192\20';
@ -784,6 +634,8 @@ li.task-row.task-completed .task-note-block .task-note { text-decoration:line-th
.task-note-actions { font-size:0.8rem; }
.hidden { display:none; }
.invisible { visibility:hidden; }
.in500 { width:500px; color: var(--color-text-reduced); }
.in100 { width:100px; color: var(--color-text-reduced); }
.task-note a { color: var(--color-tasklist-note-link); }
.task-note a:hover { color: var(--color-tasklist-link-hover) }
@ -810,47 +662,32 @@ li.task-row.task-completed .task-note-block .task-note { text-decoration:line-th
text-align: center;
}
.form-container {
display: flex;
flex-wrap: wrap;
}
.form-row {
margin-top:0.6rem;
}
.form-row-short {
margin-right:12px;
}
.form-row:not(.form-row-short) {
width:100%;
}
.form-row { margin-top:0.6rem; }
.form-row .h {
font-weight:bold;
color: var(--color-text-reduced);
}
.form-row div.h {
margin-bottom:3px;
}
.form-input {
color: var(--color-text-reduced);
}
.form-input.in100 {
width:100px;
}
.form-input.inmax {
width:100%;
box-sizing:border-box;
}
textarea.form-input.inmax {
height:280px;
.form-row div.h { margin-bottom:3px; }
.form-row-short-end { clear:both; }
#page_taskedit .form-row .in500 { width:100%; box-sizing:border-box; }
#page_taskedit .form-row textarea.in500 {
height:300px;
font-size:13px;
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
white-space: pre;
padding: 5px;
}
#page_taskedit .form-row-short { float:left; margin-right:12px; }
#alltags .tag { font-weight:bold; color:var(--color-tasklist-tag); }
#alltags .tag:hover {
background-color:var(--color-taglist-text-hover-bg);
color: var(--color-taglist-text-hover);
}
.alltags-cell { width:1%; white-space:nowrap; padding-left:5px; }
#page_taskedit.mtt-inadd .mtt-inedit { display:none; }
#page_taskedit.mtt-inedit .mtt-inadd { display:none; }
#taskedit_id, #taskviewer_id {
#taskedit_id {
color: var(--color-text-reduced2);
font-weight: normal;
margin-left: 5px;
@ -858,61 +695,15 @@ textarea.form-input.inmax {
#taskedit_info { font-size:1rem; font-weight:normal; color:var(--color-text-reduced2); margin-bottom:6px; }
#taskedit_info > div { margin-top:6px; }
#page_taskviewer h3 {
display: flex;
align-items: baseline;
}
#page_taskviewer .container {
display: flex;
max-width: 100%;
}
#page_taskviewer .container .left {
flex-grow: 1;
min-width: 0; /* to limit long text*/
border-right: 1px solid var(--color-tasklist-row-expanded-border);
padding-right: 0.5rem;
}
#page_taskviewer .container .right {
min-width:300px;
max-width:300px;
padding-left: 0.5rem;
overflow: hidden;
}
#page_taskviewer .container .right .property {
padding: 0.8rem 0.5rem;
}
#page_taskviewer .container .right .property:not(:last-child) {
border-bottom: 1px solid var(--color-tasklist-row-expanded-border);
}
#page_taskviewer .container .right .form-bottom-buttons .mtt-back-button {
display: none;
}
#page_taskviewer .title {
word-break: break-word;
}
#page_taskviewer .note {
line-height: 1.3;
display: block;
white-space: normal;
word-wrap: break-word;
}
#page_taskviewer .no-note {
color: var(--color-text-reduced3);
display: none;
}
#page_taskviewer.no-note .no-note {
display: block;
}
#page_taskviewer.no-note .note {
display: none;
}
#page_taskviewer .property.tags .content {
display: inline-flex;
flex-wrap: wrap;
max-width: 100%;
}
#mtt.readonly #taskviewer_edit_btn {
display: none;
a.mtt-back-button { font-size:1rem; }
h3.page-title a.mtt-back-button {
display:inline-block;
width:1.3rem; height:1.3rem;
margin-right:0.4rem;
position: relative;
top:4px;
mask: url(images/back.svg) no-repeat; -webkit-mask: url(images/back.svg) no-repeat;
background-color: var(--color-btn-default);
}
/* autocomplete */
@ -939,7 +730,6 @@ textarea.form-input.inmax {
#priopopup .prio-pos:hover { border-color: var(--color-priority-urgent); }
#priopopup .prio-pos-1:hover { border-color: var(--color-priority-high); }
#tagcloudbtn {
font-size: 1rem;
font-weight: normal;
@ -961,6 +751,19 @@ textarea.form-input.inmax {
#tagcloud > div {
padding:5px;
}
#tagcloud .tag {
margin: 2px;
padding: 2px;
line-height:160%;
color: var(--color-text-default);
cursor: pointer;
text-decoration: underline;
white-space: nowrap;
}
#tagcloud .tag:hover {
background-color:var(--color-taglist-text-hover-bg);
color: var(--color-taglist-text-hover);
}
#tagcloudload {
display:none;
height:24px;
@ -978,11 +781,8 @@ textarea.form-input.inmax {
#tagcloudAllLists:disabled + label {
opacity: 0.6;
}
#tagcloudSearch {
margin-left: 0.5rem;
}
#tagcloudcancel span {
mask: var(--svg-closetag) no-repeat; -webkit-mask: var(--svg-closetag) no-repeat;
mask: url(images/closetag.svg) no-repeat; -webkit-mask: url(images/closetag.svg) no-repeat;
background-color: var(--color-btn-reduced);
}
#tagcloudcontent {
@ -990,41 +790,6 @@ textarea.form-input.inmax {
text-overflow: ellipsis;
}
#tagcloud .tag,
#alltags .tag,
#page_taskviewer .property.tags .tag {
display: inline-block;
padding: 2px 0.5em;
border-radius: 1em;
color:var(--color-taglist-tag);
background-color:var(--color-taglist-tag-bg);
cursor: pointer;
background-clip: padding-box; /* for transparent border */
border: 1px solid transparent;
margin-bottom:2px;
margin-right: 1px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 1em;
font-weight: normal;
}
#alltags .tag {
margin-right: 3px;
}
#page_taskviewer .property.tags .tag {
overflow: hidden;
text-overflow: ellipsis;
margin-bottom:0;
margin-top:3px;
}
#tagcloud .tag:hover,
#alltags .tag:hover,
#page_taskviewer .property.tags .tag:hover {
/*background-color:var(--color-taglist-tag-hover-bg);*/
border-color: var(--color-taglist-tag-hover-bg);;
color: var(--color-taglist-tag-hover);
}
.ui-datepicker {
width: 250px;
@ -1070,26 +835,10 @@ textarea.form-input.inmax {
background-color: var(--color-btn-default);
}
.ui-datepicker .ui-icon-circle-triangle-e { display:block; position:absolute; top:50%; margin-top:-8px; right:50%;
mask: var(--svg-arr-right) no-repeat; -webkit-mask: var(--svg-arr-right) no-repeat;
mask: url(images/arr-right.svg) no-repeat; -webkit-mask: url(images/arr-right.svg) no-repeat;
background-color: var(--color-btn-default);
}
.ui-datepicker select {
appearance: none;
-webkit-appearance: none; -moz-appearance: none;
background: transparent var(--svg-select) no-repeat top 2px right 4px;
background-size: 1rem 1rem;
padding: 1px 3px;
padding-right: calc(1rem + 7px);
border: 1px solid var(--color-border-default);
border-radius: 2px;
color: var(--color-text-default);
margin: 0 2px;
}
.ui-datepicker select:focus {
outline:none;
/*border-color: var(--color-border-focus);
box-shadow:0 0 0 2px var(--color-border-focus-shadow);*/
}
.mtt-menu-button {
user-select: none;
@ -1154,24 +903,15 @@ textarea.form-input.inmax {
.mtt-menu-container li.mtt-menu-delimiter { height:0px; line-height:0; border-bottom:1px solid var(--color-menu-border); margin:2px -1px; padding:0px; font-size:0px; }
.mtt-menu-container .menu-icon { width:16px; height:16px; position:absolute; left:6px; top:50%; margin-top:-8px; }
li.mtt-item-checked .menu-icon {
mask: var(--svg-checkmark) no-repeat; -webkit-mask: var(--svg-checkmark) no-repeat;
mask: url(images/checkmark.svg) no-repeat; -webkit-mask: url(images/checkmark.svg) no-repeat;
background-color: var(--color-btn-default);
}
li.mtt-menu-indicator .submenu-icon {
position:absolute; right:6px; top:50%; margin-top:-8px;
width:16px; height:16px;
mask: var(--svg-arr-right) no-repeat; -webkit-mask: var(--svg-arr-right) no-repeat;
mask: url(images/arr-right.svg) no-repeat; -webkit-mask: url(images/arr-right.svg) no-repeat;
background-color: var(--color-btn-default);
}
.mtt-menu-container .counter {
position:absolute; right:6px; top:50%; margin-top:-8px;
height: 16px; min-width:16px;
border-radius: 1rem;
font-size: 0.8rem;
text-align: center;
background-color: #686868; /* #de5141 */
color: white;
}
li.mtt-item-hidden { display:none; }
#slmenucontainer li.mtt-list-hidden a { font-style:italic; }
@ -1181,11 +921,11 @@ li.mtt-item-hidden { display:none; }
/**/
#btnRssFeed .menu-icon { background:var(--svg-rss) no-repeat; }
#btnRssFeed.mtt-item-disabled .menu-icon { background:var(--svg-rss-disabled) no-repeat; }
#btnRssFeed .menu-icon { background:url(images/rss.svg) no-repeat; }
#btnRssFeed.mtt-item-disabled .menu-icon { background:url(images/rss-disabled.svg) no-repeat; }
#task, #search {
transition: box-shadow .1s ease-in-out;
transition: box-shadow .15s ease-in-out;
}
#task:focus, #search:focus {
outline: none;
@ -1193,60 +933,18 @@ li.mtt-item-hidden { display:none; }
box-shadow: 0 0 0 2px var(--color-border-focus-shadow);
}
.monospace, pre {
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
}
.mtt-settings-table {
width:100%;
}
.mtt-settings-table { width:100%; }
.mtt-settings-table .tr {
border-bottom:1px solid var(--color-row-underlinig);
padding: 10px 0;
vertical-align:top;
display: flex;
}
.mtt-settings-table .tr.group-header {
border-bottom: none !important;
padding-bottom: 0 !important;
background-color: unset !important;
}
.mtt-settings-table .group {
margin-top: 0.75rem;
margin-bottom: 0.5rem;
border-radius: 1rem;
background-color: var(--color-settings-row);
}
.mtt-settings-table .group .tr {
margin: 0 10px;
padding: 1rem 0;
}
.mtt-settings-table .group .th {
padding-left: 0;
}
.mtt-settings-table .group .td {
padding-right: 0;
}
.mtt-settings-table .tr:not(:last-child) {
border-bottom:1px solid var(--color-row-underlinig);
}
.mtt-settings-table .th, .mtt-settings-table .td {
.mtt-settings-table .th,
.mtt-settings-table .td {
padding: 0 10px;
}
.mtt-settings-table .th {
width: 30%;
font-weight: bold;
font-weight: 600;
}
.mtt-settings-table .group-header .th {
font-size: 0.9rem;
}
.mtt-settings-table .td {
flex-grow: 1;
line-height: 1.6em;
}
.mtt-settings-table .td.extensions {
line-height: 1.7em;
}
.mtt-settings-table .th { width:30%; font-weight: bold; }
.mtt-settings-table .descr {
display: block;
font-size:0.8rem;
@ -1254,40 +952,21 @@ li.mtt-item-hidden { display:none; }
color:var(--color-text-reduced);
padding-top: 4px;
}
.mtt-settings-table .in350 { min-width:350px; }
.mtt-settings-table .inmax { width:100%; }
.mtt-settings-table textarea.in350 { height:400px; }
.mtt-settings-table textarea.inmax {
height:400px;
width:100%;
.mtt-settings-table .td {
line-height: 1.5em;
}
.mtt-settings-table .in350 { min-width:350px; }
.mtt-settings-table textarea.in350 { height:400px; }
.mtt-settings-table input,
.mtt-settings-table select,
.mtt-settings-table button,
.mtt-settings-table textarea,
.mtt-settings-upload-button {
.mtt-settings-table textarea {
padding: 3px;
border: 1px solid var(--color-border-default);
border-radius: 2px;
background-color: var(--color-input-bg);
color: var(--color-text-default);
}
.mtt-settings-table button:disabled {
background-color: var(--color-btn-hover);
color: var(--color-text-reduced2);
}
.mtt-settings-upload-button input[type=file] {
visibility:hidden;
position:absolute;
z-index:-2;
}
.mtt-settings-table select {
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);
/*background-color: var(--color-bg);*/
color: var(--color-text-default)
}
.mtt-settings-table select { padding:2px; }
.mtt-settings-table input:focus,
.mtt-settings-table select:focus {
outline:none;
@ -1297,41 +976,15 @@ li.mtt-item-hidden { display:none; }
.mtt-settings-table a {
color: var(--color-text-default);
}
.mtt-settings-table button,
.mtt-settings-upload-button {
padding: 3px 6px;
border-radius: 3px;
}
.mtt-settings-table button:hover,
.mtt-settings-upload-button:hover {
background-color: var(--color-btn-hover);
}
.mtt-settings-table .form-bottom-buttons button {
min-width: 4rem;
padding: 4px 6px;
margin: 0 6px;
background-color: var(--color-submit);
border: 1px solid var(--color-border-default);
border-radius: 3px;
}
.mtt-settings-table .form-bottom-buttons button:hover {
background-color: var(--color-submit-hover);
}
#modal {
position: fixed;
position: absolute;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#modal .modal-box {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
min-width: 350px;
max-width: 1100px; /* same as #mtt */
max-width: 1000px; /* same as #mtt */
border: 1px solid var(--color-menu-border);
background: var(--color-bg);
box-shadow: var(--color-popup-shadow);
@ -1361,12 +1014,10 @@ li.mtt-item-hidden { display:none; }
font-size: 16px;
-webkit-text-size-adjust: 100%; /* Dont increase font-size in horizontal orientation on ios */
}
textarea.form-input.inmax { font-size: 14px; }
#page_taskedit .form-row textarea.in500 { font-size: 14px; }
}
/* ========== narrow screens =========*/
/* narrow screens */
@media only screen and (max-width: 600px) {
#mtt { padding: 15px 8px 0px; }
@ -1382,16 +1033,7 @@ li.mtt-item-hidden { display:none; }
.mtt-img-button { padding:4px; }
.mtt-menu-button { padding:4px; }
h3.page-title a.mtt-back-button {
width: 2rem;
}
#page_ajax.mtt-page-settings h3.page-title {
padding-left: 10px;
border: none;
}
#page_ajax.mtt-page-settings h3.page-title a.mtt-back-button {
width: 1.5rem;
}
h3.page-title a.mtt-back-button { width:2rem; }
.mtt-tabs-new-button { padding-left:0.4rem; padding-right:0.4rem; } /* make thiсker */
@ -1408,10 +1050,6 @@ li.mtt-item-hidden { display:none; }
#toolbar.mtt-insearch .searchbox-c { width:100%; max-width:100%; }
#search { padding:5px 20px; border-radius:15px; }
#tagcloudSearch {
margin-top: 0.5rem;
}
.task-date {
white-space: nowrap;
overflow: hidden;
@ -1436,96 +1074,51 @@ li.mtt-item-hidden { display:none; }
#tasklist li.task-row .task-through { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#tasklist li.task-row.task-expanded .task-note-block { display:none; }
#tasklist li.task-row.clicked {
border:1px solid var(--color-tasklist-row-expanded-border);
border-radius: 3px;
}
#tasklist li.task-row.task-has-note.clicked .task-block {
border-bottom: 1px solid var(--color-tasklist-row-inter-border);
}
#tasklist li.task-row.clicked .task-actions { display:block; }
#tasklist li.task-row.clicked .task-through { white-space:normal; display:inline; }
#tasklist li.task-row.clicked.task-has-note .task-note-block { display:block; }
.task-toggle { display:none; }
#tasklist .mtt-task-placeholder { line-height:1rem; padding-top:0.6rem; padding-bottom:0.6rem; }
#tasklist .ui-sortable-helper { box-shadow:0px 0px 3px #555; opacity: 0.9; }
#tasklist .ui-sortable-helper .task-left { visibility: hidden }
#tasklist .ui-sortable-helper .task-actions > * { visibility: hidden }
#tasklist .ui-sortable-helper { box-shadow:0px 0px 3px #555; }
#page_taskedit { max-width:100%; border:none; position:static; padding:0; }
#page_taskedit .form-table { width:100%; }
#page_taskedit .form-row textarea { height: 150px; }
#page_taskedit .form-row textarea { height: 70px; }
#loading { padding:0px; padding-top:1px; padding-right:1px; height:16px; overflow:hidden; }
#tagcloud { max-width:100%; margin:0px 5px 5px 5px; }
.mtt-settings-table .in350 { min-width:50px; width:100%; }
.mtt-settings-table .in350 { min-width:50px; }
.mtt-notes-showhide { display:none; }
.mtt-menu-container li {
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
#page_taskviewer .container {
flex-direction: column;
}
#page_taskviewer .container .left {
border-right: none;
padding-right: 0;
}
#page_taskviewer .container .right {
max-width: unset;
padding-left: 0;
}
#page_taskviewer .container .right .property {
padding: 0.8rem 0;
}
#page_taskviewer .container .right .form-bottom-buttons .mtt-back-button {
display: block;
}
#page_taskviewer .note,
#page_taskviewer .no-note {
padding-bottom: 1rem;
margin-bottom: 1rem;
border-bottom: 2px solid var(--color-tasklist-row-expanded-border);
}
.mtt-menu-container li { padding-top:0.3rem; padding-bottom:0.3rem; }
.mtt-settings-table .tr { display: block; }
.mtt-settings-table .tr:not(.form-bottom-buttons) {
display: block;
background-color:var(--color-settings-row);
border:1px solid var(--color-settings-row);
border-radius: 10px;
padding:10px 15px;
margin-bottom: 5px;
}
.mtt-settings-table .group {
background-color: var(--color-settings-row);
}
.mtt-settings-table .group .tr {
border-radius: 0;
padding: 1rem 1rem;
margin: 0 0;
margin-bottom: 5px;
background-color: unset;
}
.mtt-settings-table .group .tr:not(:last-child) {
border-bottom:1px solid var(--color-border-default);
}
.mtt-settings-table .group .th {
padding: 0;
}
.mtt-settings-table .th {
width: auto;
padding-left: 0px;
}
.mtt-settings-table .td {
padding-top: 4px;
padding-left: 0px;
}
.mtt-settings-table .td.extensions {
line-height: 2em;
}
.mtt-settings-table input,
.mtt-settings-table select,
.mtt-settings-table label {
margin-top:5px;
box-sizing: border-box;
}
.mtt-settings-table .th { width: auto; padding-left: 0px; }
.mtt-settings-table .td { padding-top: 4px; padding-left: 0px; }
.mtt-settings-table input , .mtt-settings-table select, .mtt-settings-table label { margin-top:5px; background-color: var(--color-settings-row); }
.form-bottom-buttons > * {
padding: 7px;
border-radius: 14px;
}
#modal {
width: 90%;
}
} /* end of @media min 600px */

View file

@ -1,23 +1,13 @@
body { direction:rtl; }
.topblock h2 {
background-position: right;
padding-left: 10px;
padding-right: 30px;
}
h2 { padding-left:10px; padding-right:0px; }
#loading { margin-left:6px; margin-right:0px; }
.bar-menu { text-align: left; }
.bar-menu > * {
margin-right: 10px;
margin-left: unset;
}
.mtt-tab { margin-left:3px; margin-right:0px; border-top-right-radius:0px; border-top-left-radius:8px; }
.mtt-tabs-new-button { border-top-right-radius:0px; border-top-left-radius:8px; }
.mtt-tabs-select-button>span { transform:rotateY(180deg); }
#task { padding-left:20px; padding-right:4px; }
.mtt-taskbox-icon { left:2px; right:auto; transform:rotateY(180deg); }
#newtask_adv { margin-left:0; margin-right:0.5rem; transform:rotateY(180deg); }
#newtask_adv { margin-left:0; margin-right:0.5rem; }
.mtt-searchbox-icon.mtt-icon-search { right:4px; left:auto; }
.mtt-searchbox-icon.mtt-icon-cancelsearch { left:4px; right:auto; }
@ -37,9 +27,10 @@ body { direction:rtl; }
.duedate:before { content:'\20\2190\20'; }
#tagcloud { box-shadow:-1px 2px 5px rgba(0,0,0,0.5); }
#tagcloudcancel { float:left; }
h3.page-title a.mtt-back-button { transform:rotate(180deg); }
.form-row-short { margin-left:12px; margin-right:0; }
#taskedit-date { float:left; }
#page_taskedit .form-row-short { float:right; margin-left:12px; margin-right:0; }
.alltags-cell { padding-left:0; padding-right:5px; }
.mtt-menu-container { box-shadow:-1px 2px 5px rgba(0,0,0,0.5); }

View file

@ -2,14 +2,14 @@
// Rename it to config.php before using in docker.
if (getenv('MTT_DB_TYPE') == 'mysql' || getenv('MTT_DB_TYPE') == 'postgres') {
define("MTT_DB_TYPE", getenv('MTT_DB_TYPE'));
define("MTT_DB_HOST", getenv('MTT_DB_HOST') ?: "undefined_host");
define("MTT_DB_NAME", getenv('MTT_DB_NAME') ?: "undefined_db");
define("MTT_DB_USER", getenv('MTT_DB_USER') ?: "undefined_user");
define("MTT_DB_PASSWORD", getenv('MTT_DB_PASSWORD') ?: "");
define("MTT_DB_PREFIX", getenv('MTT_DB_PREFIX') ?: "mtt_");
define("MTT_DB_DRIVER", getenv('MTT_DB_DRIVER') ?: "");
if (getenv('MTT_DB_TYPE') == 'mysql') {
define("MTT_DB_TYPE", "mysql");
define("MTT_DB_HOST", getenv('MTT_DB_HOST'));
define("MTT_DB_NAME", getenv('MTT_DB_NAME'));
define("MTT_DB_USER", getenv('MTT_DB_USER'));
define("MTT_DB_PASSWORD", getenv('MTT_DB_PASSWORD'));
define("MTT_DB_PREFIX", getenv('MTT_DB_PREFIX'));
define("MTT_DB_DRIVER", getenv('MTT_DB_DRIVER'));
}
else if (getenv('MTT_DB_TYPE') == 'sqlite') {
define("MTT_DB_TYPE", "sqlite");
@ -17,7 +17,3 @@ else if (getenv('MTT_DB_TYPE') == 'sqlite') {
}
define("MTT_SALT", "Put Random Text Here");
//define("MTT_DISABLE_EXT", 1);
if (getenv('MTT_API_USE_PATH_INFO')) {
define("MTT_API_USE_PATH_INFO", 1);
}

View file

@ -24,25 +24,33 @@ if (!$listData) {
die("No list found.");
}
$data = DBCore::default()->getTasksByListId($listId, '', (int)$listData['sorting']);
$sqlSort = "ORDER BY compl ASC, ";
if($listData['sorting'] == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC";
elseif($listData['sorting'] == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC";
else $sqlSort .= "ow ASC";
if (_get('format') == 'ical') {
printICal($listData, $data);
}
else {
printCSV($listData, $data);
$data = array();
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist WHERE list_id=$listId $sqlSort");
while($r = $q->fetchAssoc())
{
$data[] = $r;
}
$format = _get('format');
function printCSV(array $listData, array $data)
if($format == 'ical') printICal($listData, $data);
else printCSV($listData, $data);
function printCSV($listData, $data)
{
$s = "\xEF\xBB\xBF". "Completed;Priority;Task;Notes;Tags;Due;DateCreated;DateCompleted\n";
foreach($data as $r)
{
$s .= ($r['compl']?'1':'0'). ';'.
$r['prio']. ';'. escape_csv($r['title'] ?? ''). ';'.
escape_csv($r['note'] ?? ''). ';'.
escape_csv($r['tags'] ?? ''). ';'.
$r['prio']. ';'. escape_csv($r['title']). ';'.
escape_csv($r['note']). ';'.
escape_csv($r['tags']). ';'.
$r['duedate']. ';'.
date('Y-m-d H:i:s O',$r['d_created']). ';'.
($r['d_completed'] ? date('Y-m-d H:i:s O',$r['d_completed']) :''). "\n";
@ -52,7 +60,7 @@ function printCSV(array $listData, array $data)
print $s;
}
function escape_csv(string $v)
function escape_csv($v)
{
//escape formulas
$nf = '';
@ -63,7 +71,7 @@ function escape_csv(string $v)
return '"'. $nf. str_replace('"', '""', $v). '"';
}
function printICal(array $listData, array $data)
function printICal($listData, $data)
{
$mttToIcalPrio = array("1" => 5, "2" => 1);
$s = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nMETHOD:PUBLISH\r\nCALSCALE:GREGORIAN\r\nPRODID:-//myTinyTodo//iCalendar Export v1.4//EN\r\n".

View file

@ -1,5 +0,0 @@
<FilesMatch "\.(json|md|MD)$">
Order deny,allow
Deny from all
</FilesMatch>

View file

@ -1 +0,0 @@
deny from all

View file

@ -1,6 +0,0 @@
{
"bundleId": "CustomCSS",
"name": "Custom CSS",
"version": "1.0.1",
"description": "Add you own css rules"
}

View file

@ -1,7 +0,0 @@
{
"ext.CustomCSS.name": "Custom CSS",
"customcss.h_css": "CSS",
"customcss.d_css": "Write you own CSS rules",
"customcss.not_writable": "CSS file is not writable, check permissions for custom.css",
"customcss.saved": "Saved"
}

View file

@ -1,7 +0,0 @@
{
"ext.CustomCSS.name": "Własny styl CSS",
"customcss.h_css": "CSS",
"customcss.d_css": "Dodaj własne reguły CSS",
"customcss.not_writable": "Plik stylu nie jest zapisywalny, sprawdź uprawnienia dla pliku custom.css",
"customcss.saved": "Zmiany zostały zapisane"
}

View file

@ -1,7 +0,0 @@
{
"ext.CustomCSS.name": "Дополнительные стили",
"customcss.h_css": "CSS",
"customcss.d_css": "Добавляйте собственные стили CSS",
"customcss.not_writable": "Ошибка записи в файл, проверьте права доступа к custom.css",
"customcss.saved": "Сохранено"
}

View file

@ -1,93 +0,0 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
if (!defined('MTTPATH')) {
die("Unexpected usage.");
}
function mtt_ext_customcss_instance(): MTTExtension
{
return new CustomCssExtension();
}
class CustomCssExtension extends MTTExtension implements MTTExtensionSettingsInterface
{
//the same as dir name
const bundleId = 'CustomCSS';
// settings domain
const domain = "ext.customcss.json";
const cssFilename = 'custom.css';
function init()
{
$prefs = self::preferences();
if (isset($prefs['css'])) {
$href = htmlspecialchars( get_unsafe_mttinfo('theme_url'). self::cssFilename. '?v='. ($prefs['edited'] ?? 0) );
$cb = function() use ($href) {
print "<link rel='stylesheet' type='text/css' href='$href'>\n";
};
add_action('theme_head_end', $cb);
}
}
function settingsPage(): string
{
$e = function($s) { return __($s, true); };
$prefs = self::preferences();
$css = htmlspecialchars($prefs['css'] ?? '');
return
<<<EOD
<div class="tr">
<div class="th"> {$e('customcss.h_css')}
<div class="descr">{$e('customcss.d_css')}</div>
</div>
<div class="td"> <textarea name="css" class="inmax monospace">$css</textarea> </div>
</div>
EOD;
}
function settingsPageType(): int
{
return 0; //default page
}
function saveSettings(array $params, ?string &$outMessage): bool
{
if (defined('MTT_DEMO')) {
$outMessage = "Demo";
return true;
}
$css = $params['css'] ?? '';
$cssFilename = MTT_THEME_PATH. self::cssFilename;
if (!file_exists($cssFilename)) {
@touch($cssFilename);
}
if (!is_writable($cssFilename)) {
$outMessage = __('customcss.not_writable');
return false;
}
@file_put_contents($cssFilename, $css);
$outMessage = __('customcss.saved');
return true;
}
static function preferences(): array
{
$prefs['cssFilename'] = $cssFilename = MTT_THEME_PATH. self::cssFilename;
if (file_exists($prefs['cssFilename'])) {
$prefs['edited'] = filemtime($cssFilename) ?? 0;
$prefs['css'] = @file_get_contents($cssFilename) ?? '';
}
return $prefs;
}
}

View file

@ -1 +0,0 @@
deny from all

View file

@ -1,6 +0,0 @@
{
"bundleId": "CustomSmartSyntax",
"name": "Custom Smart Syntax",
"version": "1.0",
"description": "Prototype to write you own smart syntax parser"
}

View file

@ -1,41 +0,0 @@
<?php declare(strict_types=1);
/*
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.
*/
if (!defined('MTTPATH')) {
die("Unexpected usage.");
}
function mtt_ext_customsmartsyntax_instance(): MTTExtension
{
return new CustomSmartSyntaxExtension();
}
class CustomSmartSyntaxExtension extends MTTExtension implements MTTFilterInterface
{
//the same as dir name
const bundleId = 'CustomSmartSyntax';
function init() {
\MTTFilterCenter::addFilterForAction('parseSmartSyntax', $this);
}
// parseSmartSyntax
function filter($title, &$out)
{
$a = [
'prio' => 0, // int: -1 .. 2
'title' => $title, // string
'tags' => '', // string: "tag1, tag2, tag3,..."
'duedate' => null, // string: "Y-m-d" format
];
// This filter just overwrites results of default parser
$out = $a;
}
}

View file

@ -1 +0,0 @@
deny from all

View file

@ -1,203 +0,0 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2023-2025 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
namespace BackupExtension;
use Exception;
class Backup
{
public $lastErrorString = null;
public $filename;
private $fh;
private $level = 0;
private $tagClosed = true;
function __construct(?string $filename)
{
$this->filename = is_null($filename) ? MTTPATH. 'db/backup.xml' : $filename;
}
function isFileWritable()
{
if (!file_exists($this->filename)) {
@touch($this->filename);
}
if (!is_writable($this->filename)) {
return false;
}
return true;
}
function makeBackup()
{
if (!$this->isFileWritable()) {
$this->lastErrorString = __('backup.not_writable');
return false;
}
$this->fh = fopen($this->filename, 'w');
if ($this->fh === false) {
$ea = error_get_last();
$this->lastErrorString = $ea['message'] ?? "Failed to open file for writing";
return false;
}
$db = \DBConnection::instance();
fwrite($this->fh, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
$this->writeOpeningTag('mttdb', [
'version' => 1,
'appversion' => \mytinytodo\Version::VERSION,
'dbversion' => \mytinytodo\Version::DB_VERSION,
'dbtype' => $db::DBTYPE,
'created' => date(DATE_ATOM)
]);
$this->level = 0;
$this->writeTable($db->prefix.'lists', 'lists', 'list');
$this->writeTable($db->prefix.'todolist', 'tasks', 'task');
$this->writeTable($db->prefix.'tags', 'tags', 'tag');
$this->writeTable($db->prefix.'tag2task', 'tag2task', 'item');
$this->writeTable($db->prefix.'settings', 'settings', 'item');
$this->writeClosingTag('mttdb');
fwrite($this->fh, "\n");
if (!fclose($this->fh)) {
$ea = error_get_last();
$this->lastErrorString = $ea['message'] ?? "Failed to close file";
return false;
}
return true;
}
function writeTable(string $table, string $group, string $itemName)
{
if (!preg_match("/^[\\w:]+$/", $table)) {
throw new Exception("Malformed table name: $table");
}
$db = \DBConnection::instance();
$props = null;
$autoinc = $this->getTableAutoIncrement($table);
if ($autoinc != '') {
$props = ['auto_increment' => $autoinc];
}
$this->writeOpeningTag($group, $props);
$q = $db->dq("SELECT * FROM $table");
while ($r = $q->fetchAssoc()) {
$this->writeItem($itemName, $r);
}
$this->writeClosingTag($group);
}
function writeItem(string $entity, $r)
{
$tagAttrs = null;
if (isset($r['id'])) {
$tagAttrs = ['id' => $r['id']];
unset($r['id']);
}
$this->writeOpeningTag($entity, $tagAttrs);
foreach ($r as $field => $value) {
$props = null;
if (is_null($value)) {
$props['isnull'] = 'yes';
}
$this->writeOpeningTag($field, $props);
$this->writeTagContent((string)$value);
$this->writeClosingTag($field);
}
$this->writeClosingTag($entity);
}
function getTableAutoIncrement($table): string
{
$db = \DBConnection::instance();
if ($db::DBTYPE == \DBConnection::DBTYPE_MYSQL) {
$r = $db->sqa("SHOW TABLE STATUS WHERE Name=?", [$table]);
return (string)$r['Auto_increment'] ?? '';
}
else if ($db::DBTYPE == \DBConnection::DBTYPE_SQLITE) {
$seq = (int)$db->sq("SELECT seq FROM sqlite_sequence WHERE name=?", [$table]);
if ($seq > 0)
return (string)$seq;
}
else if ($db::DBTYPE == \DBConnection::DBTYPE_POSTGRES) {
if ($db->tableFieldExists($table, 'id')) {
$v = (int)$db->sq("SELECT last_value FROM ". $table. '_id_seq');
if ($v > 0)
return (string)$v;
}
}
return '';
}
function writeOpeningTag(string $tag, ?array $attrs = null)
{
if (!preg_match("/^[\\w:]+$/", $tag)) {
throw new Exception("Malformed tag: $tag");
}
$data = "<$tag";
if ($attrs !== null) {
$a = [];
foreach ($attrs as $k => $v) {
if (!preg_match("/^[\\w:-]+$/", $k)) {
throw new Exception("Malformed attribute name: $k");
}
$v = (string)$v;
if (preg_match("/[\\r\\n]+/", $v)) {
throw new Exception("Malformed attribute value: $v");
}
$a[] = "$k=\"". htmlspecialchars($v). "\"";
}
if (count($a) > 0) {
$data .= " ". implode(" ", $a);
}
}
$data .= ">";
$this->write( ($this->tagClosed ? "" : "\n"). str_repeat(' ', $this->level) . $data );
$this->level += 1;
$this->tagClosed = false;
}
function writeClosingTag(string $tag)
{
if (!preg_match("/^[\\w:]+$/", $tag)) {
throw new Exception("Malformed tag: $tag");
}
$this->level -= 1;
if ($this->level < 0) $this->level = 0;
$padding = '';
if ($this->tagClosed) {
$padding = str_repeat(' ', $this->level);
}
$this->write( $padding . "</$tag>\n" );
$this->tagClosed = true;
}
function writeTagContent(?string $content)
{
if ($content !== null) {
$this->write( htmlspecialchars($content, ENT_XML1, 'UTF-8') ); //TODO: make xml compliant?
}
}
function write(string $data)
{
if (false === @fwrite($this->fh, $data)) {
$ea = error_get_last();
throw new Exception("Failed to write to file: ". ($ea['message'] ?? "unknown reason"));
}
}
}

View file

@ -1,126 +0,0 @@
<?php declare(strict_types=1);
/*
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.
*/
namespace BackupExtension;
use DBConnection;
class Check
{
public $lastErrorString = null;
public $report = '';
function check(): bool
{
$db = DBConnection::instance();
$msg = [];
// Task without list
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)");
if ($count) {
$msg[] = "Tasks without list: $count";
}
// Tag without task (not a broblem)
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tags WHERE id NOT IN (SELECT tag_id FROM {$db->prefix}tag2task)");
if ($count) {
$msg[] = "Tags without task: $count";
}
// tag2task no list
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tag2task WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)");
if ($count) {
$msg[] = "tag2task no list: $count";
}
// tag2task no tag
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tag2task WHERE tag_id NOT IN (SELECT id FROM {$db->prefix}tags)");
if ($count) {
$msg[] = "tag2task no tag: $count";
}
// tag2task no task
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tag2task WHERE task_id NOT IN (SELECT id FROM {$db->prefix}todolist)");
if ($count) {
$msg[] = "tag2task no task: $count";
}
$count = 0;
$uniqTag = []; // lowerTag => [id, tag]
$nonuniqTag = []; // id => [tag, lowerTag, uniqId, uniqTag, taskCount]
$q = $db->dq("SELECT id,name,COUNT(task_id) c FROM {$db->prefix}tags t LEFT JOIN {$db->prefix}tag2task tt ON t.id=tt.tag_id GROUP BY id ORDER BY id");
while ($r = $q->fetchAssoc()) {
$v = mb_strtolower((string)$r['name'], 'UTF-8');
if (!isset($uniqTag[$v])) {
$uniqTag[$v] = [$r['id'], $r['name']];
}
else {
$count++;
$nonuniqTag[$r['id']] = [$r['name'], $v, $uniqTag[$v][0], $uniqTag[$v][1], $r['c']];
}
}
if ($count > 0) {
$msg[] = "Non-unique tags: $count";
foreach ($nonuniqTag as $id => $a) {
$msg[] = " ID:{$id} Tag:{$a[0]} (tasks: {$a[4]}) same as ID:{$a[2]} Tag:{$a[3]}";
}
}
if (count($msg) == 0) {
$msg[] = "OK";
}
$this->report = implode("\n", $msg);
return true;
}
function repair(): bool
{
$db = DBConnection::instance();
$db->ex("BEGIN");
// Task without list
$count = (int)$db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)");
if ($count > 0) {
// Move to new list
$listID = \DBCore::default()->createListWithName("Restored tasks");
$db->ex("UPDATE {$db->prefix}todolist SET list_id=? WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)", [$listID]);
}
//Tags
$db->ex("DELETE FROM {$db->prefix}tags WHERE id NOT IN (SELECT tag_id FROM {$db->prefix}tag2task)");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id NOT IN (SELECT id FROM {$db->prefix}todolist)");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE tag_id NOT IN (SELECT id FROM {$db->prefix}tags)");
//Non-unique tags replace with first unique
$uniqTag = [];
$replace = [];
$q = $db->dq("SELECT id,name FROM {$db->prefix}tags t LEFT JOIN {$db->prefix}tag2task tt ON t.id=tt.tag_id GROUP BY id ORDER BY id");
while ($r = $q->fetchAssoc()) {
$v = mb_strtolower((string)$r['name'], 'UTF-8');
if (!isset($uniqTag[$v])) {
$uniqTag[$v] = $r['id'];
}
else {
$replace[$r['id']] = $uniqTag[$v];
}
}
foreach ($replace as $id => $newId) {
$db->ex("UPDATE {$db->prefix}tag2task SET tag_id=? WHERE tag_id=?", [$newId, $id]);
}
$db->ex("DELETE FROM {$db->prefix}tags WHERE id NOT IN (SELECT tag_id FROM {$db->prefix}tag2task)");
// TODO: tag2task no list ?
$db->ex("COMMIT");
return true;
}
}

View file

@ -1,156 +0,0 @@
<?php declare(strict_types=1);
/*
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.
*/
namespace BackupExtension;
use BackupExtension;
use BackupExtension\Backup;
use BackupExtension\Download;
use BackupExtension\Check;
class Controller extends \ApiController
{
function postMakeBackup()
{
require_once('class.backup.php');
$filename = BackupExtension::backupFilePath();
$backup = new Backup($filename);
if (!$backup->makeBackup()) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $backup->lastErrorString ?? '',
];
}
$this->response->data = [
'total' => 1,
'ok' => true,
'msg' => __("backup.done"),
'alertTextOnLoad' => __("backup.done"),
];
}
function postDownload()
{
require_once('class.download.php');
$filename = BackupExtension::backupFilePath();
$download = new Download($filename);
if (!$download->checkFileAccess()) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $download->lastErrorString ?? '',
];
return;
}
$this->response->data = [
'total' => 1,
'redirect' => $download->downloadUrl()
];
}
function getDownload()
{
require_once('class.download.php');
$filename = BackupExtension::backupFilePath();
$download = new Download($filename);
$ott = (string)_get('t');
if (!$download->checkFileAccess($ott)) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $download->lastErrorString ?? '',
];
return;
}
$download->printFile();
exit();
}
function postRestore()
{
require_once('class.restore.php');
$restore = new Restore();
if (!$restore->isUploaded()) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $restore->lastErrorString ?? '',
];
return;
}
if (!$restore->restore()) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $restore->lastErrorString ?? '',
];
return;
}
$this->response->data = [
'total' => 1,
'msg' => __("backup.done"),
'redirect' => get_mttinfo('url'),
];
}
function postCheckInconsistency()
{
require_once('class.check.php');
$check = new Check();
if (!$check->check()) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $check->lastErrorString ?? '',
];
return;
}
$this->response->data = [
'total' => 1,
'ok' => true,
'msg' => __("backup.done"),
];
if ($check->report == 'OK') {
$this->response->data['alertText'] = "OK";
}
else {
$this->response->data['html'] = "<pre>". htmlspecialchars($check->report). "</pre>";
}
}
function postRepairInconsistency()
{
require_once('class.check.php');
$check = new Check();
if (!$check->repair()) {
$this->response->data = [
'total' => 0,
'msg' => __("error"),
'details' => $check->lastErrorString ?? '',
];
return;
}
$this->response->data = [
'total' => 1,
'ok' => true,
'msg' => __("backup.done"),
'alertText' => __("backup.done"),
];
}
}

View file

@ -1,74 +0,0 @@
<?php declare(strict_types=1);
/*
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.
*/
namespace BackupExtension;
use BackupExtension;
class Download
{
public $filename;
public $lastErrorString = null;
private $token = '';
function __construct(?string $filename)
{
$this->filename = is_null($filename) ? MTTPATH. 'db/backup.xml' : $filename;
}
function checkFileAccess(?string $tokenHash = null): bool
{
if (!file_exists($this->filename)) {
$this->lastErrorString = "Backup file not found";
return false;
}
$this->token = access_token();
if ($this->token == '') {
$this->lastErrorString = "No token provided";
return false;
}
if (!is_null($tokenHash)) {
$a = explode(':', $tokenHash, 2);
$rnd = $a[0] ?? '';
$hash = $a[1] ?? '';
if (!hash_equals(hash_hmac('sha256', $rnd, $this->token), $hash)) {
$this->lastErrorString = "No temp token provided";
return false;
}
}
return true;
}
function downloadUrl()
{
$rnd = randomString();
$hash = $rnd. ':'. hash_hmac('sha256', $rnd, $this->token);
$url = BackupExtension::extApiActionUrl("download", "t=$hash");
return $url;
}
function printFile()
{
header('Content-type: application/xml; charset=utf-8');
header('Content-disposition: attachment; filename=backup.xml');
$fh = fopen($this->filename, "r") or die("Couldn't open file");
if ($fh) {
while (!feof($fh)) {
$buffer = fgets($fh, 4096);
print($buffer);
}
fclose($fh);
}
exit();
}
}

View file

@ -1,255 +0,0 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2023-2025 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
namespace BackupExtension;
use XMLReader;
use DBConnection;
use Exception;
class Restore
{
public $lastErrorString = null;
private $filename;
/** @var XMLReader */
private $reader;
private $tableItem;
function __construct()
{
// xml table => [ db table, xml item ]
$this->tableItem = [
'lists' => ['lists', 'list'],
'tasks' => ['todolist', 'task'],
'tags' => ['tags', 'tag'],
'tag2task' => ['tag2task', 'item'],
'settings' => ['settings', 'item'],
];
}
function isUploaded(): bool
{
if (!isset($_FILES['file']) || !isset($_FILES['file']['name']) || !isset($_FILES['file']['tmp_name'])) {
$this->lastErrorString = "Not uploaded";
return false;
}
$this->filename = $_FILES['file']['tmp_name'];
if (!file_exists($this->filename) || !is_readable($this->filename)) {
$this->lastErrorString = "Can't open file";
return false;
}
return true;
}
function restore(): bool
{
$this->reader = $reader = new XMLReader();
$reader->open($this->filename);
// root element
$reader->next('mttdb');
if ($reader->name != 'mttdb') {
$this->lastErrorString = "Incorrect format: missing 'mttdb'.";
return false;
}
if (!$this->moveNextElement()) {
$this->lastErrorString = "Incorrect format: tables not found.";
return false;
}
$this->beginRestore();
$tables = array_keys($this->tableItem);
// Enumerate tables
do {
if ($reader->nodeType != XMLReader::ELEMENT) {
error_log("Unexpected element '{$reader->name}' of type: {$reader->nodeType}");
break;
}
//error_log("Found table '{$reader->name}'");
$result = null;
if (in_array($reader->name, $tables)) {
$result = $this->readTable($this->tableItem[$reader->name][0], $this->tableItem[$reader->name][1]);
}
else {
continue; // Unexpected table, just skip
}
if (is_null($result)) {
return false; // Incorrect format, error is set, stop
}
} while ($this->moveNextElementSameLevel());
$this->endRestore();
$reader->close();
return true;
}
function moveNextElement(?string $el = null): ?bool
{
while ($this->reader->read()) {
if ($this->reader->nodeType == XMLReader::ELEMENT) {
if (!is_null($el) && $this->reader->name != $el) {
return false;
}
return true;
}
else if ($this->reader->nodeType == XMLReader::END_ELEMENT) {
return null;
}
}
return null;
}
function moveNextElementSameLevel(?string $el = null)
{
return $this->reader->next() && ($this->reader->nodeType == XMLReader::ELEMENT || $this->moveNextElement($el));
}
function readTable(string $table, string $itemName): ?int
{
$autoinc = $this->reader->getAttribute("auto_increment");
$maxId = 0;
$count = 0;
// find first item
$found = $this->moveNextElement($itemName);
if ($found === false) {
$this->lastErrorString = "Incorrect item name {$this->reader->name}, expected '{$itemName}'";
return null; // Error
}
else if (is_null($found)) {
return 0; // No items found
}
do {
$count++;
$id = $this->reader->getAttribute("id");
if (!is_null($id)) {
$maxId = max($maxId, (int)$id);
}
// error_log("# $count: found $itemName with id $id");
$itemXml = $this->reader->readOuterXml();
$xml = simplexml_load_string($itemXml); //SimpleXMLElement
if ($xml === false) {
error_log("Incorrect format of $itemName");
continue;
}
if (!$this->insertToTable($table, $xml)) {
return null; // Error
}
} while ($this->moveNextElementSameLevel($itemName));
// restore table last auto_increment (mysql)
if (!is_null($autoinc)) {
$autoinc = max((int)$autoinc, $maxId);
}
else {
$autoinc = $maxId + 1;
}
if ($autoinc > 1) {
$this->updateAutoinc($table, $autoinc);
}
return $count;
}
private function insertToTable(string $table, \SimpleXMLElement $xml): bool
{
if (!preg_match("/^[\\w]+$/", $table)) {
throw new Exception("Malformed table name: $table");
}
$fields = [];
$values = [];
$attrsXml = $xml->attributes();
if (isset($attrsXml['id']) && $attrsXml['id'] != '') {
$fields[] = 'id';
$values[] = (string)$attrsXml['id'];
}
foreach ($xml->children() as $item) {
$field = $item->getName();
$value = (string)$item;
if (!preg_match("/^[\\w]+$/", $field)) {
throw new Exception("Malformed field name: $field");
}
$attrsXml = $item->attributes();
if (isset($attrsXml['isnull']) && $attrsXml['isnull'] == 'yes') { //$attrsXml['isnull']->__toString()
$value = null;
}
$fields[] = $field;
$values[] = $value;
}
$fieldsStr = implode(',', $fields); // id,name,title ...
$subsStr = implode(',', array_fill(0, count($fields), '?')); // ?,?,? ...
$db = DBConnection::instance();
try {
$db->ex("INSERT INTO {$db->prefix}{$table} ($fieldsStr) VALUES ($subsStr)", $values);
}
catch (Exception $e) {
error_log("Failed query: {$db->lastQuery}");
$this->lastErrorString = "Failed to add data to table '{$db->prefix}$table'. Database error (see query in error log): ". $e->getMessage();
return false;
}
return true;
}
private function updateAutoinc(string $table, int $autoinc)
{
$db = DBConnection::instance();
switch ($db::DBTYPE) {
case DBConnection::DBTYPE_MYSQL:
$db->ex("ALTER TABLE {$db->prefix}$table AUTO_INCREMENT = ". (int)$autoinc);
break;
case DBConnection::DBTYPE_POSTGRES:
$db->ex("ALTER TABLE {$db->prefix}$table ALTER COLUMN id RESTART WITH ". (int)$autoinc);
break;
case DBConnection::DBTYPE_SQLITE:
$db->ex("UPDATE sqlite_sequence SET seq=? WHERE name=?", [$autoinc, $db->prefix. $table]);
break;
default:
break;
}
}
private function beginRestore()
{
$db = DBConnection::instance();
$db->ex("BEGIN");
foreach ($this->tableItem as $a) {
$table = $db->prefix. $a[0];
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
$db->ex("TRUNCATE TABLE $table RESTART IDENTITY");
}
else {
# - we do not use TRUNCATE on mysql due to autocommit
# - sqlite has truncate optimizer while delete all to make it faster
# - no need to reset auto_increment sequence before inserting lower ids
$db->ex("DELETE FROM $table");
}
}
$db->ex("DELETE FROM {$db->prefix}sessions");
}
private function endRestore()
{
$db = DBConnection::instance();
$db->ex("COMMIT");
// vacuum?
}
}

View file

@ -1,6 +0,0 @@
{
"bundleId": "backup",
"name": "Backup",
"version": "1.1",
"description": "Backup"
}

View file

@ -1,17 +0,0 @@
{
"ext.backup.name": "Backup",
"backup.h_make": "Make backup",
"backup.d_make": "Will create backup file in '%s' folder.",
"backup.make": "Make",
"backup.download": "Download",
"backup.done": "Done",
"backup.not_writable": "Backup file is not writable, check permissions for db/backup.xml",
"backup.last_backup": "Last backup: %s",
"backup.h_inconsistency": "Check for inconsistency",
"backup.d_inconsistency": "If you want to restore the backup to another database version or type, it's better to fix inconsistency before making backup.",
"backup.check": "Check",
"backup.repair": "Repair",
"backup.h_restore": "Restore from backup",
"backup.d_restore": "Will delete all records in existing database before restoring.",
"backup.restore": "Restore…"
}

View file

@ -1,17 +0,0 @@
{
"ext.backup.name": "Резервное копирование",
"backup.h_make": "Сделать копию",
"backup.d_make": "Сохранит файл в папке '%s'.",
"backup.make": "Создать",
"backup.download": "Скачать",
"backup.done": "Выполнено",
"backup.not_writable": "Ошибка записи в файл, проверьте права доступа к db/backup.xml",
"backup.last_backup": "Резервная копия: %s",
"backup.h_inconsistency": "Проверка базы данных",
"backup.d_inconsistency": "Если планируете восстановление из резервной копии в другую базу данных, то лучше исправить возможные ошибки перед резервным копированием.",
"backup.check": "Проверить",
"backup.repair": "Исправить",
"backup.h_restore": "Восстановить из резервной копии",
"backup.d_restore": "Удалит все существующие записи во время восстановления.",
"backup.restore": "Восстановить…"
}

View file

@ -1,140 +0,0 @@
<?php declare(strict_types=1);
/*
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.
*/
if (!defined('MTTPATH')) {
die("Unexpected usage.");
}
require_once('class.controller.php');
function mtt_ext_backup_instance(): MTTExtension
{
return new BackupExtension();
}
use BackupExtension\Controller;
class BackupExtension extends MTTExtension implements MTTExtensionSettingsInterface, MTTHttpApiExtender
{
//the same as dir name
const bundleId = 'backup';
// settings domain
const domain = "ext.backup.json";
function init() {
}
// MTTHttpApiExtender
function extendHttpApi(): array
{
return array(
'/makeBackup' => [
'POST' => [ Controller::class , 'postMakeBackup' ],
],
'/download' => [
'POST' => [ Controller::class , 'postDownload' ],
'GET' => [ Controller::class , 'getDownload', true ], // doesn't check auth token
],
'/restore' => [
'POST' => [ Controller::class , 'postRestore' ],
],
'/checkInconsistency' => [
'POST' => [ Controller::class , 'postCheckInconsistency' ],
],
'/repairInconsistency' => [
'POST' => [ Controller::class , 'postRepairInconsistency' ],
],
);
}
function settingsPage(): string
{
$warning = '';
$e = function($s, $arg=null) { return __($s, true, $arg); };
$ext = htmlspecialchars(self::bundleId);
$downloadDisabled = '';
$lastBackup = '';
$filename = MTTPATH. 'db/backup.xml';
if (file_exists($filename)) {
$time = filemtime($filename);
$lastBackup = htmlspecialchars( sprintf($e('backup.last_backup'), formatTime(Config::get('dateformat'). " H:i:s", $time)) );
}
else {
$downloadDisabled = 'disabled';
}
return <<<EOD
$warning
<script>
function onBackupFileChange(el) {
const fd = new FormData();
fd.append('file', el.files[0]);
mytinytodo.extensionSettingsAction(el.dataset.extSettingsAction, el.dataset.ext, fd);
}
</script>
<div class="tr">
<div class="th"> {$e('backup.h_make')}
<div class="descr">{$e('backup.d_make', 'db')}</div>
</div>
<div class="td">
<button type=button data-ext-settings-action="post:makeBackup" data-ext="$ext"> {$e('backup.make')} </button> <br>
<br> $lastBackup &nbsp;
<button type=button data-ext-settings-action="post:download" data-ext="$ext" $downloadDisabled> {$e('backup.download')} </button>
</div>
</div>
<div class="tr">
<div class="th"> {$e('backup.h_inconsistency')}
<div class="descr">{$e('backup.d_inconsistency')}</div>
</div>
<div class="td">
<button type=button data-ext-settings-action="post:checkInconsistency" data-ext="$ext"> {$e('backup.check')} </button> &nbsp;
<button type=button data-ext-settings-action="post:repairInconsistency" data-ext="$ext"> {$e('backup.repair')} </button> <br>
</div>
</div>
<div class="tr">
<div class="th"> {$e('backup.h_restore')}
<div class="descr"> {$e('backup.d_restore')} </div>
</div>
<div class="td">
<label class="mtt-settings-upload-button">
<input type="file" name="file" onchange="return onBackupFileChange(this)" data-ext-settings-action="post:restore" data-ext="$ext">
{$e('backup.restore')}
</label>
</div>
</div>
EOD;
}
function settingsPageType(): int
{
return 1; //no form buttons
}
function saveSettings(array $params, ?string &$outMessage): bool
{
return false;
}
/*
static function preferences(): array
{
return [
'backupFilePath' => MTTPATH. 'db/backup.xml'
];
}
*/
static function backupFilePath()
{
//return self::preferences()['backupFilePath'];
return MTTPATH. 'db/backup.xml';
}
}

View file

Some files were not shown because too many files have changed in this diff Show more