diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index 0c7ca1d..7461445 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -13,6 +13,7 @@ import 'hyperscript.org';
import * as _hyperscript from "hyperscript.org";
import hotkeys from "hotkeys-js";
import * as theme from "./theme.ts";
+import * as timer from "./timer.ts";
_hyperscript.browserInit();
@@ -146,46 +147,7 @@ document.addEventListener('click', function (event) {
document.addEventListener("DOMContentLoaded", function () {
theme.init();
-
- let n = setInterval(
- () => {
- let whichOne = 0;
- // document.getElementById('active-timer').querySelectorAll('span.timer-duration')[0]
- let dd = document.getElementById('active-timer');
- if (dd === undefined || dd === null) {
- return
- }
- let timeBox = dd.children[1].children[whichOne];
- let s = timeBox.textContent.split(":");
- let second = parseInt(s.pop());
- let minute = parseInt(s.pop());
- if (isNaN(minute)) {
- minute = 0;
- }
- let hour = parseInt(s.pop());
- if (isNaN(hour)) {
- hour = 0;
- }
- second += 1;
- if (second >= 60) {
- second = 0;
- minute += 1;
- if (minute > 60) {
- hour += 1;
- }
- }
- timeBox.textContent = hour.toString()
- // @ts-ignore
- .padStart(2, "0") + ":" +
- minute.toString()
- // @ts-ignore
- .padStart(2, "0") + ":" +
- second.toString()
- // @ts-ignore
- .padStart(2, "0");
- }, 1000
- )
-
+ timer.init();
let day_progress = setInterval(
() => {
const dd = document.getElementById('time_of_the_day');
diff --git a/frontend/src/timer.ts b/frontend/src/timer.ts
new file mode 100644
index 0000000..da600f9
--- /dev/null
+++ b/frontend/src/timer.ts
@@ -0,0 +1,42 @@
+var global_twk_timer: number|null = null;
+
+function updateTimer() {
+ let timer_dom = document.getElementById('active-timer');
+ if (timer_dom === null) {
+ return;
+ }
+
+ let timer_box_dom = timer_dom.querySelector(".timer-duration");
+ if (timer_box_dom === null) {
+ return;
+ }
+
+ let task_start = timer_dom?.getAttribute("data-task-start");
+ if (task_start === null || task_start === undefined) {
+ return;
+ }
+ let task_start_dt = Date.parse(task_start);
+ let now_utc = Date.now();
+ let diff_s = Math.floor((now_utc - task_start_dt)/1000);
+ let hours = Math.floor(diff_s/3600);
+ let minutes = Math.floor((diff_s - (hours*3600))/60);
+ let seconds = Math.floor((diff_s - (hours*3600)) - (minutes*60));
+ timer_box_dom.textContent =
+ hours.toString().padStart(2, "0") + ":" +
+ minutes.toString().padStart(2, "0") + ":" +
+ seconds.toString().padStart(2, "0")
+}
+
+export function init() {
+ global_twk_timer = setInterval(
+ updateTimer,
+ 1000
+ )
+}
+
+export function stop() {
+ if (global_twk_timer != null && global_twk_timer != undefined) {
+ clearInterval(global_twk_timer);
+ global_twk_timer = null;
+ }
+}
\ No newline at end of file
diff --git a/frontend/templates/active_task.html b/frontend/templates/active_task.html
index 7d9697f..317fba6 100644
--- a/frontend/templates/active_task.html
+++ b/frontend/templates/active_task.html
@@ -14,7 +14,7 @@
+ data-task-id="{{ active_task.uuid }}" data-task-start="{{ datetime_iso(datetime=active_task.start) }}">
{{ active_task.description }}
diff --git a/src/lib.rs b/src/lib.rs
index 9f7a696..c4b72a8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,6 +17,10 @@ use std::str::FromStr;
use crate::endpoints::tasks::task_query_builder::{TaskQuery, TaskReport};
use crate::endpoints::tasks::{is_a_tag, is_tag_keyword};
+
+#[cfg(test)]
+mod tests;
+use chrono::TimeZone;
use chrono::{DateTime, TimeDelta};
use linkify::LinkKind;
use rand::distr::{Alphanumeric, SampleString};
@@ -25,6 +29,7 @@ use taskchampion::Uuid;
use tera::{escape_html, Context};
use tracing::{trace, warn};
+
lazy_static::lazy_static! {
pub static ref TEMPLATES: tera::Tera = {
let mut tera = match tera::Tera::new("dist/templates/**/*") {
@@ -37,6 +42,7 @@ lazy_static::lazy_static! {
tera.register_function("project_name", get_project_name_link());
tera.register_function("date_proper", get_date_proper());
tera.register_function("timer_value", get_timer());
+ tera.register_function("datetime_iso", get_datetime_iso());
tera.register_function("date", get_date());
tera.register_function("obj", obj());
tera.register_function("remove_project_tag", remove_project_from_tag());
@@ -367,6 +373,17 @@ fn update_tag_bar_key_comb() -> impl tera::Filter {
)
}
+#[cfg(not(test))]
+#[allow(dead_code)]
+fn get_utc_now() -> DateTime {
+ chrono::prelude::Utc::now()
+}
+#[cfg(test)]
+#[allow(dead_code)]
+fn get_utc_now() -> DateTime {
+ chrono::Utc.with_ymd_and_hms(2025, 5, 1, 3, 55, 0).unwrap()
+}
+
pub struct DeltaNow {
pub now: DateTime,
pub delta: TimeDelta,
@@ -380,7 +397,7 @@ impl DeltaNow {
// Try taskchampions variant.
chrono::prelude::NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%SZ").unwrap())
.and_utc();
- let now = chrono::prelude::Utc::now();
+ let now = get_utc_now();
let delta = now - time;
Self { now, delta, time }
}
@@ -424,6 +441,22 @@ fn get_date_proper() -> impl tera::Function {
)
}
+fn get_datetime_iso() -> impl tera::Function {
+ Box::new(
+ move |args: &HashMap| -> tera::Result {
+ let date_time_str = args.get("datetime").unwrap().as_str().unwrap();
+ // we are working with utc time
+ let date_time = chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y%m%dT%H%M%SZ")
+ .unwrap_or_else(|_|
+ // Try taskchampions variant.
+ chrono::prelude::NaiveDateTime::parse_from_str(date_time_str, "%Y-%m-%dT%H:%M:%SZ").unwrap()
+ )
+ .and_utc();
+ Ok(tera::to_value(date_time.to_rfc3339()).unwrap())
+ },
+ )
+}
+
fn get_date() -> impl tera::Function {
Box::new(
move |args: &HashMap| -> tera::Result {
@@ -485,7 +518,7 @@ fn get_timer() -> impl tera::Function {
let s = if delta.num_hours() > 0 {
format!(
- "{:>02}:{:>02}",
+ "{:>02}:{:>02}:00",
delta.num_hours(),
delta.num_minutes() - (delta.num_hours() * 60)
)
@@ -497,7 +530,7 @@ fn get_timer() -> impl tera::Function {
num_seconds % 60
)
} else {
- format!("{}s", num_seconds)
+ format!("00:00:{:>02}", num_seconds)
};
Ok(tera::to_value(s).unwrap())
},
diff --git a/src/tests.rs b/src/tests.rs
new file mode 100644
index 0000000..6180007
--- /dev/null
+++ b/src/tests.rs
@@ -0,0 +1,54 @@
+use tera::Function;
+
+use super::*;
+
+#[test]
+fn test_get_timer() {
+ let get_timer_func = get_timer();
+ let time_seconds: HashMap = HashMap::from([
+ ("date".to_string(), serde_json::to_value("20250501T035408Z").unwrap())
+ ]);
+
+ let result = get_timer_func.call(&time_seconds);
+ assert_eq!(result.is_ok(), true);
+ let result_value = result.unwrap();
+ assert_eq!(result_value, serde_json::to_value("00:00:52").unwrap());
+
+ let time_seconds: HashMap = HashMap::from([
+ ("date".to_string(), serde_json::to_value("20250501T033408Z").unwrap())
+ ]);
+
+ let result = get_timer_func.call(&time_seconds);
+ assert_eq!(result.is_ok(), true);
+ let result_value = result.unwrap();
+ assert_eq!(result_value, serde_json::to_value("00:20:52").unwrap());
+
+ let time_seconds: HashMap = HashMap::from([
+ ("date".to_string(), serde_json::to_value("20250501T023408Z").unwrap())
+ ]);
+
+ let result = get_timer_func.call(&time_seconds);
+ assert_eq!(result.is_ok(), true);
+ let result_value = result.unwrap();
+ assert_eq!(result_value, serde_json::to_value("01:20:00").unwrap());
+}
+
+#[test]
+fn test_get_datetime_iso() {
+ let get_datetime_iso_func = get_datetime_iso();
+ let datetime: HashMap = HashMap::from([
+ ("datetime".to_string(), serde_json::to_value("20250501T035408Z").unwrap())
+ ]);
+ let result = get_datetime_iso_func.call(&datetime);
+ assert_eq!(result.is_ok(), true);
+ let result_value = result.unwrap();
+ assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap());
+
+ let datetime: HashMap = HashMap::from([
+ ("datetime".to_string(), serde_json::to_value("2025-05-01T03:54:08Z").unwrap())
+ ]);
+ let result = get_datetime_iso_func.call(&datetime);
+ assert_eq!(result.is_ok(), true);
+ let result_value = result.unwrap();
+ assert_eq!(result_value, serde_json::to_value("2025-05-01T03:54:08+00:00").unwrap());
+}