Skip to content

Commit 8938f14

Browse files
committed
Feature: custom queries
This commit enables function to define custom queries within configuration file or by environment variables. Its not scope of this commit to define custom queries on-the-fly on the ui. Closes #24
1 parent a8e3f01 commit 8938f14

File tree

21 files changed

+718
-158
lines changed

21 files changed

+718
-158
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## Sun 04, May, 2025
2+
- [x] Remember shortcuts for project and tags in a cache file.
3+
Default cache path: $HOME/.cache/taskwarrior-web/mnemonics.cache
4+
5+
## Thu 01, May, 2025
6+
- [x] Improved CI pipeline in order to increase continues quality checks.
7+
- [x] Improved mobile view: proper scaling of the pages as well as headers are now really sticky, not fixed anymore. This ensures, that the header does not cover any tasks on small displays.
8+
9+
## Tue 29, April, 2025
10+
- [x] Rework of task modifications.
11+
- [x] Adding absolute dates in task details.
12+
- [x] Added possibility to delete tasks.
13+
- [x] For denotation its now possible to select specific annotations.
14+
115
## Fri 28, March, 2025
216
- [x] Font's can be customized
317
- [x] Removed packaged font

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ serde_path_to_error = "0.1.17"
2828
shell-words = "1.1.0"
2929
directories = "6.0.0"
3030
toml = "0.8.22"
31+
config = { version = "0.15.11", default-features = false, features = ["toml"] }
3132

3233
[dev-dependencies]
3334
tempfile = "3.19.1"

README.md

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,12 @@ It is recommend to specify the corresponding volume in order to persist the data
7878

7979
In order to configure the environment variables and contexts for `timewarrior-web`, docker environments can be specified:
8080

81-
| Docker environment | Shell environment | Purpose
82-
| -------------------------------- | ----------------------- | ---------------------------------------------------------|
81+
| Docker environment | Shell environment | Purpose |
82+
| -------------------------------- | ----------------------- | -------------------------------------------------------- |
8383
| TASK_WEB_TWK_SERVER_PORT | TWK_SERVER_PORT | Specifies the server port (see "Ports") |
8484
| TASK_WEB_DISPLAY_TIME_OF_THE_DAY | DISPLAY_TIME_OF_THE_DAY | Displays a time of the day widget in case of value `1` |
8585
| TASK_WEB_TWK_USE_FONT | TWK_USE_FONT | Font to be used. If not, browsers default fonts are used |
86-
| TASK_WEB_TWK_THEME | TWK_THEME | Defines the theme to be used (see "Themes") |
86+
| TASK_WEB_TWK_THEME | TWK_THEME | Defines the theme to be used (see "Themes") |
8787

8888
## Hooks
8989

@@ -219,17 +219,63 @@ Keyboard shortcut is `u`
219219
This will bring up undo confirmation dialog
220220
![Undo](./screenshots/undo.png)
221221
222+
## Custom queries
223+
224+
Task organization is a pretty personal thing. And depending on the project or individual base, custom workflows and reportings are required.
225+
Create a configuration file under Linux in `$HOME/.config/taskwarrior-web/config.toml` or under Windows in `%APPDATA%\taskwarrior-web\config.toml` and add custom queries.
226+
227+
A configuration file can look like:
228+
229+
```toml
230+
[custom_queries]
231+
232+
[custom_queries.completed_last_week]
233+
query = "end.after:today-1wk and status:completed"
234+
description = "completed last 7days"
235+
236+
[custom_queries.due_today]
237+
query = "due:today"
238+
description = "to be done today"
239+
fixed_key = "ni" # this will override randomly generated key
240+
```
241+
242+
Following options for each query definition is available:
243+
| property | mandatory | meaning |
244+
| ----------- | --------- | ---------------------------------------------------------------------- |
245+
| query | X | specifies the query to be executed on `taskwarrior`. |
246+
| description | X | description to be shown in the Web-UI for recognizing the right query. |
247+
| fixed_key | | Can be specified as two characters which will hardcode the shortcut. |
248+
249+
The query can be selected via keyboard shortcuts or via click on the right buttons.
250+
In order to select custom queries with the keyboard, first type in `q` as key for queries.
251+
A list is shown with available custom queries:
252+
![Custom query selections](./screenshots/custom_queries_selection.png)
253+
254+
On each custom query, either a pre-defined shortcut key is shown or an automatic and cached shortcut is shown.
255+
The right one is typed and automatically the custom query is set:
256+
257+
![Custom query list](./screenshots/custom_queries_list.png)
258+
259+
As soon as one of the other reports like `next`, `pending` or others are selected, the custom query is unset and `taskwarrior-web` standard reports are shown.
260+
261+
Beside of a configuration file, it is possible to configure via environment variables as well:
262+
```shell
263+
env TWK_custom_queries__one_query__fixed_key=ni TWK_custom_queries__one_query__query="end.after:today-1wk and status:completed" TWK_custom_queries__one_query__description="completed last 7days" RUST_LOG="INFO" cargo run
264+
```
265+
266+
The same way it is possible to configure the docker container accordingly.
267+
222268
## Switch theme
223269
224270
It is possible to switch the theme, which is saved in local storage too.
225271
226272
For this following three symbols are used (left of the command bar):
227273
228-
| Symbol | Purpose |
229-
|--------|---------------------------------------|
230-
| | Auto Mode or forced mode from server |
231-
| | Dark mode |
232-
| 🌣 | Light mode |
274+
| Symbol | Purpose |
275+
| ------ | ------------------------------------ |
276+
| | Auto Mode or forced mode from server |
277+
| | Dark mode |
278+
| 🌣 | Light mode |
233279
234280
# WIP warning
235281
@@ -243,11 +289,11 @@ there will be errors, as no checks, and there may not be any error messages in c
243289
- [x] Hiding empty columns
244290
- [ ] Temporary highlight last modified row, if visible
245291
- [x] Make the mnemonics same for tags on refresh
246-
- [ ] Modification
247-
- [ ] Deleting
292+
- [x] Modification
293+
- [x] Deleting
248294
- [ ] Following Context
249295
- [ ] Error handling
250-
- [ ] Retaining input in case of error
296+
- [x] Retaining input in case of error
251297
- [ ] Finetune error handling
252298
- [ ] Add more tests
253299
- [ ] Convert to desktop app using Tauri

config.sample.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[custom_queries]
2+
3+
[custom_queries.one_query]
4+
description = "report of something"
5+
6+
[custom_queries.two_query]
7+
description = "report of another thing"
8+
fixed_key = "n" # this will override randomly generated key

frontend/src/main.ts

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function focusTextInput(event: KeyboardEvent | MouseEvent) {
2323
event.preventDefault()
2424
ss.focus();
2525
} else {
26-
document.getElementById('cmd-inp').focus();
26+
document.getElementById('cmd-inp')?.focus();
2727
}
2828
}
2929

@@ -34,11 +34,11 @@ window.handleTaskAnnotations = (event: KeyboardEvent | MouseEvent) => {
3434
}
3535
event.preventDefault();
3636
let annoSelector = document.getElementById('anno-inp');
37-
document.querySelector('#anno-inp').classList.toggle('hidden');
37+
document.querySelector('#anno-inp')?.classList.toggle('hidden');
3838
Array.from(document.querySelectorAll('.is-a-annotation')).forEach((value) => {
3939
value.classList.toggle('hidden');
4040
});
41-
if (annoSelector.checkVisibility()) {
41+
if (annoSelector?.checkVisibility()) {
4242
annoSelector.focus();
4343
}
4444
return false;
@@ -59,45 +59,66 @@ window.handleTaskAnnotationTrigger = (event: KeyboardEvent | MouseEvent) => {
5959

6060
hotkeys('esc', function (event, handler) {
6161
// Prevent the default refresh event under WINDOWS system
62-
if(event.target != document.getElementById('tag-inp')) {
62+
if(event.target != document.getElementById('tag-inp') &&
63+
event.target != document.getElementById('query-inp')) {
6364
console.log("not processing")
6465
return
6566
}
66-
event.preventDefault()
67-
let tag_selector = document.getElementById('cmd-inp')
68-
document.querySelector('#tags_map_drawer').classList.toggle('hidden')
69-
if (tag_selector.checkVisibility()) {
67+
event.preventDefault();
68+
let tag_selector = document.getElementById('cmd-inp');
69+
if (event.target == document.getElementById('tag-inp')) {
70+
document.querySelector('#tags_map_drawer')?.classList.toggle('hidden');
71+
} else if (event.target == document.getElementById('query-inp')) {
72+
document.querySelector('#querys_map_drawer')?.classList.toggle('hidden');
73+
}
74+
if (tag_selector?.checkVisibility()) {
7075
tag_selector.focus();
7176
}
7277
return false;
7378
});
7479

80+
hotkeys('ctrl+shift+K', function (event, handler) {
81+
// Prevent the default refresh event under WINDOWS system
82+
event.preventDefault();
83+
focusTextInput(event);
84+
return false;
85+
});
86+
7587
hotkeys('t', function (event, handler) {
7688
// Prevent the default refresh event under WINDOWS system
7789
if(event.target != document.getElementById('cmd-inp')) {
78-
console.log("not processing")
90+
console.debug("not processing")
7991
return
8092
}
8193
event.preventDefault()
82-
window['toggleTagPanel']();
83-
94+
window['togglePanel']('tag');
8495
});
8596

86-
hotkeys('ctrl+shift+K', function (event, handler) {
97+
hotkeys('q', function (event, handler) {
8798
// Prevent the default refresh event under WINDOWS system
88-
event.preventDefault();
89-
focusTextInput(event);
90-
return false;
99+
if(event.target != document.getElementById('cmd-inp')) {
100+
console.debug("not processing")
101+
return
102+
}
103+
event.preventDefault()
104+
window['togglePanel']('query');
91105
});
92106

93-
window['toggleTagPanel'] = () => {
94-
let tagSelector = document.getElementById('tag-inp')
95-
document.querySelector('#tags_map_drawer').classList.toggle('hidden')
96-
if (tagSelector.checkVisibility()) {
107+
window['togglePanel'] = (panelType: string) => {
108+
let tagSelector = document.getElementById(panelType + '-inp')
109+
document.querySelector('#' + panelType + 's_map_drawer')?.classList.toggle('hidden')
110+
if (tagSelector?.checkVisibility()) {
97111
tagSelector.focus();
98112
}
99113
return false;
100-
}
114+
};
115+
116+
window['processPanelShortcut'] = (event: KeyboardEvent, panelType: string) {
117+
const shortcut = event.target?.value;
118+
if (shortcut.length >= 2) {
119+
document.getElementById(panelType + "s_" + shortcut)?.click()
120+
};
121+
};
101122

102123
document.addEventListener('click', function (event) {
103124
let element = document.getElementsByTagName('html')[0];
@@ -154,6 +175,7 @@ document.addEventListener("DOMContentLoaded", function () {
154175
.padStart(2, "0");
155176
}, 1000
156177
)
178+
157179
let day_progress = setInterval(
158180
() => {
159181
const dd = document.getElementById('time_of_the_day');

frontend/templates/left_action_bar.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
<div class="pl-2 pr-2 py-1.5 rounded-sm text-xs flex bg-base-100 gap-2 items-center flex-wrap">
22

33
<div class="join" id="tag_search_bar">
4-
<button class="btn btn-neutral btn-xs join-item" id="tag-selection-toggle" onclick="window['toggleTagPanel']()">
4+
<button class="btn btn-neutral btn-xs join-item" id="tag-selection-toggle" onclick="window['togglePanel']('tag')">
55
<span><span class="shortcut_key opacity-50">t</span>ags</span>
66
</button>
7+
<button class="btn btn-neutral btn-xs join-item" id="query-selection-toggle" onclick="window['togglePanel']('query')">
8+
<span><span class="shortcut_key opacity-50">q</span>ueries</span>
9+
</button>
710
<span class="btn btn-neutral btn-xs join-item" id="task_action_bar">
811
<button hx-get="task_action_bar" hx-target="#task_action_bar" hx-swap="outerHTML"
912
hx-trigger="click,keyup[key=='s'] from:#cmd-inp">ta<span class="shortcut_key opacity-50">s</span>k</button>

frontend/templates/query_bar.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
<div class="flex flex-wrap gap-2 p-4 bg-accent-content">
3+
<div class="join">
4+
<button
5+
id="query-btn-back"
6+
class="btn btn-warning btn-xs join-item"
7+
hx-get="tasks"
8+
hx-trigger="click,keyup[key=='Escape'] from:#task-inp"
9+
hx-target="#list-of-tasks"
10+
hx-include="[id='filtering']"
11+
><span><span class="shortcut_key opacity-50">ESC</span></span></button>
12+
<label for="query-inp" class="hidden"></label>
13+
<input type="text" id="query-inp"
14+
class="input input-xs input-accent join-item"
15+
placeholder="Custom queries"
16+
hx-trigger="changes delay:2s"
17+
hx-target="#list-of-tasks"
18+
hx-include="[id='filtering']"
19+
hx-swap="innerHTML"
20+
autofocus
21+
autocomplete="off"
22+
onkeyup="processPanelShortcut(event, 'query')"
23+
/>
24+
</div>
25+
{% for shortcut, query in custom_queries_map %}
26+
<div class="mb-2">
27+
<div class="flex gap-2">
28+
<button id="querys_{{shortcut}}"
29+
class="btn btn-xs btn-info shrink"
30+
hx-get="tasks?filter={{ query.query }}&custom_query={{query.description | urlencode}}"
31+
hx-target="#list-of-tasks"
32+
hx-include="[id='filtering']"
33+
hx-swap="innerHTML"
34+
>
35+
<span class="kbd kbd-xs bg-primary">{{ shortcut }}</span>
36+
{{ query.description }}
37+
</button>
38+
</div>
39+
</div>
40+
{% endfor %}
41+
</div>
42+

0 commit comments

Comments
 (0)