Compare commits

...

280 commits

Author SHA1 Message Date
maxpozdeev
c2ff9a8a7d + Add "Any tag" filter in tag cloud (GH-127) 2026-02-12 22:00:16 +03:00
maxpozdeev
c1467a72b1 Revert "+ can use mid: URL scheme in markdown links (GH-126)"
This reverts commit 1c86b1f606.
2026-02-12 19:35:21 +03:00
maxpozdeev
6b82fc2082 * rewrite titleMarkup() to be able to be redefined by extension 2026-02-12 19:34:47 +03:00
maxpozdeev
768cb550d6 - fix for e00ae3d 2026-01-29 16:03:50 +03:00
maxpozdeev
1c86b1f606 + can use mid: URL scheme in markdown links (GH-126) 2026-01-03 18:27:46 +03:00
maxpozdeev
6f5f329c12 - fix: unable to use 'j M Y ' short date format in duedate picker (closes GH-128) 2026-01-03 18:25:56 +03:00
maxpozdeev
0d1da0db87 - fix: incompatibility with postgesql while export to ical/csv/rss (closes GH-125) 2026-01-03 15:09:06 +03:00
maxpozdeev
e00ae3d1dd * avoid sqlite deprecation notices in PHP 8.5 2026-01-03 14:24:04 +03:00
Max Pozdeev
d0b19e59a6
* Update ZH-CN and ZH-TW Chinese translations (thanks to @wangyouworld)
Simplified and Traditional Chinese Translation based on v1.8.2
2025-07-23 14:38:15 +03:00
wangyouworld
f40b40f030 Simplified and Traditional Chinese Translation based on v1.8.2(file with 4-space indentation) 2025-07-23 09:13:34 +08:00
maxpozdeev
9403d1214a add script for emergency password reset 2025-06-25 15:31:24 +03:00
maxpozdeev
1c27df9b32 set version to 1.8.3 2025-05-29 18:07:03 +03:00
maxpozdeev
3ce7d21df9 update percentage of translations done 2025-05-29 17:51:22 +03:00
maxpozdeev
25f4818d2e - fix: incompatibility with SessionUpdateTimestampHandlerInterface in PHP 7.x 2025-05-29 17:44:58 +03:00
maxpozdeev
9c020ded6d - fix: escaped special html symbols in browser page title (closes GH-113) 2025-05-29 10:18:34 +03:00
maxpozdeev
71b325f389 update Dutch (nl) translation (thanks to J.C.Barnhoorn) 2025-05-17 15:21:37 +03:00
maxpozdeev
ce5491ea1c do not use string_agg in sqlite due to not implemented in old lib versions (like php docker images) 2025-05-13 11:14:40 +03:00
maxpozdeev
9e5ee4f65b update symfony/polyfill-intl-normalizer to v1.32 2025-05-13 09:55:23 +03:00
maxpozdeev
3286f4e78a update percentage of translations done 2025-05-13 09:29:40 +03:00
maxpozdeev
3acf78b77e * Remove restriction on having some characters (<, >, &, ', ") in list names (closes GH-108) 2025-05-12 15:52:24 +03:00
maxpozdeev
ff53d7e443 - Bugfix: no list name in all tasks list after task edit (closes GH-110) 2025-05-12 15:36:20 +03:00
maxpozdeev
b589fcbdb5 use MTTExtensionLoaderException while loading extensions 2025-05-12 15:22:34 +03:00
maxpozdeev
30e6d8c5ae + can use own markdown converter with MTTMarkdown::setInstanceClass 2025-05-12 15:03:08 +03:00
maxpozdeev
0c451ee8ee add get_filever() 2025-04-08 00:03:30 +03:00
maxpozdeev
d34378032f add theme_scripts action and link_css_dark id 2025-04-08 00:02:43 +03:00
maxpozdeev
302a827eca * fix background color of inputs in mobile settings 2025-04-05 20:35:19 +03:00
maxpozdeev
5346491f37 change array_is_list polyfill 2025-04-04 01:27:09 +03:00
maxpozdeev
42baf175aa fix: extensions were not loaded due to non-list storage 2025-04-04 01:26:38 +03:00
maxpozdeev
1c30cf12d1 update COPYRIGHT file 2025-03-11 12:26:17 +03:00
maxpozdeev
7f84eaa713 add Estonian translation (thanks to Rivo Zängov) 2025-03-03 21:29:10 +03:00
maxpozdeev
591f1ca11c fix prev commit 2025-03-03 20:38:51 +03:00
maxpozdeev
a4635a1933 more checks of arrays 2025-03-03 15:09:14 +03:00
maxpozdeev
70d3e08c11 * do not write session data on every request 2025-02-27 21:34:32 +03:00
maxpozdeev
0679aa53eb remove unused tag 2025-02-26 18:53:31 +03:00
maxpozdeev
58642b9535 - Fix missing task's list name in All Tasks list after task was moved to another list (GH-107) 2025-02-25 22:09:23 +03:00
maxpozdeev
4c2033aa8c - Fix incorrect list name in task viewer opened from All tasks list (GH-107) 2025-02-25 21:46:11 +03:00
maxpozdeev
4356dd2c62 - dont show edit task button in taskviewer in readonly mode 2025-02-23 23:26:25 +03:00
maxpozdeev
dd4c38438b * better handling of auto_increment in backup extension 2025-02-23 22:12:08 +03:00
maxpozdeev
ac1df904ca update .htaccess 2025-02-21 22:18:46 +03:00
maxpozdeev
8e926b1208 update the percentage of translation completeness 2025-02-21 19:09:08 +03:00
maxpozdeev
7754719702 + Can filter tags in tag cloud (GH-105) 2025-02-21 12:20:27 +03:00
maxpozdeev
722a0a401c fix typo 2025-02-21 10:42:54 +03:00
maxpozdeev
99afe12cba Use randomString in setup for csrf token 2025-02-20 18:29:04 +03:00
maxpozdeev
846b4222fe - Fix: unable to upgrade old setups with db v1.4 with password protection 2025-02-20 17:28:47 +03:00
maxpozdeev
2841e12a30 * Batch of small UI changes 2025-02-19 22:38:55 +03:00
maxpozdeev
f7a7294466 Update jQuery UI to v1.14.1 2025-02-19 18:42:29 +03:00
maxpozdeev
0a093d4029 Merge branch 'master' of git@github.com:maxpozdeev/mytinytodo.git 2025-02-18 22:38:19 +03:00
Max Pozdeev
81a2dc6981
Add sys reqs to README.md 2025-02-18 22:37:37 +03:00
maxpozdeev
c7f935f1c8 Update jQuery UI Touch Punch to 1.1.5 with my patches 2025-02-17 22:17:25 +03:00
maxpozdeev
b6a6bc0ca4 add comments to avoid notices in ide 2025-02-17 12:31:10 +03:00
maxpozdeev
8082278af5 + Can filter tasks by having any tag or not having tags (closes GH-64) 2025-02-17 12:30:30 +03:00
maxpozdeev
4548aa17de Set version to 1.8.2 2025-02-16 18:40:42 +03:00
maxpozdeev
5cbe0d93a7 Added info about percent of translations completion 2025-02-16 18:28:56 +03:00
Max Pozdeev
510d04c48f
Merge pull request #100 from AlainRnet/patch-1
Update fr translation (thanks @AlainRnet)
2025-02-16 18:26:01 +03:00
maxpozdeev
360661f002 Restore authors history in zh-cn translation 2025-02-16 14:41:49 +03:00
maxpozdeev
0a56396091 Fix tabs in ru,en 2025-02-16 14:27:29 +03:00
maxpozdeev
a2b472f2d8 + Show counters of new tasks in the Select List menu 2025-02-16 00:27:50 +03:00
maxpozdeev
c11b7f5b34 + Can show a counter of new tasks as a favicon (GH-5, GH-25) 2025-02-15 21:50:09 +03:00
maxpozdeev
b05f40f77c Move js call of tasks/newCounter to js api file 2025-02-15 20:37:54 +03:00
maxpozdeev
30b08bd87c update .gitignore 2025-02-15 13:34:24 +03:00
maxpozdeev
2cbc827bca remove dev docker files 2025-02-15 13:34:03 +03:00
maxpozdeev
6b5c8221de add xml to .editorconfig 2025-02-15 13:18:24 +03:00
maxpozdeev
430de1793c little refactoring in updater extension 2025-02-09 01:08:22 +03:00
maxpozdeev
9bcb0cb7b1 Update Parsedown 1.7.4 to remove deprecation notice in PHP 8.4 2025-02-08 23:44:25 +03:00
maxpozdeev
96aaba10aa do not use E_STRICT 2025-02-05 23:49:13 +03:00
maxpozdeev
49b02cd4f8 - Fix empty page while print in firefox (closes GH-103) 2025-02-01 23:56:24 +03:00
maxpozdeev
c611762c9e - Fix margin of title in print style 2025-02-01 23:08:14 +03:00
maxpozdeev
2b5f31dab0 Fix authors and duplicates in French translation 2025-02-01 21:36:56 +03:00
Max Pozdeev
57681c8390
Merge pull request #83 from wangyouworld/master
Update zh-cn translation (thanks @wangyouworld GH-83)
2025-01-30 20:25:19 +03:00
maxpozdeev
7136e9f26c Fix indentation for easy diff 2025-01-30 20:22:01 +03:00
maxpozdeev
f952ddc130 add types to url_dir() definition 2025-01-21 16:16:27 +03:00
maxpozdeev
3ddd6d6ee4 can log sql queries to file 2025-01-20 00:35:22 +03:00
maxpozdeev
b0a104ea01 more strictness 2025-01-19 23:43:23 +03:00
maxpozdeev
6b97296251 update libs 2025-01-19 22:35:43 +03:00
maxpozdeev
b7d9cf08a0 can use podman for composer 2025-01-19 22:30:50 +03:00
Alain Rihs
f461a16d65
Update fr.json
from v1.6.8 to v1.8.1
2024-12-10 11:07:57 +01:00
maxpozdeev
a4cc7f4e40 - fix deprecation notice in PHP 8.4 2024-08-14 23:01:19 +03:00
Max Pozdeev
7ec5108e22
Merge pull request #89 from dennykorsukewitz/pr-language-de
Updated de language file, thanks to Denny Korsukéwit @dennykorsukewitz (GH-89)
2024-08-14 15:19:11 +03:00
maxpozdeev
cd38503951 * Add german localization for Notifications and Updater extensions (thanks to Daniel Schlapa @Dtrieb) (GH-92 GH-93) 2024-08-11 12:36:53 +03:00
maxpozdeev
22f3dab70d normalize ext loader func to be able to have '-' in ext directory name 2024-08-10 20:01:04 +03:00
Denny Korsukéwitz
d35684f861 Updated de language file via myTinyTodo translation maker v2.1. 2024-04-03 22:45:24 +02:00
maxpozdeev
dde725d06d add Y38 notice 2024-02-05 00:02:38 +03:00
maxpozdeev
80de4b7a9b + add setting for counter of new tasks (disabled by default) (2 strings added for translation) 2024-01-23 13:35:19 +03:00
maxpozdeev
34e8421435 UI groups in notifications extension 2024-01-19 17:00:32 +03:00
maxpozdeev
700e58e3dc can set MTT_DEBUG constant in config file 2024-01-08 13:39:56 +03:00
maxpozdeev
454eef2e07 Revert "* do not strikethrough completed tasks"
This reverts commit bad99a11ab.
2024-01-08 13:11:24 +03:00
maxpozdeev
87620921a2 css for groups in settings 2024-01-08 13:06:45 +03:00
maxpozdeev
3f721aa1cf little more debug info in setup 2024-01-07 14:24:03 +03:00
maxpozdeev
634cdcceee - bugfix: failed to upgrade db v1.4 due to unnecessary db request (GH-85) 2024-01-07 14:23:31 +03:00
wangyouworld
29d9da73bb
v1.8.0 zh_cn latest translation files 2024-01-02 16:42:36 +08:00
maxpozdeev
33bb209b97 - fix backup ext date format (minute) 2023-12-26 13:03:41 +03:00
maxpozdeev
081c1a94fe ext can alert on settings page refresh 2023-12-26 13:01:01 +03:00
maxpozdeev
9191031140 backup extension shows alerts on check and repair inconsistency actions 2023-12-26 12:36:04 +03:00
maxpozdeev
39ba94e303 * ext action can show alert on result 2023-12-26 12:34:22 +03:00
maxpozdeev
273ceca063 Merge branch 'master' of github.com:maxpozdeev/mytinytodo 2023-12-18 12:39:10 +03:00
Max Pozdeev
39790f01dd
Merge pull request #80 from toddy15/main
Update German translation
2023-12-18 12:34:53 +03:00
Dr. Tobias Quathamer
8e45ceaa3a Update German translation 2023-12-17 23:20:12 +01:00
maxpozdeev
f31b97c4c7 check for posix module in nottifications extension 2023-12-17 17:45:04 +03:00
maxpozdeev
7058769c96 update dev docker for php 8.3 2023-12-02 15:46:48 +03:00
maxpozdeev
d3e4286380 set version to 1.8.1 2023-12-02 14:31:39 +03:00
maxpozdeev
b721ca9b33 * do not highlight a task on hover in tasklist 2023-12-02 14:27:33 +03:00
maxpozdeev
b4703b29f8 - fix: alltasks tab was not filled if new list with tasks was created in another session 2023-12-02 14:13:39 +03:00
maxpozdeev
dd27362e96 - fix: counter of new tasks was not displayed due to incorrect list ref 2023-12-02 00:53:58 +03:00
maxpozdeev
7b84cdabf6 set version to 1.8.0 2023-11-23 23:18:40 +03:00
maxpozdeev
9c4087fe38 - handle more area to start manual sorting of tasks 2023-11-23 23:18:24 +03:00
maxpozdeev
691a99cb28 + can use manual sorting in reverse order (i.e. new tasks are on top) 2023-11-23 23:16:40 +03:00
maxpozdeev
dfe908776e little refactoring of js api driver 2023-11-23 21:51:54 +03:00
maxpozdeev
17c8cf87d6 * replace last float with flex in css 2023-11-09 23:07:45 +03:00
maxpozdeev
edfe391e41 * add more compatibility for http api (able to use GET and POST requests only) 2023-11-04 14:58:24 +03:00
maxpozdeev
b7e060e6db better css for RTL localisations 2023-10-18 15:57:18 +03:00
maxpozdeev
146d9a19bb better css for RTL localisations 2023-10-09 11:10:26 +03:00
maxpozdeev
ae6a4ef2a4 - fix: new tasks counter was not working in old browsers (like chrome 49) due to the absent cookies in fetch 2023-10-09 11:09:17 +03:00
maxpozdeev
9a1970d3b1 * use link media instead of js for auto and compulsory dark mode 2023-10-09 11:07:34 +03:00
maxpozdeev
9489e51574 * add ability to select month and year in due date picker 2023-10-04 23:17:34 +03:00
maxpozdeev
0b743618ea * disable preview by tag 2023-10-01 21:52:41 +03:00
maxpozdeev
6eac80ee1d set version to 1.8-beta2 2023-09-27 15:13:40 +03:00
maxpozdeev
e446880836 - fix: counter of new tasks was not reset when tasks were completed 2023-09-27 14:14:16 +03:00
maxpozdeev
5b9e142f89 exclude xattrs while build tar 2023-09-17 16:30:48 +03:00
maxpozdeev
1efbe606fd - fix: new tasks counter failed for newly created lists 2023-09-17 16:15:23 +03:00
maxpozdeev
ab9f398d7b - fix tab counter failed if only one tab open 2023-09-15 23:58:50 +03:00
maxpozdeev
d5dc74bea4 fix strings in updater ext 2023-09-14 12:55:59 +03:00
maxpozdeev
6fec3aca77 set version to 1.8-beta 2023-09-14 10:26:20 +03:00
maxpozdeev
6a816619a4 * Updater extension can update to v1.8.x 2023-09-14 10:26:00 +03:00
maxpozdeev
cf40bc72b2 polish ui 2023-09-12 10:48:57 +03:00
maxpozdeev
30ec81e95d * add setting to show date inline (closes GH-56) 2023-09-12 09:43:38 +03:00
maxpozdeev
0d07356817 do not open task viewer if click on link in task title 2023-09-11 22:13:11 +03:00
maxpozdeev
7ef36ee565 allow taskview on mobiles only by default 2023-09-11 21:50:11 +03:00
maxpozdeev
abcae0ee97 polish the style in taskviewer 2023-09-11 21:32:47 +03:00
maxpozdeev
988a3e8a03 updates in dev-docker 2023-09-11 16:05:14 +03:00
maxpozdeev
6958596fb8 * use data uri for images in css which were preloaded before 2023-09-11 16:01:40 +03:00
maxpozdeev
2e29733c8c fix for 1a75eec 2023-09-11 09:49:05 +03:00
maxpozdeev
288db261a9 + add setting to show exact due date (instead of relative) in tasklist (GH-19) 2023-09-11 09:48:39 +03:00
maxpozdeev
1a75eec5dc + add backup/restore feature as extension 2023-09-10 23:31:40 +03:00
maxpozdeev
149b554676 style for upload button in settings 2023-09-10 22:55:13 +03:00
maxpozdeev
4a3ebe285c update used php libs 2023-09-10 22:49:23 +03:00
maxpozdeev
d66a656733 add ext example for custom smart syntax 2023-09-10 22:45:08 +03:00
maxpozdeev
0a52e3a5f7 * counter of new tasks shows only open tasks (excluding completed) 2023-09-10 18:57:29 +03:00
maxpozdeev
628f5ed673 add support of 3-letter weekdays for duedate in smart syntax 2023-09-10 12:01:39 +03:00
maxpozdeev
28bd570436 * can use weekday for duedate in smart syntax; can use ! and @ for duedate prefix (GH-26) 2023-09-10 11:54:22 +03:00
maxpozdeev
6b882d13b2 update dev-docker for php 8.2 2023-09-09 21:28:30 +03:00
maxpozdeev
68bd687069 + added support duedate in smart syntax using format @duedate in the end of task (GH-26) 2023-09-09 21:02:12 +03:00
maxpozdeev
a3113ed195 * add filter action for smart syntax parser 2023-09-09 17:31:26 +03:00
maxpozdeev
b395eaa76c * revert back history while tab switching 2023-09-09 17:23:50 +03:00
maxpozdeev
bd6b3c650e * go back to app settings from extension settings; can use history navigation 2023-09-08 23:29:35 +03:00
maxpozdeev
6978d6b14b fix for 42e265d 2023-09-08 19:22:13 +03:00
maxpozdeev
c94aa7bcba * update jQuery to v3.7.1 2023-09-08 17:09:30 +03:00
maxpozdeev
84104a4238 * do not change browser history when switching between tabs 2023-09-08 15:18:46 +03:00
maxpozdeev
d5877f8a66 fix tab counter style 2023-09-08 13:45:41 +03:00
maxpozdeev
3ce91c9f44 fix version query for css and js files for dev needs 2023-09-08 09:48:12 +03:00
maxpozdeev
7f39f91ecd * rename jquery dir to js, rename jquery files (use query parameter for version caching) 2023-09-08 09:15:15 +03:00
maxpozdeev
882cc4c43a * add shadow on taskllist row on hover (to highlight the task when need to open task context menu) 2023-09-08 09:08:20 +03:00
maxpozdeev
2bf49d69ec update lang year 2023-09-08 08:31:33 +03:00
Max Pozdeev
ad73454f16
Merge pull request #68 from toddy15/main
Update German translation
2023-09-08 08:15:04 +03:00
maxpozdeev
42e265d802 + add badge to tabs with counter of newly created tasks (GH-5, GH-25) 2023-09-08 08:09:00 +03:00
maxpozdeev
f870bc3cf0 fix index in postgres 2023-09-06 22:30:57 +03:00
maxpozdeev
476ccc99ff add db::ciEquals() 2023-09-06 22:30:23 +03:00
maxpozdeev
b3e640a1f7 polish mobile task viewer 2023-09-06 17:54:00 +03:00
maxpozdeev
9fb376d8b2 + add setting for compulsory dark theme (closes GH-70) 2023-09-06 14:35:03 +03:00
maxpozdeev
bb0a102ceb updates in COPYRIGHT file 2023-09-06 09:48:00 +03:00
maxpozdeev
2cf207fba2 * use static date in date format settings 2023-09-06 09:42:14 +03:00
maxpozdeev
f9f97f3b7f * open links in markdown notes in new window (fixes GH-72) 2023-09-05 22:49:09 +03:00
maxpozdeev
580853a2b4 fix some css sizes 2023-09-05 21:54:26 +03:00
maxpozdeev
57ba859c57 * set width to 1100 px 2023-09-05 21:50:47 +03:00
maxpozdeev
05e4ed3bfb * find tasks filtered by tag using its normalized name (useful in sqlite) 2023-09-05 21:44:17 +03:00
maxpozdeev
d4cabb61b6 changes in internal api 2023-09-05 21:41:28 +03:00
maxpozdeev
696c795a4a small refactoring 2023-08-28 14:06:26 +03:00
maxpozdeev
675d523d0d fix wrong assignment 2023-08-25 11:39:41 +03:00
Dr. Tobias Quathamer
8bcb8eeb9e Update German translation 2023-08-24 23:09:05 +02:00
maxpozdeev
42a6c35d9c * add Noto Sans font to list of used fonts 2023-08-22 19:07:09 +03:00
maxpozdeev
a0de111dea * save the state of show notes on click with Cmd/Ctrl 2023-08-22 18:08:40 +03:00
maxpozdeev
0c2694c746 + use task viewer in mobile style 2023-08-22 16:54:33 +03:00
maxpozdeev
c6ceb562d0 * add rounded corners to tags in tag list 2023-08-21 20:52:39 +03:00
maxpozdeev
bad99a11ab * do not strikethrough completed tasks 2023-08-21 17:37:23 +03:00
maxpozdeev
e43519e27e - js: open new page (like edit task) scrolled to top 2023-08-21 11:24:18 +03:00
maxpozdeev
c8963ed6e3 fix markdown code css 2023-08-20 19:17:25 +03:00
maxpozdeev
2f14fa0b1c fix popup shadow in dark mode 2023-08-20 17:47:02 +03:00
maxpozdeev
6fe3b8a955 experiments with group style in settings 2023-08-20 16:05:45 +03:00
maxpozdeev
5b1d00fbe2 decrease transition time to 0.1s for some elements 2023-08-20 16:04:36 +03:00
maxpozdeev
f48b20c389 ui fixes in settings 2023-08-20 11:31:01 +03:00
maxpozdeev
d2c54f671f remove slash in self-closing tags 2023-08-19 21:33:03 +03:00
maxpozdeev
69e61efd86 fix for prev commit 2023-08-19 21:21:44 +03:00
maxpozdeev
ce7f88f75d - fix font look in modal dialogs (weird in safari) 2023-08-19 19:18:48 +03:00
maxpozdeev
8c74e3cbd7 little update of notifications extension 2023-08-19 18:47:18 +03:00
maxpozdeev
5325274a91 * can search for polish Ł with latin L in sqlite 2023-08-19 18:28:25 +03:00
maxpozdeev
aeb732b446 + new notifications for tasks (edited, completed, deleted) and lists (deleted, deleted complted tasks) 2023-08-19 18:24:40 +03:00
maxpozdeev
a6bde46cc3 * can set mail from in notifications extension 2023-08-19 16:56:15 +03:00
maxpozdeev
c42143a17d add theme_footer_content_end action 2023-08-18 16:21:39 +03:00
maxpozdeev
8d6935e7a3 - fix Updater extension 2023-08-18 14:55:28 +03:00
maxpozdeev
bcafe5c031 * can close task editor on esc key 2023-08-14 22:06:19 +03:00
maxpozdeev
7dc1ea5013 * larger area in height for click on task 2023-08-14 18:19:26 +03:00
maxpozdeev
abedfee4a2 - escape tag html in tag filter bar 2023-08-14 17:24:00 +03:00
maxpozdeev
ec5e380c51 * show "no tags" in tag cloud and task editor 2023-08-14 13:37:39 +03:00
maxpozdeev
3b76ff34a5 add dev-docker with php 8.3 2023-08-08 22:24:54 +03:00
maxpozdeev
ab90cadd3e * use utf8mb4_unicode_520_ci for mysql columns in setup 2023-08-08 21:34:53 +03:00
maxpozdeev
c21c6d6910 show more info on db error while setup 2023-08-07 21:25:24 +03:00
maxpozdeev
4b4e47df6a Merge branch 'v1.8' 2023-08-06 19:12:45 +03:00
maxpozdeev
3e5252e931 * check X-Forwarded-Proto for https
(cherry picked from commit 778a4e5fa8)
2023-08-06 18:45:41 +03:00
maxpozdeev
f19573221a - more relative paths
(cherry picked from commit cf39163d7b)
2023-08-06 18:45:30 +03:00
maxpozdeev
4411e2a8d3 - use relative urls for internal needs to avoid malfunction in setups with proxy
(cherry picked from commit e871e2e02b)
2023-08-06 18:44:44 +03:00
maxpozdeev
b4f360de78 Update jQuery to v3.6.4 2023-08-06 17:18:58 +03:00
maxpozdeev
778a4e5fa8 * check X-Forwarded-Proto for https 2023-08-06 17:05:25 +03:00
maxpozdeev
b38bf18f9d optimize docker dev env for postgres: less disk size, able to run in podman 2023-08-06 15:54:15 +03:00
maxpozdeev
956c3cf53b do not use hostname in docker dev containers 2023-08-06 12:34:19 +03:00
maxpozdeev
85ef3c04af add docker test env with php 7.2 and postgres 10.0 2023-08-04 12:05:06 +03:00
maxpozdeev
173a1b387d * adopt sql to support postgresql 2023-08-03 23:13:55 +03:00
maxpozdeev
772305dbed + can setup postgresql database 2023-08-03 23:09:32 +03:00
maxpozdeev
cf39163d7b - more relative paths 2023-08-03 18:06:46 +03:00
maxpozdeev
01a0cdb50b Set version to 1.7.4 2023-08-03 14:19:10 +03:00
maxpozdeev
e871e2e02b - use relative urls for internal needs to avoid malfunction in setups with proxy 2023-08-03 01:14:12 +03:00
maxpozdeev
8254493d97 fix toobar height 2023-04-19 23:27:02 +03:00
maxpozdeev
a81e069f77 * ext settings page can have no bottom buttons (added method to MTTExtensionSettingsInterface) 2023-04-06 13:47:30 +03:00
maxpozdeev
6e41e5865e small ui fixes in settings (extensions lists line-height, back button padding for better touch) 2023-04-06 13:00:29 +03:00
maxpozdeev
28c59c3c5b little more debug info in updater extension 2023-04-06 12:36:06 +03:00
maxpozdeev
4c01eca4ad add spaces for radio options in settings 2023-03-31 14:20:32 +03:00
maxpozdeev
c8075e0d6b - show default (english) localization in extension if required language file is not present in extension 2023-03-31 14:01:36 +03:00
maxpozdeev
7e72dbe14d increase width of settings input in mobile style 2023-03-31 13:57:47 +03:00
maxpozdeev
1f0b707193 * show notice in notification extension if allow_url_fopen directive is off 2023-03-31 13:56:28 +03:00
maxpozdeev
df031728da bump version to 1.7.4-dev 2023-03-29 13:50:54 +03:00
maxpozdeev
3f6c275298 little more debug info in telegram notifications 2023-03-29 13:48:55 +03:00
maxpozdeev
ca3e332a2f -fix: handle incorrect json in settings
(cherry picked from commit 80f19f936c)
2023-03-28 23:17:11 +03:00
maxpozdeev
80f19f936c -fix: handle incorrect json in settings 2023-03-28 23:15:06 +03:00
maxpozdeev
755d766262 add dev-docker for php 8.2 + angie 2023-03-27 15:45:53 +03:00
maxpozdeev
74e59f1ccf add dev-docker for php-fpm 8.2 + nginx 2023-03-24 23:21:37 +03:00
maxpozdeev
176b1e9c99 - fix deprecation notices in php 8.2
(cherry picked from commit 47972dce05)
2023-03-24 19:08:27 +03:00
maxpozdeev
47972dce05 - fix deprecation notices in php 8.2 2023-03-24 19:07:58 +03:00
maxpozdeev
ced75b805a can setup web api url to use PATH_INFO 2023-03-24 18:51:46 +03:00
maxpozdeev
a41eae1ea7 rename DBCore::defaultInstance() 2023-03-24 18:23:06 +03:00
maxpozdeev
8815569861 ** remove "tags" and "tags_ids" fields from tasks table (used for better performance) 2023-03-24 18:19:21 +03:00
maxpozdeev
3a68339cb9 - fix deprecation notices in php 8.2
(cherry picked from commit 8833e7c417)
2023-03-23 22:35:49 +03:00
maxpozdeev
8833e7c417 - fix deprecation notices in php 8.2 2023-03-23 22:28:00 +03:00
maxpozdeev
8da989ed51 update dev-docker with php 8.2 to use release version of php
(cherry picked from commit f0d0adf493)
2023-03-15 13:03:38 +03:00
maxpozdeev
46a98f459f Updated Portuguese (Brazilian) translation by @rscarrasco (thanks to Rafael da Silva Carrasco) 2023-03-13 16:56:54 +03:00
Max Pozdeev
ff7d399eaa
Merge pull request #58 from famousman/patch-2
updated fa.json by @famousman
2023-03-11 15:29:03 +03:00
Saeb Khanzadeh
afbf174000
modified fa.json
I translated this app into Persian completely. I hope people use it and enjoy it as All humans deserved.
thank you, developers
2023-02-09 15:09:15 +03:30
maxpozdeev
31d446e8c4 rename translation file 'cns.json' to 'zh-cn.json' (closed GH-55) 2022-12-22 19:01:52 +03:00
Max Pozdeev
d5a57c869b
Merge pull request #54 from xhemj/master
Update translation Chinese Simplified (cns) (thanks to @xhemj)
2022-12-22 18:54:22 +03:00
maxpozdeev
d61d556574 Update Dutch translation (thanks to J.C.Barnhoorn) GH-53 2022-12-21 19:21:42 +03:00
xhemj
84024e177c
Fix translation errors 2022-12-20 12:12:13 +08:00
xhemj
3e31e87daf
Update translation Chinese Simplified (cns) 2022-12-20 11:44:31 +08:00
maxpozdeev
0990900da2 liitle optiomization of sql to get tasks 2022-12-14 00:29:57 +03:00
maxpozdeev
f0d0adf493 update dev-docker with php 8.2 to use release version of php 2022-12-12 11:03:30 +03:00
maxpozdeev
cbcba71e32 Update Polish translation (thanks to Marcin R Martin) 2022-12-12 11:02:05 +03:00
maxpozdeev
7ef88217cb add Normalizer polyfill by Symfony 2022-12-07 15:53:47 +03:00
maxpozdeev
2566881fbf + search task in sqlite by normalized value (no diacritics) (use Normalizer from Intl extension) 2022-12-07 15:48:18 +03:00
maxpozdeev
47d37a46b5 set version to 1.8-dev 2022-12-05 12:41:51 +03:00
maxpozdeev
5c174fade3 - fix page title in setup
(cherry picked from commit 79c06713cb)
2022-12-05 11:44:19 +03:00
maxpozdeev
79c06713cb - fix page title in setup 2022-12-05 11:40:39 +03:00
maxpozdeev
24ca9b1a33 + add custom collation to sqlite for tag name (to be utf8-case-insensitive) and task title (to sort by title) 2022-12-05 10:45:24 +03:00
maxpozdeev
56fa0fa039 + add updater extension 2022-11-29 20:56:23 +03:00
maxpozdeev
d36bab9e16 fix non-throwing exception in setup 2022-11-29 00:27:51 +03:00
maxpozdeev
6923bdcda6 fixes in ru translation 2022-11-29 00:26:43 +03:00
maxpozdeev
0029be3291 some changes for extension settings 2022-11-29 00:26:18 +03:00
maxpozdeev
01e688b49e + add CustomCSS extension 2022-11-22 00:29:55 +03:00
maxpozdeev
27bdebfde4 add monospace class to css 2022-11-22 00:28:27 +03:00
maxpozdeev
11d2ad4139 * do not use PATH_INFO in http api by default 2022-11-21 14:55:13 +03:00
maxpozdeev
3daa661adb check php version in setup 2022-11-20 16:02:13 +03:00
maxpozdeev
e3f32c5209 can set custom location of theme files with MTT_THEME constant 2022-11-20 16:01:35 +03:00
maxpozdeev
3af04cbd7e can show message details of extension api action 2022-11-19 21:37:30 +03:00
maxpozdeev
7a5a82b617 fix right padding for long names in tabs 2022-11-19 21:29:58 +03:00
maxpozdeev
f109b7a762 * apply css appearance for select 2022-11-19 21:28:16 +03:00
maxpozdeev
be6738c8ba facelifting of newtask-ext.svg (better looking on low-dpi screens) 2022-11-19 00:24:33 +03:00
maxpozdeev
26d42c8e6d define MTT_CONTENT_PATH 2022-11-14 20:58:34 +03:00
maxpozdeev
59d69c2708 log json decoding errors in translation loading 2022-11-14 19:51:39 +03:00
maxpozdeev
c833fe4828 + add theme_head_end notification 2022-11-14 19:09:45 +03:00
maxpozdeev
9dd95d3e5b implement extension httpApiUrlPrefix 2022-11-14 19:09:19 +03:00
maxpozdeev
2123d6630d * focus text input in modal prompt 2022-11-14 16:56:52 +03:00
maxpozdeev
4452e8eb1b disable background scrolling on modal dialog with js 2022-11-14 16:53:39 +03:00
maxpozdeev
068335c2ad css fixes for modal dialog 2022-11-13 18:44:31 +03:00
maxpozdeev
f10ee503f8 allow textarea in setings 2022-11-13 15:08:18 +03:00
maxpozdeev
8d8f542908 add simple callbacks to MTTNotificationCenter 2022-11-13 14:56:56 +03:00
maxpozdeev
02ac0655b0 little changes in http api internals 2022-11-13 00:45:24 +03:00
maxpozdeev
d71400ea63 - do not scroll to top if modal dialog is open 2022-11-12 18:26:36 +03:00
maxpozdeev
9e4b8776d7 keep more space in dev docker by not using dockerfile for mariadb containers 2022-11-12 16:35:13 +03:00
maxpozdeev
86c02990c1 experimental sort by title (disabled yet) 2022-11-12 15:22:59 +03:00
maxpozdeev
e41429c174 refactor js changeTaskOrder 2022-11-11 16:39:59 +03:00
maxpozdeev
c7b2d26a95 set version to 1.7.3 2022-11-10 21:27:06 +03:00
maxpozdeev
7625cd975c - close tag autocomplete menu on edit/add form close 2022-11-10 21:26:16 +03:00
maxpozdeev
40f9d33a93 + now search is case-insensitive for non-latin characters in sqlite database 2022-11-10 21:24:41 +03:00
maxpozdeev
37e6f30a57 * do not remember last used list for readonly users (GH-47) 2022-11-05 20:06:39 +03:00
maxpozdeev
a91aaa09af * workaround in api if PATH_INFO is not set by webserver 2022-11-04 16:48:37 +03:00
160 changed files with 7477 additions and 2917 deletions

View file

@ -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
View file

@ -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/

View file

@ -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.

View file

@ -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");
}

View file

@ -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
View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "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"
}

View file

@ -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 $@

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
}
}

View file

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

View file

@ -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

View file

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

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

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

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

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

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
# }

View file

@ -16,7 +16,7 @@ https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
myTinyTodo
--------------
Url: https://www.mytinytodo.net/
Copyright: 2009-2011,2019-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 20112014, 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.

View file

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

View file

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -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 =
}
};
})();

View file

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

View file

@ -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

View 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

View 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

View 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();
}

View file

@ -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;
}

View file

@ -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; }

View file

@ -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: ' · '; /* '&middot;' */
}
#mtt.show-date .task-date {
display: block;
}
#mtt.show-date.date-inline .task-date {
display: inline-block; /* for RTL */
margin:0;
margin-left:3px;
}
#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 */

View file

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

View file

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

View file

@ -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
View file

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

View file

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

View file

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

View 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"
}

View 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"
}

View file

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

View 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;
}
}

View file

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

View file

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

View 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
View file

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

View 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"));
}
}
}

View 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;
}
}

View 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"),
];
}
}

View 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();
}
}

View 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?
}
}

View file

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

View 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…"
}

View 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
View 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 &nbsp;
<button type=button data-ext-settings-action="post:download" data-ext="$ext" $downloadDisabled> {$e('backup.download')} </button>
</div>
</div>
<div class="tr">
<div class="th"> {$e('backup.h_inconsistency')}
<div class="descr">{$e('backup.d_inconsistency')}</div>
</div>
<div class="td">
<button type=button data-ext-settings-action="post:checkInconsistency" data-ext="$ext"> {$e('backup.check')} </button> &nbsp;
<button type=button data-ext-settings-action="post:repairInconsistency" data-ext="$ext"> {$e('backup.repair')} </button> <br>
</div>
</div>
<div class="tr">
<div class="th"> {$e('backup.h_restore')}
<div class="descr"> {$e('backup.d_restore')} </div>
</div>
<div class="td">
<label class="mtt-settings-upload-button">
<input type="file" name="file" onchange="return onBackupFileChange(this)" data-ext-settings-action="post:restore" data-ext="$ext">
{$e('backup.restore')}
</label>
</div>
</div>
EOD;
}
function settingsPageType(): int
{
return 1; //no form buttons
}
function saveSettings(array $params, ?string &$outMessage): bool
{
return false;
}
/*
static function preferences(): array
{
return [
'backupFilePath' => MTTPATH. 'db/backup.xml'
];
}
*/
static function backupFilePath()
{
//return self::preferences()['backupFilePath'];
return MTTPATH. 'db/backup.xml';
}
}

0
src/ext/index.html Normal file
View file

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