mirror of
https://github.com/maxpozdeev/mytinytodo.git
synced 2026-03-11 08:55:27 +00:00
Compare commits
280 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2ff9a8a7d | ||
|
|
c1467a72b1 | ||
|
|
6b82fc2082 | ||
|
|
768cb550d6 | ||
|
|
1c86b1f606 | ||
|
|
6f5f329c12 | ||
|
|
0d1da0db87 | ||
|
|
e00ae3d1dd | ||
|
|
d0b19e59a6 | ||
|
|
f40b40f030 | ||
|
|
9403d1214a | ||
|
|
1c27df9b32 | ||
|
|
3ce7d21df9 | ||
|
|
25f4818d2e | ||
|
|
9c020ded6d | ||
|
|
71b325f389 | ||
|
|
ce5491ea1c | ||
|
|
9e5ee4f65b | ||
|
|
3286f4e78a | ||
|
|
3acf78b77e | ||
|
|
ff53d7e443 | ||
|
|
b589fcbdb5 | ||
|
|
30e6d8c5ae | ||
|
|
0c451ee8ee | ||
|
|
d34378032f | ||
|
|
302a827eca | ||
|
|
5346491f37 | ||
|
|
42baf175aa | ||
|
|
1c30cf12d1 | ||
|
|
7f84eaa713 | ||
|
|
591f1ca11c | ||
|
|
a4635a1933 | ||
|
|
70d3e08c11 | ||
|
|
0679aa53eb | ||
|
|
58642b9535 | ||
|
|
4c2033aa8c | ||
|
|
4356dd2c62 | ||
|
|
dd4c38438b | ||
|
|
ac1df904ca | ||
|
|
8e926b1208 | ||
|
|
7754719702 | ||
|
|
722a0a401c | ||
|
|
99afe12cba | ||
|
|
846b4222fe | ||
|
|
2841e12a30 | ||
|
|
f7a7294466 | ||
|
|
0a093d4029 | ||
|
|
81a2dc6981 | ||
|
|
c7f935f1c8 | ||
|
|
b6a6bc0ca4 | ||
|
|
8082278af5 | ||
|
|
4548aa17de | ||
|
|
5cbe0d93a7 | ||
|
|
510d04c48f | ||
|
|
360661f002 | ||
|
|
0a56396091 | ||
|
|
a2b472f2d8 | ||
|
|
c11b7f5b34 | ||
|
|
b05f40f77c | ||
|
|
30b08bd87c | ||
|
|
2cbc827bca | ||
|
|
6b5c8221de | ||
|
|
430de1793c | ||
|
|
9bcb0cb7b1 | ||
|
|
96aaba10aa | ||
|
|
49b02cd4f8 | ||
|
|
c611762c9e | ||
|
|
2b5f31dab0 | ||
|
|
57681c8390 | ||
|
|
7136e9f26c | ||
|
|
f952ddc130 | ||
|
|
3ddd6d6ee4 | ||
|
|
b0a104ea01 | ||
|
|
6b97296251 | ||
|
|
b7d9cf08a0 | ||
|
|
f461a16d65 | ||
|
|
a4cc7f4e40 | ||
|
|
7ec5108e22 | ||
|
|
cd38503951 | ||
|
|
22f3dab70d | ||
|
|
d35684f861 | ||
|
|
dde725d06d | ||
|
|
80de4b7a9b | ||
|
|
34e8421435 | ||
|
|
700e58e3dc | ||
|
|
454eef2e07 | ||
|
|
87620921a2 | ||
|
|
3f721aa1cf | ||
|
|
634cdcceee | ||
|
|
29d9da73bb | ||
|
|
33bb209b97 | ||
|
|
081c1a94fe | ||
|
|
9191031140 | ||
|
|
39ba94e303 | ||
|
|
273ceca063 | ||
|
|
39790f01dd | ||
|
|
8e45ceaa3a | ||
|
|
f31b97c4c7 | ||
|
|
7058769c96 | ||
|
|
d3e4286380 | ||
|
|
b721ca9b33 | ||
|
|
b4703b29f8 | ||
|
|
dd27362e96 | ||
|
|
7b84cdabf6 | ||
|
|
9c4087fe38 | ||
|
|
691a99cb28 | ||
|
|
dfe908776e | ||
|
|
17c8cf87d6 | ||
|
|
edfe391e41 | ||
|
|
b7e060e6db | ||
|
|
146d9a19bb | ||
|
|
ae6a4ef2a4 | ||
|
|
9a1970d3b1 | ||
|
|
9489e51574 | ||
|
|
0b743618ea | ||
|
|
6eac80ee1d | ||
|
|
e446880836 | ||
|
|
5b9e142f89 | ||
|
|
1efbe606fd | ||
|
|
ab9f398d7b | ||
|
|
d5dc74bea4 | ||
|
|
6fec3aca77 | ||
|
|
6a816619a4 | ||
|
|
cf40bc72b2 | ||
|
|
30ec81e95d | ||
|
|
0d07356817 | ||
|
|
7ef36ee565 | ||
|
|
abcae0ee97 | ||
|
|
988a3e8a03 | ||
|
|
6958596fb8 | ||
|
|
2e29733c8c | ||
|
|
288db261a9 | ||
|
|
1a75eec5dc | ||
|
|
149b554676 | ||
|
|
4a3ebe285c | ||
|
|
d66a656733 | ||
|
|
0a52e3a5f7 | ||
|
|
628f5ed673 | ||
|
|
28bd570436 | ||
|
|
6b882d13b2 | ||
|
|
68bd687069 | ||
|
|
a3113ed195 | ||
|
|
b395eaa76c | ||
|
|
bd6b3c650e | ||
|
|
6978d6b14b | ||
|
|
c94aa7bcba | ||
|
|
84104a4238 | ||
|
|
d5877f8a66 | ||
|
|
3ce91c9f44 | ||
|
|
7f39f91ecd | ||
|
|
882cc4c43a | ||
|
|
2bf49d69ec | ||
|
|
ad73454f16 | ||
|
|
42e265d802 | ||
|
|
f870bc3cf0 | ||
|
|
476ccc99ff | ||
|
|
b3e640a1f7 | ||
|
|
9fb376d8b2 | ||
|
|
bb0a102ceb | ||
|
|
2cf207fba2 | ||
|
|
f9f97f3b7f | ||
|
|
580853a2b4 | ||
|
|
57ba859c57 | ||
|
|
05e4ed3bfb | ||
|
|
d4cabb61b6 | ||
|
|
696c795a4a | ||
|
|
675d523d0d | ||
|
|
8bcb8eeb9e | ||
|
|
42a6c35d9c | ||
|
|
a0de111dea | ||
|
|
0c2694c746 | ||
|
|
c6ceb562d0 | ||
|
|
bad99a11ab | ||
|
|
e43519e27e | ||
|
|
c8963ed6e3 | ||
|
|
2f14fa0b1c | ||
|
|
6fe3b8a955 | ||
|
|
5b1d00fbe2 | ||
|
|
f48b20c389 | ||
|
|
d2c54f671f | ||
|
|
69e61efd86 | ||
|
|
ce7f88f75d | ||
|
|
8c74e3cbd7 | ||
|
|
5325274a91 | ||
|
|
aeb732b446 | ||
|
|
a6bde46cc3 | ||
|
|
c42143a17d | ||
|
|
8d6935e7a3 | ||
|
|
bcafe5c031 | ||
|
|
7dc1ea5013 | ||
|
|
abedfee4a2 | ||
|
|
ec5e380c51 | ||
|
|
3b76ff34a5 | ||
|
|
ab90cadd3e | ||
|
|
c21c6d6910 | ||
|
|
4b4e47df6a | ||
|
|
3e5252e931 | ||
|
|
f19573221a | ||
|
|
4411e2a8d3 | ||
|
|
b4f360de78 | ||
|
|
778a4e5fa8 | ||
|
|
b38bf18f9d | ||
|
|
956c3cf53b | ||
|
|
85ef3c04af | ||
|
|
173a1b387d | ||
|
|
772305dbed | ||
|
|
cf39163d7b | ||
|
|
01a0cdb50b | ||
|
|
e871e2e02b | ||
|
|
8254493d97 | ||
|
|
a81e069f77 | ||
|
|
6e41e5865e | ||
|
|
28c59c3c5b | ||
|
|
4c01eca4ad | ||
|
|
c8075e0d6b | ||
|
|
7e72dbe14d | ||
|
|
1f0b707193 | ||
|
|
df031728da | ||
|
|
3f6c275298 | ||
|
|
ca3e332a2f | ||
|
|
80f19f936c | ||
|
|
755d766262 | ||
|
|
74e59f1ccf | ||
|
|
176b1e9c99 | ||
|
|
47972dce05 | ||
|
|
ced75b805a | ||
|
|
a41eae1ea7 | ||
|
|
8815569861 | ||
|
|
3a68339cb9 | ||
|
|
8833e7c417 | ||
|
|
8da989ed51 | ||
|
|
46a98f459f | ||
|
|
ff7d399eaa | ||
|
|
afbf174000 | ||
|
|
31d446e8c4 | ||
|
|
d5a57c869b | ||
|
|
d61d556574 | ||
|
|
84024e177c | ||
|
|
3e31e87daf | ||
|
|
0990900da2 | ||
|
|
f0d0adf493 | ||
|
|
cbcba71e32 | ||
|
|
7ef88217cb | ||
|
|
2566881fbf | ||
|
|
47d37a46b5 | ||
|
|
5c174fade3 | ||
|
|
79c06713cb | ||
|
|
24ca9b1a33 | ||
|
|
56fa0fa039 | ||
|
|
d36bab9e16 | ||
|
|
6923bdcda6 | ||
|
|
0029be3291 | ||
|
|
01e688b49e | ||
|
|
27bdebfde4 | ||
|
|
11d2ad4139 | ||
|
|
3daa661adb | ||
|
|
e3f32c5209 | ||
|
|
3af04cbd7e | ||
|
|
7a5a82b617 | ||
|
|
f109b7a762 | ||
|
|
be6738c8ba | ||
|
|
26d42c8e6d | ||
|
|
59d69c2708 | ||
|
|
c833fe4828 | ||
|
|
9dd95d3e5b | ||
|
|
2123d6630d | ||
|
|
4452e8eb1b | ||
|
|
068335c2ad | ||
|
|
f10ee503f8 | ||
|
|
8d8f542908 | ||
|
|
02ac0655b0 | ||
|
|
d71400ea63 | ||
|
|
9e4b8776d7 | ||
|
|
86c02990c1 | ||
|
|
e41429c174 | ||
|
|
c7b2d26a95 | ||
|
|
7625cd975c | ||
|
|
40f9d33a93 | ||
|
|
37e6f30a57 | ||
|
|
a91aaa09af |
160 changed files with 7477 additions and 2917 deletions
|
|
@ -19,7 +19,7 @@ indent_size = 4
|
|||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
[*.{css,yml,html,svg}]
|
||||
[*.{css,yml,html,svg,xml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,7 +1,10 @@
|
|||
.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/
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -1,3 +1,22 @@
|
|||
myTinyTodo
|
||||
# myTinyTodo
|
||||
|
||||
Your tiny todo list
|
||||
|
||||
Original website - http://www.mytinytodo.net/
|
||||
|
||||
### System requirements
|
||||
- PHP 7.2 or greater
|
||||
- PHP extensions:
|
||||
- mbstring
|
||||
- pdo_sqlite, intl (SQLite version)
|
||||
- pdo_mysql or mysqli (MySQL version)
|
||||
- pdo_pgsql (PostgreSQL version)
|
||||
- One of databases:
|
||||
- MySQL 5.7 or greater / MariaDB 10.2 or greater
|
||||
- PostgreSQL 10 or greater
|
||||
- SQLite (system library)
|
||||
|
||||
Supported browsers: Chrome 49, Safari 10, Firefox 53.
|
||||
|
||||
Internet Explorer and Opera with Presto engine are not supported.
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ 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
|
||||
|
||||
|
|
@ -58,6 +61,7 @@ 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) {
|
||||
|
|
@ -68,7 +72,7 @@ if (is_dir('src/ext')) {
|
|||
}
|
||||
chdir('../ext2');
|
||||
if ($extCount > 0) {
|
||||
`tar -czf ../ext/extensions.tar.gz *`; #OS dep.!!!
|
||||
`tar --no-xattrs -czf ../ext/extensions.tar.gz *`; #OS dep.!!!
|
||||
}
|
||||
chdir('../..');
|
||||
deleteTreeIfDir('src/ext2');
|
||||
|
|
@ -78,7 +82,7 @@ if (is_dir('src/ext')) {
|
|||
|
||||
rename('src', 'mytinytodo') or die("Cant rename 'src'\n");
|
||||
|
||||
`tar -czf mytinytodo.tar.gz mytinytodo`; #OS dep.!!!
|
||||
`tar --no-xattrs -czf mytinytodo.tar.gz mytinytodo`; #OS dep.!!!
|
||||
if (!file_exists('mytinytodo.tar.gz')) {
|
||||
die("Failed to pack files (no output tar.gz file)\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
{
|
||||
"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": {
|
||||
"erusev/parsedown": "^1.7"
|
||||
"php": ">=7.2",
|
||||
"ext-mbstring": "*",
|
||||
"erusev/parsedown": "1.7.x-dev#f7285e7",
|
||||
"symfony/polyfill-intl-normalizer": "^1.31"
|
||||
},
|
||||
"require-dev": {
|
||||
"league/commonmark": "^2.3"
|
||||
"league/commonmark": "^2.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
259
composer.lock
generated
259
composer.lock
generated
|
|
@ -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": "18661f8c2155d955e7962271812bbed0",
|
||||
"content-hash": "c27d4b183be86ec7d95203f1a84b86e2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "erusev/parsedown",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.x-dev",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/erusev/parsedown.git",
|
||||
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
|
||||
"reference": "f7285e7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
|
||||
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown/zipball/f7285e7",
|
||||
"reference": "f7285e7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35"
|
||||
"phpunit/phpunit": "^4.8|^5.7|^6.5|^7.5|^8.5|^9.6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -54,22 +54,103 @@
|
|||
"issues": "https://github.com/erusev/parsedown/issues",
|
||||
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
|
||||
},
|
||||
"time": "2019-12-30T22:54:17+00:00"
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"version": "v3.0.1",
|
||||
"version": "v3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
|
||||
"reference": "0992cc19268b259a39e86f296da5f0677841f42c"
|
||||
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
|
||||
"reference": "0992cc19268b259a39e86f296da5f0677841f42c",
|
||||
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f",
|
||||
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -80,7 +161,7 @@
|
|||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
|
||||
"scrutinizer/ocular": "1.6.0",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"vimeo/psalm": "^3.14"
|
||||
"vimeo/psalm": "^4.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
|
@ -129,22 +210,22 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
|
||||
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
|
||||
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3"
|
||||
},
|
||||
"time": "2021-08-13T13:06:58+00:00"
|
||||
"time": "2024-07-08T12:26:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.3.4",
|
||||
"version": "2.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "155ec1c95626b16fda0889cf15904d24890a60d5"
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/155ec1c95626b16fda0889cf15904d24890a60d5",
|
||||
"reference": "155ec1c95626b16fda0889cf15904d24890a60d5",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -157,22 +238,23 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"cebe/markdown": "^1.0",
|
||||
"commonmark/cmark": "0.30.0",
|
||||
"commonmark/commonmark.js": "0.30.0",
|
||||
"commonmark/cmark": "0.31.1",
|
||||
"commonmark/commonmark.js": "0.31.1",
|
||||
"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",
|
||||
"michelf/php-markdown": "^1.4 || ^2.0",
|
||||
"nyholm/psr7": "^1.5",
|
||||
"phpstan/phpstan": "^0.12.88 || ^1.0.0",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
|
||||
|
|
@ -180,7 +262,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.4-dev"
|
||||
"dev-main": "2.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -237,20 +319,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-07-17T16:25:47+00:00"
|
||||
"time": "2025-05-05T12:20:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
"version": "v1.1.1",
|
||||
"version": "v1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/config.git",
|
||||
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e"
|
||||
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
|
||||
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
|
||||
"url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
|
||||
"reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -259,7 +341,7 @@
|
|||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.90",
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"unleashedtech/php-coding-standard": "^3.1",
|
||||
|
|
@ -319,35 +401,35 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-14T12:15:32+00:00"
|
||||
"time": "2022-12-11T20:36:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
"version": "v1.2.2",
|
||||
"version": "v1.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/schema.git",
|
||||
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
|
||||
"reference": "da801d52f0354f70a638673c4a0f04e16529431d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
|
||||
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
|
||||
"reference": "da801d52f0354f70a638673c4a0f04e16529431d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
|
||||
"php": ">=7.1 <8.2"
|
||||
"nette/utils": "^4.0",
|
||||
"php": "8.1 - 8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.3 || ^2.4",
|
||||
"phpstan/phpstan-nette": "^0.12",
|
||||
"tracy/tracy": "^2.7"
|
||||
"nette/tester": "^2.5.2",
|
||||
"phpstan/phpstan-nette": "^1.0",
|
||||
"tracy/tracy": "^2.8"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2-dev"
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -379,34 +461,36 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/schema/issues",
|
||||
"source": "https://github.com/nette/schema/tree/v1.2.2"
|
||||
"source": "https://github.com/nette/schema/tree/v1.3.2"
|
||||
},
|
||||
"time": "2021-10-15T11:40:02+00:00"
|
||||
"time": "2024-10-06T23:10:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v3.2.7",
|
||||
"version": "v4.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99"
|
||||
"reference": "ce708655043c7050eb050df361c5e313cf708309"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99",
|
||||
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309",
|
||||
"reference": "ce708655043c7050eb050df361c5e313cf708309",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2 <8.2"
|
||||
"php": "8.0 - 8.4"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/di": "<3.0.6"
|
||||
"nette/finder": "<3",
|
||||
"nette/schema": "<1.2.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "~2.0",
|
||||
"jetbrains/phpstorm-attributes": "dev-master",
|
||||
"nette/tester": "^2.5",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"tracy/tracy": "^2.3"
|
||||
"tracy/tracy": "^2.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "to use Image",
|
||||
|
|
@ -414,13 +498,12 @@
|
|||
"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-xml": "to use Strings::length() etc. when mbstring is not available"
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.2-dev"
|
||||
"dev-master": "4.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -464,9 +547,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v3.2.7"
|
||||
"source": "https://github.com/nette/utils/tree/v4.0.6"
|
||||
},
|
||||
"time": "2022-01-24T11:29:14+00:00"
|
||||
"time": "2025-03-30T21:06:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/event-dispatcher",
|
||||
|
|
@ -520,16 +603,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.1.1",
|
||||
"version": "v3.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
|
||||
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
|
||||
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
|
||||
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -537,12 +620,12 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.1-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
"url": "https://github.com/symfony/contracts",
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -567,7 +650,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.1.1"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -583,33 +666,30 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-25T11:15:52+00:00"
|
||||
"time": "2024-09-25T14:20:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.26.0",
|
||||
"version": "v1.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
||||
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.26-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -650,7 +730,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -666,15 +746,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-05-10T07:21:04+00:00"
|
||||
"time": "2025-01-02T08:10:11+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"erusev/parsedown": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.3.0"
|
||||
"platform": {
|
||||
"php": ">=7.2",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,9 @@
|
|||
#dir="$( dirname -- "$( readlink -f -- "$0"; )"; )"
|
||||
dir="$PWD"
|
||||
|
||||
docker run -it --rm -v "$dir:/app" composer $@
|
||||
app=$(which podman)
|
||||
if [ -z $app ]; then
|
||||
app="docker"
|
||||
fi
|
||||
|
||||
$app run -it --rm -v "$dir:/app" composer $@
|
||||
|
|
|
|||
1
docker/dev/.gitignore
vendored
1
docker/dev/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
*/db_data
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
FROM mariadb:10.5
|
||||
|
||||
COPY my.cnf /etc/mysql/conf.d/
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[mysqld]
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
character-set-server = utf8mb4
|
||||
innodb_log_file_size=10M
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
FROM mariadb:10.8
|
||||
|
||||
COPY my.cnf /etc/mysql/conf.d/
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[mysqld]
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
character-set-server = utf8mb4
|
||||
innodb_log_file_size=10M
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
FROM nginx:latest
|
||||
|
||||
COPY default.conf /etc/nginx/conf.d/
|
||||
|
||||
VOLUME /var/www/html
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
# 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php72-apache-mariadb105
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php72-apache
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
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/
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php74-apache
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
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/
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php74-nginx
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php80-apache-mariadb105
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php80-apache
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
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/
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php81-apache-mariadb108
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php81-apache
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
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/
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php81-nginx
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
PLATFORM_NAME=mtt-dev-php82-apache
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
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/
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
; 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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
;; 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
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,12 +1,29 @@
|
|||
# For Apache
|
||||
# For REST API in Apache
|
||||
#<IfModule mod_rewrite.c>
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^api/(.*)$ api.php/$1 [L,QSA]
|
||||
#</IfModule>
|
||||
#<Limit GET POST PUT DELETE>
|
||||
# Allow from all
|
||||
#</Limit>
|
||||
|
||||
# For Nginx set something like this:
|
||||
|
||||
# In Nginx set something like this:
|
||||
|
||||
# Deny access to some files and folders
|
||||
#location ~ ^/(db|includes)/ {
|
||||
# deny all;
|
||||
#}
|
||||
#location ~ /\.ht {
|
||||
# deny all;
|
||||
#}
|
||||
#location ~* ^/ext/.*\.(json|md)$ {
|
||||
# deny all;
|
||||
#}
|
||||
|
||||
# Optional
|
||||
# location /api/ {
|
||||
# rewrite ^/api/(.*) /api.php/$1 last;
|
||||
# }
|
||||
|
|
|
|||
|
|
@ -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-2022 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
Copyright: 2009-2011,2019-2025 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
License: GPL version 2 or any later (see LICENSE file)
|
||||
|
||||
|
||||
|
|
@ -34,9 +34,11 @@ Url: http://jqueryui.com/
|
|||
Copyright: Copyright jQuery Foundation and other contributors
|
||||
License: MIT license. Compatible with GNU GPL.
|
||||
|
||||
jQuery UI Touch Punch
|
||||
-----------------------
|
||||
Url: https://github.com/furf/jquery-ui-touch-punch
|
||||
jQuery UI Touch Punch (fork by RWAP Software)
|
||||
---------------------------------------------
|
||||
Url: https://github.com/RWAP/jquery-ui-touch-punch
|
||||
based on original touchpunch
|
||||
Original: https://github.com/furf/jquery-ui-touch-punch
|
||||
Copyright: Copyright 2011–2014, Dave Furfero
|
||||
License: Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
|
||||
|
|
@ -46,7 +48,12 @@ 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 files content/themes/default/images/COPYRIGHT for details.
|
||||
See file content/theme/images/COPYRIGHT for details.
|
||||
|
|
|
|||
43
src/api.php
43
src/api.php
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/*
|
||||
This file is a part of myTinyTodo.
|
||||
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
(C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
|
||||
*/
|
||||
|
||||
|
|
@ -32,6 +32,7 @@ $endpoints = array(
|
|||
'GET' => [ ListsController::class , 'getId' ],
|
||||
'PUT' => [ ListsController::class , 'putId' ],
|
||||
'DELETE' => [ ListsController::class , 'deleteId' ],
|
||||
'POST' => [ ListsController::class , 'putId' ], //compatibility
|
||||
],
|
||||
'/tasks' => [
|
||||
'GET' => [ TasksController::class , 'get' ],
|
||||
|
|
@ -41,10 +42,14 @@ $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' ],
|
||||
],
|
||||
|
|
@ -57,6 +62,7 @@ $endpoints = array(
|
|||
'/ext-settings/(.+)' => [
|
||||
'GET' => [ ExtSettingsController::class , 'get' ],
|
||||
'PUT' => [ ExtSettingsController::class , 'put' ],
|
||||
'POST' => [ ExtSettingsController::class , 'put' ], //compatibility
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -67,7 +73,7 @@ foreach (MTTExtensionLoader::loadedExtensions() as $instance) {
|
|||
foreach ($newRoutes as $endpoint => $methods) {
|
||||
$endpoint = '/ext/'. $instance::bundleId. $endpoint;
|
||||
foreach ($methods as $k => &$v) {
|
||||
$v[2] = true; // Mark extension method
|
||||
$v[3] = true; // Mark extension method
|
||||
}
|
||||
$endpoints[$endpoint] = $methods;
|
||||
}
|
||||
|
|
@ -85,17 +91,22 @@ 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];
|
||||
$isExt = $classDescr[2] ?? false;
|
||||
if ($isExt) {
|
||||
checkWriteAccess();
|
||||
$isExtMethod = $classDescr[3] ?? false;
|
||||
if ($isExtMethod) {
|
||||
if (false == ($classDescr[2] ?? false)) { //TODO: describe $classDescr[2]
|
||||
// By default all extension methods require write access rights
|
||||
checkWriteAccess();
|
||||
}
|
||||
}
|
||||
$param = null;
|
||||
if (count($m) >= 2) {
|
||||
|
|
@ -104,7 +115,8 @@ 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);
|
||||
|
|
@ -114,15 +126,21 @@ 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) {
|
||||
$response->htmlContent("Unknown command", 404);
|
||||
if (MTT_DEBUG) {
|
||||
$response->htmlContent("Unknown endpoint: {$req->method} {$req->path}", 404)
|
||||
->exit();
|
||||
}
|
||||
$response->htmlContent("Unknown endpoint", 404);
|
||||
}
|
||||
$response->exit();
|
||||
|
||||
|
|
@ -133,7 +151,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 || $errno==E_STRICT) {
|
||||
elseif ($errno==E_WARNING || $errno==E_CORE_WARNING || $errno==E_COMPILE_WARNING || $errno==E_USER_WARNING) {
|
||||
if (error_reporting() & $errno) $error = 'Warning'; else return;
|
||||
}
|
||||
elseif ($errno==E_NOTICE || $errno==E_USER_NOTICE || $errno==E_DEPRECATED || $errno==E_USER_DEPRECATED) {
|
||||
|
|
@ -182,6 +200,7 @@ 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) );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
Select the database type: sqlite or mysql or postgres.
|
||||
*/
|
||||
|
||||
#define("MTT_DB_TYPE", "sqlite");
|
||||
|
|
|
|||
2
src/content/jquery/jquery-3.6.1.min.js
vendored
2
src/content/jquery/jquery-3.6.1.min.js
vendored
File diff suppressed because one or more lines are too long
6
src/content/jquery/jquery-ui-1.13.2.min.js
vendored
6
src/content/jquery/jquery-ui-1.13.2.min.js
vendored
File diff suppressed because one or more lines are too long
6
src/content/js/jquery-ui.min.js
vendored
Normal file
6
src/content/js/jquery-ui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
src/content/js/jquery.min.js
vendored
Normal file
2
src/content/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* jQuery UI Touch Punch 1.0.8 as modified by RWAP Software
|
||||
* jQuery UI Touch Punch 1.1.5 as modified by RWAP Software
|
||||
* based on original touchpunch v0.2.3 which has not been updated since 2014
|
||||
*
|
||||
* Updates by RWAP Software to take account of various suggested changes on the original code issues
|
||||
|
|
@ -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,31 +31,30 @@
|
|||
}(function ($) {
|
||||
|
||||
// Detect touch support - Windows Surface devices and other touch devices
|
||||
$.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
|
||||
$.mspointer = window.navigator.msPointerEnabled;
|
||||
$.touch = ( 'ontouchstart' in document
|
||||
|| 'ontouchstart' in window
|
||||
|| window.TouchEvent
|
||||
|| (window.DocumentTouch && document instanceof DocumentTouch)
|
||||
|| navigator.maxTouchPoints > 0
|
||||
|| navigator.msMaxTouchPoints > 0
|
||||
);
|
||||
|
||||
// Ignore browsers without touch or mouse support
|
||||
if ((!$.support.touch && !$.support.mspointer) || !$.ui.mouse) {
|
||||
return;
|
||||
if ((!$.touch && !$.mspointer) || !$.ui.mouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
var mouseProto = $.ui.mouse.prototype,
|
||||
let mouseProto = $.ui.mouse.prototype,
|
||||
_mouseInit = mouseProto._mouseInit,
|
||||
_mouseDestroy = mouseProto._mouseDestroy,
|
||||
touchHandled;
|
||||
touchHandled, lastClickTime = 0;
|
||||
|
||||
var delay = 300,
|
||||
let delay = 300,
|
||||
delayTimer,
|
||||
delayEvent,
|
||||
delayStarted = false,
|
||||
delayFinished = false,
|
||||
lastClickTimeStamp = 0,
|
||||
lastClickCoord;
|
||||
|
||||
|
||||
|
|
@ -82,32 +81,26 @@
|
|||
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();
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
let touch = event.originalEvent.changedTouches[0],
|
||||
simulatedEvent = new MouseEvent(simulatedType, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view:window,
|
||||
screenX:touch.screenX,
|
||||
screenY:touch.screenY,
|
||||
clientX:touch.clientX,
|
||||
clientY:touch.clientY
|
||||
});
|
||||
|
||||
// Dispatch the simulated event to the target element
|
||||
event.target.dispatchEvent(simulatedEvent);
|
||||
|
|
@ -125,7 +118,7 @@
|
|||
|
||||
function fireMouseDown () {
|
||||
|
||||
var self = this;
|
||||
const self = this;
|
||||
|
||||
delayFinished = true;
|
||||
|
||||
|
|
@ -152,7 +145,7 @@
|
|||
*/
|
||||
mouseProto._touchStart = function (event) {
|
||||
|
||||
var self = this;
|
||||
let self = this;
|
||||
|
||||
// Interaction time
|
||||
this._startedMove = event.timeStamp;
|
||||
|
|
@ -224,22 +217,23 @@
|
|||
// 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
|
||||
var timeMoving = event.timeStamp - this._startedMove;
|
||||
let timeMoving = event.timeStamp - this._startedMove;
|
||||
if (!this._touchMoved || timeMoving < 500) {
|
||||
|
||||
// Simulate the click event
|
||||
simulateMouseEvent(event, 'click');
|
||||
|
||||
if (lastClickTimeStamp != 0 && event.timeStamp - lastClickTimeStamp < 500 &&
|
||||
// Simulate the dblclick event if last click was not far away from the previous one
|
||||
if ( event.timeStamp - lastClickTime < 400 &&
|
||||
Math.abs(lastClickCoord.x - this._startPos.x) < 10 && Math.abs(lastClickCoord.y - this._startPos.y) < 10) {
|
||||
// Simulate the dblclick event
|
||||
simulateMouseEvent(event, 'dblclick');
|
||||
}
|
||||
lastClickTimeStamp = event.timeStamp
|
||||
// Simulate the click event
|
||||
else
|
||||
simulateMouseEvent(event, 'click');
|
||||
|
||||
lastClickTime = event.timeStamp
|
||||
lastClickCoord = this._startPos;
|
||||
}
|
||||
else {
|
||||
var endPos = getTouchCoords(event);
|
||||
let endPos = getTouchCoords(event);
|
||||
if ((Math.abs(endPos.x - this._startPos.x) < 10) && (Math.abs(endPos.y - this._startPos.y) < 10)) {
|
||||
// If the touch interaction did not move, it should trigger a click
|
||||
if (!this._touchMoved || event.originalEvent.changedTouches[0].touchType === 'stylus') {
|
||||
|
|
@ -256,6 +250,10 @@
|
|||
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
|
||||
|
|
@ -264,18 +262,22 @@
|
|||
*/
|
||||
mouseProto._mouseInit = function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
let self = this;
|
||||
|
||||
// Microsoft Surface Support = remove original touch Action
|
||||
if ($.support.mspointer) {
|
||||
if ($.mspointer) {
|
||||
self.element[0].style.msTouchAction = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
_touchStartBound = mouseProto._touchStart.bind(self);
|
||||
_touchMoveBound = mouseProto._touchMove.bind(self);
|
||||
_touchEndBound = mouseProto._touchEnd.bind(self);
|
||||
|
||||
// Delegate the touch handlers to the widget's element
|
||||
self.element.on({
|
||||
touchstart: $.proxy(self, '_touchStart'),
|
||||
touchmove: $.proxy(self, '_touchMove'),
|
||||
touchend: $.proxy(self, '_touchEnd')
|
||||
touchstart: _touchStartBound,
|
||||
touchmove: _touchMoveBound,
|
||||
touchend: _touchEndBound
|
||||
});
|
||||
|
||||
// Call the original $.ui.mouse init method
|
||||
|
|
@ -287,13 +289,13 @@
|
|||
*/
|
||||
mouseProto._mouseDestroy = function () {
|
||||
|
||||
var self = this;
|
||||
let self = this;
|
||||
|
||||
// Delegate the touch handlers to the widget's element
|
||||
self.element.off({
|
||||
touchstart: $.proxy(self, '_touchStart'),
|
||||
touchmove: $.proxy(self, '_touchMove'),
|
||||
touchend: $.proxy(self, '_touchEnd')
|
||||
touchstart: _touchStartBound,
|
||||
touchmove: _touchMoveBound,
|
||||
touchend: _touchEndBound
|
||||
});
|
||||
|
||||
// Call the original $.ui.mouse destroy method
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,56 +1,72 @@
|
|||
/*
|
||||
This file is a part of myTinyTodo.
|
||||
(C) Copyright 2010,2020,2022 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
(C) Copyright 2010,2020-2025 Max Pozdeev <maxpozdeev@gmail.com>
|
||||
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
"use strict";
|
||||
|
||||
var mtt;
|
||||
|
||||
function MytinytodoAjaxApi(amtt)
|
||||
/**
|
||||
* @class
|
||||
*/
|
||||
function MytinytodoAjaxApi(props)
|
||||
{
|
||||
mtt = amtt;
|
||||
if (typeof mytinytodo !== 'object') {
|
||||
throw "mytinytodo global object is not found!";
|
||||
}
|
||||
this.useREST = true;
|
||||
|
||||
if (props.hasOwnProperty('useREST')) {
|
||||
this.useREST = !!props.useREST;
|
||||
}
|
||||
}
|
||||
|
||||
window.MytinytodoAjaxApi = MytinytodoAjaxApi;
|
||||
MytinytodoAjaxApi.prototype = {
|
||||
|
||||
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)
|
||||
/**
|
||||
* required method
|
||||
* @param {string} action
|
||||
* @param {Object.<string, any>} [params]
|
||||
* @param {ApiDriverCallback} [callback]
|
||||
*
|
||||
* @callback ApiDriverCallback
|
||||
* @param {object} json
|
||||
*/
|
||||
request(action, params, callback) {
|
||||
if (typeof this[action] !== 'function') {
|
||||
throw "Unknown ApiDriver action: " + action;
|
||||
}
|
||||
this[action](params, function(json){
|
||||
if (json.denied) {
|
||||
mytinytodo.errorDenied();
|
||||
}
|
||||
if (callback) {
|
||||
callback.call(mytinytodo, json)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
loadTasks: function(params, callback)
|
||||
{
|
||||
var q = '';
|
||||
loadTasks(params, callback) {
|
||||
let q = '';
|
||||
if (params.search && params.search != '') q += '&s=' + encodeURIComponent(params.search);
|
||||
if (params.tag && params.tag != '') q += '&t=' + encodeURIComponent(params.tag);
|
||||
if (params.setCompl && params.setCompl != 0) q += '&setCompl=1';
|
||||
if (params.saveSort && params.saveSort != 0) q += '&saveSort=1';
|
||||
|
||||
$.getJSON(mtt.apiUrl + 'tasks?list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q, callback);
|
||||
$.getJSON(mytinytodo.apiUrl + 'tasks' + (mytinytodo.apiUrl.indexOf('?') > -1 ? '&' : '?') +
|
||||
'list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q,
|
||||
callback);
|
||||
},
|
||||
|
||||
|
||||
newTask: function(params, callback)
|
||||
{
|
||||
newTask(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks',
|
||||
url: mytinytodo.apiUrl + 'tasks',
|
||||
method: 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'simple',
|
||||
action: 'newSimple',
|
||||
list: params.list,
|
||||
title: params.title,
|
||||
tag: params.tag,
|
||||
|
|
@ -61,14 +77,13 @@ MytinytodoAjaxApi.prototype =
|
|||
},
|
||||
|
||||
|
||||
fullNewTask: function(params, callback)
|
||||
{
|
||||
fullNewTask(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks',
|
||||
url: mytinytodo.apiUrl + 'tasks',
|
||||
method: 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'full',
|
||||
action: 'newFull',
|
||||
list: params.list,
|
||||
title: params.title,
|
||||
note: params.note,
|
||||
|
|
@ -83,11 +98,10 @@ MytinytodoAjaxApi.prototype =
|
|||
},
|
||||
|
||||
|
||||
editTask: function(params, callback)
|
||||
{
|
||||
editTask(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'edit',
|
||||
|
|
@ -103,11 +117,10 @@ MytinytodoAjaxApi.prototype =
|
|||
},
|
||||
|
||||
|
||||
editNote: function(params, callback)
|
||||
{
|
||||
editNote(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'note',
|
||||
|
|
@ -119,11 +132,10 @@ MytinytodoAjaxApi.prototype =
|
|||
},
|
||||
|
||||
|
||||
completeTask: function(params, callback)
|
||||
{
|
||||
completeTask(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'complete',
|
||||
|
|
@ -135,22 +147,24 @@ MytinytodoAjaxApi.prototype =
|
|||
},
|
||||
|
||||
|
||||
deleteTask: function(params, callback)
|
||||
{
|
||||
deleteTask(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: 'DELETE',
|
||||
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: this.useREST ? 'DELETE' : 'POST',
|
||||
contentType : 'application/json', // contentType and data are required if method is POST
|
||||
data: JSON.stringify({
|
||||
action: 'delete',
|
||||
}),
|
||||
success: callback,
|
||||
dataType: 'json'
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
setTaskPriority: function(params, callback)
|
||||
{
|
||||
setTaskPriority(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'priority',
|
||||
|
|
@ -161,11 +175,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
changeOrder: function(params, callback)
|
||||
{
|
||||
changeOrder(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks',
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'tasks',
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'order',
|
||||
|
|
@ -176,21 +189,18 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
suggestTags: function(params, callback)
|
||||
{
|
||||
$.getJSON(mtt.apiUrl + 'suggestTags', {list:params.list, q:params.q}, callback);
|
||||
suggestTags(params, callback) {
|
||||
$.getJSON(mytinytodo.apiUrl + 'suggestTags', {list:params.list, q:params.q}, callback);
|
||||
},
|
||||
|
||||
tagCloud: function(params, callback)
|
||||
{
|
||||
$.getJSON(mtt.apiUrl + 'tagCloud/' + encodeURIComponent(params.list), callback);
|
||||
tagCloud(params, callback) {
|
||||
$.getJSON(mytinytodo.apiUrl + 'tagCloud/' + encodeURIComponent(params.list), callback);
|
||||
},
|
||||
|
||||
moveTask: function(params, callback)
|
||||
{
|
||||
moveTask(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'tasks/' + encodeURIComponent(params.id),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'move',
|
||||
|
|
@ -202,10 +212,9 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
parseTaskStr: function(params, callback)
|
||||
{
|
||||
parseTaskStr(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'tasks/parseTitle',
|
||||
url: mytinytodo.apiUrl + 'tasks/parseTitle',
|
||||
method: 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
|
|
@ -218,20 +227,42 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
|
||||
// Lists
|
||||
loadLists: function(params, callback)
|
||||
{
|
||||
$.getJSON(mtt.apiUrl + 'lists', callback);
|
||||
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)
|
||||
});
|
||||
},
|
||||
|
||||
addList: function(params, callback)
|
||||
{
|
||||
|
||||
// Lists
|
||||
loadLists(params, callback) {
|
||||
$.getJSON(mytinytodo.apiUrl + 'lists', callback);
|
||||
},
|
||||
|
||||
addList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists',
|
||||
url: mytinytodo.apiUrl + 'lists',
|
||||
method: 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'new',
|
||||
name: params.name,
|
||||
}),
|
||||
success: callback,
|
||||
|
|
@ -240,21 +271,23 @@ MytinytodoAjaxApi.prototype =
|
|||
|
||||
},
|
||||
|
||||
deleteList: function(params, callback)
|
||||
{
|
||||
deleteList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'DELETE',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'DELETE' : 'POST',
|
||||
contentType : 'application/json', // contentType and data are required if method is POST
|
||||
data: JSON.stringify({
|
||||
action: 'delete',
|
||||
}),
|
||||
success: callback,
|
||||
dataType: 'json'
|
||||
});
|
||||
},
|
||||
|
||||
renameList: function(params, callback)
|
||||
{
|
||||
renameList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'rename',
|
||||
|
|
@ -265,11 +298,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
setSort: function(params, callback)
|
||||
{
|
||||
setSort(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'sort',
|
||||
|
|
@ -281,11 +313,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
publishList: function(params, callback)
|
||||
{
|
||||
publishList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'publish',
|
||||
|
|
@ -296,11 +327,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
enableFeedKey: function(params, callback)
|
||||
{
|
||||
enableFeedKey(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'enableFeedKey',
|
||||
|
|
@ -311,11 +341,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
setShowNotesInList: function(params, callback)
|
||||
{
|
||||
setShowNotesInList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'showNotes',
|
||||
|
|
@ -326,11 +355,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
setHideList: function(params, callback)
|
||||
{
|
||||
setHideList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'hide',
|
||||
|
|
@ -341,11 +369,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
changeListOrder: function(params, callback)
|
||||
{
|
||||
changeListOrder(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists',
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists',
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'order',
|
||||
|
|
@ -356,11 +383,10 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
clearCompletedInList: function(params, callback)
|
||||
{
|
||||
clearCompletedInList(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: 'PUT',
|
||||
url: mytinytodo.apiUrl + 'lists/' + encodeURIComponent(params.list),
|
||||
method: this.useREST ? 'PUT' : 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
action: 'clearCompleted',
|
||||
|
|
@ -372,10 +398,9 @@ MytinytodoAjaxApi.prototype =
|
|||
|
||||
/* Auth */
|
||||
|
||||
login: function(params, callback)
|
||||
{
|
||||
login(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'login',
|
||||
url: mytinytodo.apiUrl + 'login',
|
||||
method: 'POST',
|
||||
contentType : 'application/json',
|
||||
data: JSON.stringify({
|
||||
|
|
@ -386,10 +411,9 @@ MytinytodoAjaxApi.prototype =
|
|||
});
|
||||
},
|
||||
|
||||
logout: function(params, callback)
|
||||
{
|
||||
logout(params, callback) {
|
||||
$.ajax({
|
||||
url: mtt.apiUrl + 'logout',
|
||||
url: mytinytodo.apiUrl + 'logout',
|
||||
method: 'POST',
|
||||
success: callback,
|
||||
dataType: 'json'
|
||||
|
|
@ -397,5 +421,3 @@ MytinytodoAjaxApi.prototype =
|
|||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
/* Dark mode */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* prefers-color-scheme media query is used in html link tag */
|
||||
:root {
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-bg: #151515;
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
--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;
|
||||
|
|
@ -46,13 +47,13 @@
|
|||
--color-btn-tab-hover-bg: #5a5a5a;
|
||||
|
||||
/* Menu */
|
||||
--color-menu: #2b2b2b;
|
||||
--color-menu: #252525;
|
||||
--color-menu-border: #555;
|
||||
--color-menu-hover: #5a8df0;
|
||||
--color-menu-text: #eaeaea;
|
||||
--color-menu-text-hover: #ebf0f8;
|
||||
--color-menu-text-disabled: #696969;
|
||||
--color-popup-shadow: 1px 2px 6px 1px rgba(0,0,0,1);
|
||||
--color-popup-shadow: 1px 2px 6px 1px rgba(85,85,85,0.1);
|
||||
|
||||
/* Tasklist */
|
||||
--color-tasklist-row: var(--color-bg);
|
||||
|
|
@ -62,8 +63,11 @@
|
|||
--color-tasklist-tag: var(--color-tab-text);
|
||||
--color-tasklist-note-link: #999;
|
||||
--color-tasklist-link-hover: var(--color-link);
|
||||
--color-taglist-text-hover: var(--color-bg);
|
||||
--color-taglist-text-hover-bg: var(--color-text-default);
|
||||
--color-tasklisk-hover-shadow: rgba(255,255,255,0.4);
|
||||
--color-taglist-tag: var(--color-text-reduced);
|
||||
--color-taglist-tag-bg: #444;
|
||||
--color-taglist-tag-hover: var(--color-taglist-tag);
|
||||
--color-taglist-tag-hover-bg: var(--color-taglist-tag-bg);
|
||||
--color-tasklist-listname: #bbb;
|
||||
--color-tasklist-listname-bg: #333;
|
||||
|
||||
|
|
@ -90,5 +94,6 @@
|
|||
--color-placeholder: #444;
|
||||
--color-placeholder-border: #555;
|
||||
|
||||
}
|
||||
--svg-select: var(--svg-select-dark);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" >
|
||||
<!-- <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 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" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 318 B |
3
src/content/theme/images/select-dark.svg
Normal file
3
src/content/theme/images/select-dark.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<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>
|
||||
|
After Width: | Height: | Size: 148 B |
3
src/content/theme/images/select.svg
Normal file
3
src/content/theme/images/select.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<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>
|
||||
|
After Width: | Height: | Size: 148 B |
62
src/content/theme/images/svg2base64.php
Normal file
62
src/content/theme/images/svg2base64.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?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();
|
||||
}
|
||||
|
|
@ -27,11 +27,11 @@
|
|||
.markdown-note dl,
|
||||
.markdown-note table,
|
||||
.markdown-note pre {
|
||||
margin: 10px 0;
|
||||
margin: 12px 0;
|
||||
}
|
||||
.markdown-note ul,
|
||||
.markdown-note ol {
|
||||
padding-left: 20px;
|
||||
padding-left: 2.3rem;
|
||||
}
|
||||
.markdown-note ul.no-list,
|
||||
.markdown-note ol.no-list {
|
||||
|
|
@ -79,23 +79,20 @@
|
|||
.markdown-note code, /* inline code */
|
||||
.markdown-note tt {
|
||||
font-size: 13px; /* if main font is 14px */
|
||||
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
|
||||
margin: 0 2px;
|
||||
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
|
||||
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: nowrap;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.markdown-note pre {
|
||||
font-size: 13px;
|
||||
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
|
||||
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
|
||||
line-height: 1.2rem;
|
||||
padding: 6px 10px;
|
||||
padding: 10px;
|
||||
background-color: var(--color-md-bg-highlighted);
|
||||
border: 1px solid var(--color-md-border);
|
||||
overflow: auto;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
@media print {
|
||||
|
||||
html { height:0; }
|
||||
body { height:0; min-height:0; margin:0; }
|
||||
html,body { height:auto; }
|
||||
h2 { display: none; }
|
||||
h3 { border-bottom:2px solid #777777; }
|
||||
#toolbar { display: none; }
|
||||
|
|
@ -10,8 +9,9 @@
|
|||
.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 a span { text-align:left; padding:0; max-width:none; font-size:1.3rem; color:#000; }
|
||||
.mtt-tab.mtt-tab-selected .title { text-align:left; padding:0; margin:0; max-width:none; font-size:1.3rem; color:#000; }
|
||||
|
||||
.mtt-tabs-new-button { display:none; }
|
||||
#tabs_buttons { display:none; }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
--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;
|
||||
|
|
@ -66,8 +67,11 @@
|
|||
--color-tasklist-tag: var(--color-tab-text);
|
||||
--color-tasklist-note-link: #777;
|
||||
--color-tasklist-link-hover: var(--color-link); /* #af0000 */
|
||||
--color-taglist-text-hover: var(--color-bg);
|
||||
--color-taglist-text-hover-bg: var(--color-btn-tab-hover-bg);
|
||||
--color-tasklisk-hover-shadow: rgba(0,0,0,0.3);
|
||||
--color-taglist-tag: var(--color-text-reduced);
|
||||
--color-taglist-tag-bg: #e0e0e0;
|
||||
--color-taglist-tag-hover: var(--color-taglist-tag);
|
||||
--color-taglist-tag-hover-bg: var(--color-taglist-tag-bg);
|
||||
--color-tasklist-listname: #555;
|
||||
--color-tasklist-listname-bg: #eee;
|
||||
|
||||
|
|
@ -97,31 +101,59 @@
|
|||
--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:scroll; font-size:14px; /* =1rem */ }
|
||||
html {
|
||||
height:100%;
|
||||
/*overflow-y:hidden; /* for modal overlay to disable scrolling, but breaks position:absolute */
|
||||
font-size:14px; /* =1rem */
|
||||
}
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text-default);
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
overflow-y: scroll; /* always show scrollbar */
|
||||
}
|
||||
body, td, th, input, textarea, select, button {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", /*Roboto,*/ Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",/*Roboto,*/ Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
font-size: 1rem;
|
||||
}
|
||||
#mtt {
|
||||
flex: 1 0 auto;
|
||||
width:100%;
|
||||
max-width:1000px;
|
||||
max-width:1100px;
|
||||
padding:8px;
|
||||
margin-bottom:8px;
|
||||
margin-bottom: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
|
@ -141,14 +173,24 @@ form { display: inline; }
|
|||
/* preload images */
|
||||
body::after {
|
||||
position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
|
||||
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);
|
||||
content:url(images/logo-loading.gif);
|
||||
}
|
||||
|
||||
h3.page-title { margin:0; border-bottom:2px solid var(--color-content-delimiter); margin-bottom:10px; padding:0.6rem 0; font-size:1.1rem; }
|
||||
h3.page-title {
|
||||
margin:0;
|
||||
border-bottom:2px solid var(--color-content-delimiter);
|
||||
margin-bottom:1rem;
|
||||
padding:0.6rem 0;
|
||||
font-size:1.1rem;
|
||||
}
|
||||
a { color: var(--color-link); cursor:pointer; text-decoration:underline; }
|
||||
|
||||
.topblock { display:flex; align-items:flex-start; margin-bottom:1rem; }
|
||||
#footer { flex-shrink:0; width:100%; max-width:1000px; }
|
||||
#footer {
|
||||
flex-shrink:0;
|
||||
width:100%;
|
||||
max-width:1100px;
|
||||
}
|
||||
#footer_content {
|
||||
background-color: var(--color-footer);
|
||||
border-top: 1px solid var(--color-content-delimiter);
|
||||
|
|
@ -186,11 +228,16 @@ 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 .15s ease-in-out;
|
||||
transition: box-shadow .1s ease-in-out;
|
||||
}
|
||||
select.form-input {
|
||||
padding:2px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none; -moz-appearance: none;
|
||||
background: var(--color-input-bg) var(--svg-select) no-repeat top 4px right 5px;
|
||||
background-size: 1rem 1rem;
|
||||
padding-right: calc(1rem + 10px);
|
||||
}
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
|
|
@ -259,19 +306,45 @@ 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;
|
||||
border-bottom:none;
|
||||
border-top-right-radius:8px;
|
||||
transition:background-color 0.1s ease-in;
|
||||
max-width:230px;
|
||||
}
|
||||
.mtt-tab a {
|
||||
margin:0; text-decoration:none; white-space:nowrap;
|
||||
color: var(--color-tab-text);
|
||||
display:inline-block; outline:none;
|
||||
box-sizing:border-box; height:2.2rem; padding:1px 0.3rem 0 0.3rem; display:flex; align-items:center;
|
||||
box-sizing:border-box;
|
||||
height:2.2rem;
|
||||
padding:1px 0.3rem 0 0.3rem;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
}
|
||||
.mtt-tab a:active {
|
||||
outline:0;
|
||||
text-decoration:none;
|
||||
}
|
||||
.mtt-tab .title-block {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
min-width: 75px;
|
||||
}
|
||||
.mtt-tab .title {
|
||||
display:inline-block;
|
||||
text-align:center;
|
||||
cursor:pointer;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left:0.3rem;
|
||||
margin-right:0.3rem;
|
||||
flex-grow:1;
|
||||
}
|
||||
.mtt-tab 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 {
|
||||
|
|
@ -297,6 +370,19 @@ 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;
|
||||
|
|
@ -357,7 +443,11 @@ select.form-input {
|
|||
height:7px; width:7px;
|
||||
}
|
||||
|
||||
.newtask-n-search-container { display:flex; justify-content:flex-end; }
|
||||
.newtask-n-search-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Quick Task Add */
|
||||
.taskbox-c { flex-grow:1; display:flex; align-items:center; }
|
||||
|
|
@ -367,7 +457,10 @@ 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 { display:none; }
|
||||
#mtt.show-all-tasks .taskbox-c,
|
||||
#mtt.readonly .taskbox-c {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.mtt-taskbox-icon {
|
||||
width:16px; height:16px;
|
||||
|
|
@ -451,7 +544,7 @@ select.form-input {
|
|||
margin-left:3px;
|
||||
display:inline-block;
|
||||
width:1em; height:1em; /* em! */
|
||||
mask: url(images/closetag.svg) no-repeat; -webkit-mask: url(images/closetag.svg) no-repeat;
|
||||
mask: var(--svg-closetag) no-repeat; -webkit-mask: var(--svg-closetag) no-repeat;
|
||||
background-color: var(--color-btn-reduced);
|
||||
transition: background-color 0.1s ease-in;
|
||||
}
|
||||
|
|
@ -459,19 +552,36 @@ select.form-input {
|
|||
background-color: var(--color-btn-reduced-hover);
|
||||
}
|
||||
#mtt-tag-toolbar-close span {
|
||||
mask: url(images/closetag.svg) no-repeat; -webkit-mask: url(images/closetag.svg) no-repeat;
|
||||
mask: var(--svg-closetag) no-repeat; -webkit-mask: var(--svg-closetag) 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: 2px;
|
||||
mask: url(images/note-toggle.svg) no-repeat; -webkit-mask: url(images/note-toggle.svg) no-repeat;
|
||||
margin-right: 4px;
|
||||
mask: var(--svg-note-toggle) no-repeat; -webkit-mask: var(--svg-note-toggle) no-repeat;
|
||||
background-color: var(--color-btn-reduced);
|
||||
transition: transform .1s linear, background-color .1s ease-in;
|
||||
}
|
||||
|
|
@ -496,9 +606,14 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
|
|||
background-color: var(--color-tasklist-row); /* ?? */
|
||||
position:relative; /* for z-index */
|
||||
}
|
||||
#tasklist li.task-row:hover,
|
||||
#tasklist li.task-row.menu-active {
|
||||
/*#mtt:not(.touch-device) #tasklist li.task-row:hover,*/
|
||||
#mtt:not(.touch-device) #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;
|
||||
|
|
@ -510,6 +625,9 @@ 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);
|
||||
}
|
||||
|
|
@ -517,9 +635,12 @@ 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;
|
||||
|
|
@ -528,7 +649,10 @@ 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 {
|
||||
|
|
@ -537,13 +661,27 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
|
|||
margin-top: 0.2rem;
|
||||
display: none;
|
||||
}
|
||||
.task-id {
|
||||
/*display: none;*/
|
||||
.task-id::after {
|
||||
content: ' · '; /* '·' */
|
||||
}
|
||||
#mtt.show-date .task-date {
|
||||
display: block;
|
||||
}
|
||||
#mtt.show-date.date-inline .task-date {
|
||||
display: inline-block; /* for RTL */
|
||||
margin:0;
|
||||
margin-left:3px;
|
||||
}
|
||||
#mtt.show-inline-date .task-date { display: block; }
|
||||
.task-through { overflow:hidden; flex-grow:1; }
|
||||
.task-title a { color: var(--color-text-default); }
|
||||
.task-title a:hover { color: var(--color-tasklist-link-hover); }
|
||||
#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);
|
||||
}
|
||||
#mtt.readonly #tasklist li.task-row .task-actions { display:none; }
|
||||
.task-listname {
|
||||
background-color: var(--color-tasklist-listname-bg);
|
||||
|
|
@ -551,8 +689,20 @@ li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
|
|||
padding: 0px 3px;
|
||||
margin: 0px 2px;
|
||||
}
|
||||
.task-tags { padding:0px 2px; }
|
||||
.task-tags .tag { font-size:0.9rem; font-weight:bold; color: var(--color-tasklist-tag); text-decoration:underline; }
|
||||
.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;
|
||||
}
|
||||
.duedate { color:var(--color-duedate-default); padding:0px; padding-left:1px; margin-left:5px; white-space:nowrap; }
|
||||
.duedate:before {
|
||||
content:'\2192\20';
|
||||
|
|
@ -634,8 +784,6 @@ 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) }
|
||||
|
||||
|
|
@ -662,32 +810,47 @@ li.task-row.task-completed .task-note-block .task-note { text-decoration:line-th
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.form-row { margin-top:0.6rem; }
|
||||
.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 .h {
|
||||
font-weight:bold;
|
||||
color: var(--color-text-reduced);
|
||||
}
|
||||
.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;
|
||||
.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;
|
||||
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 {
|
||||
#taskedit_id, #taskviewer_id {
|
||||
color: var(--color-text-reduced2);
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
|
|
@ -695,15 +858,61 @@ li.task-row.task-completed .task-note-block .task-note { text-decoration:line-th
|
|||
#taskedit_info { font-size:1rem; font-weight:normal; color:var(--color-text-reduced2); margin-bottom:6px; }
|
||||
#taskedit_info > div { margin-top:6px; }
|
||||
|
||||
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);
|
||||
#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;
|
||||
}
|
||||
|
||||
/* autocomplete */
|
||||
|
|
@ -730,6 +939,7 @@ h3.page-title a.mtt-back-button {
|
|||
#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;
|
||||
|
|
@ -751,19 +961,6 @@ h3.page-title a.mtt-back-button {
|
|||
#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;
|
||||
|
|
@ -781,8 +978,11 @@ h3.page-title a.mtt-back-button {
|
|||
#tagcloudAllLists:disabled + label {
|
||||
opacity: 0.6;
|
||||
}
|
||||
#tagcloudSearch {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
#tagcloudcancel span {
|
||||
mask: url(images/closetag.svg) no-repeat; -webkit-mask: url(images/closetag.svg) no-repeat;
|
||||
mask: var(--svg-closetag) no-repeat; -webkit-mask: var(--svg-closetag) no-repeat;
|
||||
background-color: var(--color-btn-reduced);
|
||||
}
|
||||
#tagcloudcontent {
|
||||
|
|
@ -790,6 +990,41 @@ h3.page-title a.mtt-back-button {
|
|||
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;
|
||||
|
|
@ -835,10 +1070,26 @@ h3.page-title a.mtt-back-button {
|
|||
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: url(images/arr-right.svg) no-repeat; -webkit-mask: url(images/arr-right.svg) no-repeat;
|
||||
mask: var(--svg-arr-right) no-repeat; -webkit-mask: var(--svg-arr-right) 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;
|
||||
|
|
@ -903,15 +1154,24 @@ h3.page-title a.mtt-back-button {
|
|||
.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: url(images/checkmark.svg) no-repeat; -webkit-mask: url(images/checkmark.svg) no-repeat;
|
||||
mask: var(--svg-checkmark) no-repeat; -webkit-mask: var(--svg-checkmark) 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: url(images/arr-right.svg) no-repeat; -webkit-mask: url(images/arr-right.svg) no-repeat;
|
||||
mask: var(--svg-arr-right) no-repeat; -webkit-mask: var(--svg-arr-right) 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; }
|
||||
|
|
@ -921,11 +1181,11 @@ li.mtt-item-hidden { display:none; }
|
|||
|
||||
/**/
|
||||
|
||||
#btnRssFeed .menu-icon { background:url(images/rss.svg) no-repeat; }
|
||||
#btnRssFeed.mtt-item-disabled .menu-icon { background:url(images/rss-disabled.svg) no-repeat; }
|
||||
#btnRssFeed .menu-icon { background:var(--svg-rss) no-repeat; }
|
||||
#btnRssFeed.mtt-item-disabled .menu-icon { background:var(--svg-rss-disabled) no-repeat; }
|
||||
|
||||
#task, #search {
|
||||
transition: box-shadow .15s ease-in-out;
|
||||
transition: box-shadow .1s ease-in-out;
|
||||
}
|
||||
#task:focus, #search:focus {
|
||||
outline: none;
|
||||
|
|
@ -933,18 +1193,60 @@ li.mtt-item-hidden { display:none; }
|
|||
box-shadow: 0 0 0 2px var(--color-border-focus-shadow);
|
||||
}
|
||||
|
||||
.mtt-settings-table { width:100%; }
|
||||
.monospace, pre {
|
||||
font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Liberation Mono",monospace;
|
||||
}
|
||||
|
||||
.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 .th,
|
||||
.mtt-settings-table .td {
|
||||
.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 {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.mtt-settings-table .th { width:30%; font-weight: bold; }
|
||||
.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 .descr {
|
||||
display: block;
|
||||
font-size:0.8rem;
|
||||
|
|
@ -952,21 +1254,40 @@ li.mtt-item-hidden { display:none; }
|
|||
color:var(--color-text-reduced);
|
||||
padding-top: 4px;
|
||||
}
|
||||
.mtt-settings-table .td {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
.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 input,
|
||||
.mtt-settings-table select,
|
||||
.mtt-settings-table textarea {
|
||||
.mtt-settings-table button,
|
||||
.mtt-settings-table textarea,
|
||||
.mtt-settings-upload-button {
|
||||
padding: 3px;
|
||||
border: 1px solid var(--color-border-default);
|
||||
border-radius: 2px;
|
||||
/*background-color: var(--color-bg);*/
|
||||
color: var(--color-text-default)
|
||||
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);
|
||||
}
|
||||
.mtt-settings-table select { padding:2px; }
|
||||
.mtt-settings-table input:focus,
|
||||
.mtt-settings-table select:focus {
|
||||
outline:none;
|
||||
|
|
@ -976,15 +1297,41 @@ 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: absolute;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#modal .modal-box {
|
||||
min-width: 350px;
|
||||
max-width: 1000px; /* same as #mtt */
|
||||
max-width: 1100px; /* same as #mtt */
|
||||
border: 1px solid var(--color-menu-border);
|
||||
background: var(--color-bg);
|
||||
box-shadow: var(--color-popup-shadow);
|
||||
|
|
@ -1014,10 +1361,12 @@ li.mtt-item-hidden { display:none; }
|
|||
font-size: 16px;
|
||||
-webkit-text-size-adjust: 100%; /* Dont increase font-size in horizontal orientation on ios */
|
||||
}
|
||||
#page_taskedit .form-row textarea.in500 { font-size: 14px; }
|
||||
textarea.form-input.inmax { font-size: 14px; }
|
||||
}
|
||||
|
||||
/* narrow screens */
|
||||
|
||||
|
||||
/* ========== narrow screens =========*/
|
||||
@media only screen and (max-width: 600px) {
|
||||
|
||||
#mtt { padding: 15px 8px 0px; }
|
||||
|
|
@ -1033,7 +1382,16 @@ 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; }
|
||||
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;
|
||||
}
|
||||
|
||||
.mtt-tabs-new-button { padding-left:0.4rem; padding-right:0.4rem; } /* make thiсker */
|
||||
|
||||
|
|
@ -1050,6 +1408,10 @@ 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;
|
||||
|
|
@ -1074,51 +1436,96 @@ 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; }
|
||||
#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 }
|
||||
|
||||
#page_taskedit { max-width:100%; border:none; position:static; padding:0; }
|
||||
#page_taskedit .form-table { width:100%; }
|
||||
#page_taskedit .form-row textarea { height: 70px; }
|
||||
#page_taskedit .form-row textarea { height: 150px; }
|
||||
|
||||
#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; }
|
||||
.mtt-settings-table .in350 { min-width:50px; width:100%; }
|
||||
.mtt-notes-showhide { display:none; }
|
||||
|
||||
.mtt-menu-container li { padding-top:0.3rem; padding-bottom:0.3rem; }
|
||||
.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-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 .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); }
|
||||
.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;
|
||||
}
|
||||
|
||||
.form-bottom-buttons > * {
|
||||
padding: 7px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
#modal {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
} /* end of @media min 600px */
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
body { direction:rtl; }
|
||||
h2 { padding-left:10px; padding-right:0px; }
|
||||
.topblock h2 {
|
||||
background-position: right;
|
||||
padding-left: 10px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
#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; }
|
||||
#newtask_adv { margin-left:0; margin-right:0.5rem; transform:rotateY(180deg); }
|
||||
.mtt-searchbox-icon.mtt-icon-search { right:4px; left:auto; }
|
||||
.mtt-searchbox-icon.mtt-icon-cancelsearch { left:4px; right:auto; }
|
||||
|
||||
|
|
@ -27,10 +37,9 @@ h2 { padding-left:10px; padding-right:0px; }
|
|||
.duedate:before { content:'\20\2190\20'; }
|
||||
|
||||
#tagcloud { box-shadow:-1px 2px 5px rgba(0,0,0,0.5); }
|
||||
#tagcloudcancel { float:left; }
|
||||
|
||||
#taskedit-date { float:left; }
|
||||
#page_taskedit .form-row-short { float:right; margin-left:12px; margin-right:0; }
|
||||
h3.page-title a.mtt-back-button { transform:rotate(180deg); }
|
||||
.form-row-short { 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); }
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
// Rename it to config.php before using in docker.
|
||||
|
||||
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'));
|
||||
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') ?: "");
|
||||
}
|
||||
else if (getenv('MTT_DB_TYPE') == 'sqlite') {
|
||||
define("MTT_DB_TYPE", "sqlite");
|
||||
|
|
@ -17,3 +17,7 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,33 +24,25 @@ if (!$listData) {
|
|||
die("No list found.");
|
||||
}
|
||||
|
||||
$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";
|
||||
$data = DBCore::default()->getTasksByListId($listId, '', (int)$listData['sorting']);
|
||||
|
||||
$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;
|
||||
if (_get('format') == 'ical') {
|
||||
printICal($listData, $data);
|
||||
}
|
||||
else {
|
||||
printCSV($listData, $data);
|
||||
}
|
||||
|
||||
$format = _get('format');
|
||||
|
||||
if($format == 'ical') printICal($listData, $data);
|
||||
else printCSV($listData, $data);
|
||||
|
||||
|
||||
function printCSV($listData, $data)
|
||||
function printCSV(array $listData, array $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";
|
||||
|
|
@ -60,7 +52,7 @@ function printCSV($listData, $data)
|
|||
print $s;
|
||||
}
|
||||
|
||||
function escape_csv($v)
|
||||
function escape_csv(string $v)
|
||||
{
|
||||
//escape formulas
|
||||
$nf = '';
|
||||
|
|
@ -71,7 +63,7 @@ function escape_csv($v)
|
|||
return '"'. $nf. str_replace('"', '""', $v). '"';
|
||||
}
|
||||
|
||||
function printICal($listData, $data)
|
||||
function printICal(array $listData, array $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".
|
||||
|
|
|
|||
5
src/ext/.htaccess
Normal file
5
src/ext/.htaccess
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<FilesMatch "\.(json|md|MD)$">
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
1
src/ext/CustomCSS/.htaccess
Normal file
1
src/ext/CustomCSS/.htaccess
Normal file
|
|
@ -0,0 +1 @@
|
|||
deny from all
|
||||
6
src/ext/CustomCSS/extension.json
Normal file
6
src/ext/CustomCSS/extension.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"bundleId": "CustomCSS",
|
||||
"name": "Custom CSS",
|
||||
"version": "1.0.1",
|
||||
"description": "Add you own css rules"
|
||||
}
|
||||
7
src/ext/CustomCSS/lang/en.json
Normal file
7
src/ext/CustomCSS/lang/en.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
7
src/ext/CustomCSS/lang/pl.json
Normal file
7
src/ext/CustomCSS/lang/pl.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
7
src/ext/CustomCSS/lang/ru.json
Normal file
7
src/ext/CustomCSS/lang/ru.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"ext.CustomCSS.name": "Дополнительные стили",
|
||||
"customcss.h_css": "CSS",
|
||||
"customcss.d_css": "Добавляйте собственные стили CSS",
|
||||
"customcss.not_writable": "Ошибка записи в файл, проверьте права доступа к custom.css",
|
||||
"customcss.saved": "Сохранено"
|
||||
}
|
||||
93
src/ext/CustomCSS/loader.php
Normal file
93
src/ext/CustomCSS/loader.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
1
src/ext/_examples/CustomSmartSyntax/.htaccess
Normal file
1
src/ext/_examples/CustomSmartSyntax/.htaccess
Normal file
|
|
@ -0,0 +1 @@
|
|||
deny from all
|
||||
6
src/ext/_examples/CustomSmartSyntax/extension.json
Normal file
6
src/ext/_examples/CustomSmartSyntax/extension.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"bundleId": "CustomSmartSyntax",
|
||||
"name": "Custom Smart Syntax",
|
||||
"version": "1.0",
|
||||
"description": "Prototype to write you own smart syntax parser"
|
||||
}
|
||||
41
src/ext/_examples/CustomSmartSyntax/loader.php
Normal file
41
src/ext/_examples/CustomSmartSyntax/loader.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
1
src/ext/backup/.htaccess
Normal file
1
src/ext/backup/.htaccess
Normal file
|
|
@ -0,0 +1 @@
|
|||
deny from all
|
||||
203
src/ext/backup/class.backup.php
Normal file
203
src/ext/backup/class.backup.php
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
<?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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
126
src/ext/backup/class.check.php
Normal file
126
src/ext/backup/class.check.php
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
156
src/ext/backup/class.controller.php
Normal file
156
src/ext/backup/class.controller.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?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"),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
74
src/ext/backup/class.download.php
Normal file
74
src/ext/backup/class.download.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?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();
|
||||
}
|
||||
|
||||
}
|
||||
255
src/ext/backup/class.restore.php
Normal file
255
src/ext/backup/class.restore.php
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
<?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?
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
6
src/ext/backup/extension.json
Normal file
6
src/ext/backup/extension.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"bundleId": "backup",
|
||||
"name": "Backup",
|
||||
"version": "1.1",
|
||||
"description": "Backup"
|
||||
}
|
||||
17
src/ext/backup/lang/en.json
Normal file
17
src/ext/backup/lang/en.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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…"
|
||||
}
|
||||
17
src/ext/backup/lang/ru.json
Normal file
17
src/ext/backup/lang/ru.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"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": "Восстановить…"
|
||||
}
|
||||
140
src/ext/backup/loader.php
Normal file
140
src/ext/backup/loader.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?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
|
||||
<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>
|
||||
<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';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
0
src/ext/index.html
Normal file
0
src/ext/index.html
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue