Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ shell-words = "1.1.0"
directories = "6.0.0"
toml = "0.8.22"
config = { version = "0.15.11", default-features = false, features = ["toml"] }
linkify = "0.10.0"

[dev-dependencies]
tempfile = "3.19.1"
3 changes: 3 additions & 0 deletions frontend/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module.exports = {
content: [
"templates/*.html",
"src/*.ts",
],
safelist: [
'link'
]
}

6 changes: 3 additions & 3 deletions frontend/templates/desc.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{% macro desc(task) %}
{% if task.annotations %}
<p>
<strong>{{ task.description }}</strong>
<strong>{{ task.description | linkify | safe }}</strong>
</p>
<ul class="pl-4 text-sm">
{% for annotation in task.annotations %}
<li class="list-outside">
[{{date(date=annotation.entry) }}] {{ annotation.description }}
[{{date(date=annotation.entry) }}] {{ annotation.description | linkify | safe }}
</li>
{% endfor %}
</ul>
{% else %}
<p>
{{ task.description }}
{{ task.description | linkify | safe }}
</p>
{% endif %}
{% endmacro desc %}
4 changes: 2 additions & 2 deletions frontend/templates/task_details.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% import "desc.html" as desc %}
<div class="modal-box max-w-3xl" id="task-details-modal-box">
<h2 class="text-lg font-bold text-neutral-content-200">Task: {{ task.description }}</h2>
<h2 class="text-lg font-bold text-neutral-content-200">Task: {{ task.description | linkify | safe }}</h2>
<div class="join mb-3">
<button
class="btn btn-xs btn-warning join-item"
Expand Down Expand Up @@ -267,7 +267,7 @@ <h2 class="text-lg font-bold text-neutral-content-200">Task: {{ task.description
{% for annotation in task.annotations %}
<tr>
<th>{{date(date=annotation.entry) }}</th>
<td>{{ annotation.description }}</td>
<td>{{ annotation.description | linkify | safe }}</td>
{% if annotate_shortcuts %}
<td>
<button
Expand Down
93 changes: 91 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ use std::str::FromStr;
use crate::endpoints::tasks::task_query_builder::{TaskQuery, TaskReport};
use crate::endpoints::tasks::{is_a_tag, is_tag_keyword};
use chrono::{DateTime, TimeDelta};
use linkify::LinkKind;
use rand::distr::{Alphanumeric, SampleString};
use serde::{de, Deserialize, Deserializer, Serialize};
use taskchampion::Uuid;
use tera::Context;
use tracing::warn;
use tera::{escape_html, Context};
use tracing::{trace, warn};

lazy_static::lazy_static! {
pub static ref TEMPLATES: tera::Tera = {
Expand All @@ -30,6 +31,7 @@ lazy_static::lazy_static! {
tera.register_function("obj", obj());
tera.register_function("remove_project_tag", remove_project_from_tag());
tera.register_function("strip_prefix", strip_prefix());
tera.register_filter("linkify", linkify_text());
tera.register_filter("update_unique_tags", update_unique_tags());
tera.register_filter("update_tag_bar_key_comb", update_tag_bar_key_comb());
tera.register_tester("keyword_tag", is_tag_keyword_tests());
Expand Down Expand Up @@ -244,6 +246,42 @@ fn strip_prefix() -> impl tera::Function {
)
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even the links are clickable, they are not differentiable from normal text. Adding link class should do the job

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've added it. As link seems to be used only dynamically it didn't worked at the beginning and therefore i've put it now to the safelist of tailwind.

fn linkify_text() -> impl tera::Filter {
Box::new(
move |value: &tera::Value,
_args: &HashMap<String, tera::Value>|
-> tera::Result<tera::Value> {
let lfy = linkify::LinkFinder::new();
let base_text = tera::from_value::<String>(value.clone())?;
trace!("Need to linkify {}", base_text);
let mut new_text = String::new();
for span in lfy.spans(&base_text) {
let txt = match span.kind() {
Some(link) if *link == LinkKind::Url => {
format!(
"<a class=\"link\" href=\"{}\">{}</a>",
span.as_str(),
span.as_str()
)
}
Some(link) if *link == LinkKind::Email => {
format!(
"<a class=\"link\" href=\"mailto:{}\">{}</a>",
span.as_str(),
span.as_str()
)
}
Some(_) => escape_html(span.as_str()),
None => escape_html(span.as_str()),
};
new_text.push_str(&txt);
}

Ok(tera::to_value(new_text)?)
},
)
}

fn get_project_name_link() -> impl tera::Function {
Box::new(
move |args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
Expand Down Expand Up @@ -455,3 +493,54 @@ fn get_timer() -> impl tera::Function {
},
)
}

#[cfg(test)]
mod tests {

use serde_json::value::Value;
use tera::Filter;

use super::*;

#[test]
fn test_tera_linkify_text() {
let filter = linkify_text();
let value = tera::to_value("This is a test").unwrap();
let args: HashMap<String, Value> = HashMap::new();
let result = filter.filter(&value, &args);
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), tera::to_value("This is a test").unwrap());

let value = tera::to_value("This is very-important-url.tld a test").unwrap();
let result = filter.filter(&value, &args);
assert_eq!(result.is_ok(), true);
assert_eq!(
result.unwrap(),
tera::to_value("This is very-important-url.tld a test").unwrap()
);

let value = tera::to_value("This is https://very-important-url.tld a test").unwrap();
let result = filter.filter(&value, &args);
assert_eq!(result.is_ok(), true);
assert_eq!(
result.unwrap(),
tera::to_value("This is <a class=\"link\" href=\"https://very-important-url.tld\">https://very-important-url.tld</a> a test").unwrap()
);

let value = tera::to_value("This is twk@twk-test.github.com a test").unwrap();
let result = filter.filter(&value, &args);
assert_eq!(result.is_ok(), true);
assert_eq!(
result.unwrap(),
tera::to_value("This is <a class=\"link\" href=\"mailto:twk@twk-test.github.com\">twk@twk-test.github.com</a> a test").unwrap()
);

let value = tera::to_value("This <a href=\"https://very-important-url.tld\">very important</a> is https://very-important-url.tld a test").unwrap();
let result = filter.filter(&value, &args);
assert_eq!(result.is_ok(), true);
assert_eq!(
result.unwrap(),
tera::to_value("This &lt;a href=&quot;<a class=\"link\" href=\"https://very-important-url.tld\">https://very-important-url.tld</a>&quot;&gt;very important&lt;&#x2F;a&gt; is <a class=\"link\" href=\"https://very-important-url.tld\">https://very-important-url.tld</a> a test").unwrap()
);
}
}