1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//
// shortcode module
//

use tera::{Result, Function};
use std::collections::HashMap;

const ROBOTS_TXT: &'static str = "Link for Robots (No JavaScript)";

pub struct Shortcodes {
    pub functions: HashMap<String, fn(&HashMap<String, tera::Value>) -> String>,
}

impl Shortcodes {

    pub fn register(mut self,
        display: &str,
        shortcode_fn: fn(&HashMap<String, tera::Value>) -> String,
    ) -> Self {
        self.functions.insert(display.to_owned(), shortcode_fn);
        self
    }

    pub fn new() -> Self {
        Shortcodes {
            functions: HashMap::new(),
        }
    }
}

impl Function for Shortcodes {

    fn call(&self,
        args: &HashMap<String, tera::Value>,
    ) -> Result<tera::Value> {

        let display = match args.get("display") {
            Some(value) => value.as_str()
                .unwrap()
                .trim_matches(|c| c == '"' || c == '\''),
            None => return Ok(tera::Value::String("Missing display attribute".to_owned())),
        };

        let fragment = match self.functions.get(display) {
            Some(shortcode_fn) => shortcode_fn(args),
            None => {
                return Ok(tera::Value::String(format!("Unknown shortcode display name: {}", display)))
            },
        };

        Ok(tera::Value::String(fragment))

    }
}

pub fn fetch_shortcode_js(
    url: &str,
    method: Option<&str>,
    json_body: Option<&str>,
) -> String {

    let method = method.unwrap_or("GET");
    let json_body = json_body.unwrap_or("{}");

    let fetch_js = match method.to_lowercase().as_str() {
        "get" => format!(r#"const response = await fetch("{}");"#, url),
        "post" => format!(r#"
const request = new Request("{}", {{
    headers: (() => {{
        const myHeaders = new Headers();
        myHeaders.append("Content-Type", "application/json");
        return myHeaders;
    }})(),
    method: "POST",
    body: JSON.stringify({}),
}});
const response = await fetch(request);"#, url, json_body),
        _ => panic!("Invalid method: {}", method),
    };

    let js_code = format!(r#"<script>
(function () {{
    async function fetchShortcodeData() {{
        try {{
            {}
            if (!response.ok) {{
                throw new Error(`HTTP error! Status: ${{response.status}}`);
            }}
            return await response.text();
        }} catch (error) {{
            console.error("Fetch failed:", error);
            return "";
        }}
    }}
    (async () => {{
        const currentScript = document.currentScript;
        const content = await fetchShortcodeData();
        // console.log(content);
        currentScript.insertAdjacentHTML('beforebegin', content);
        currentScript.remove();
    }})();
}})();
</script>"#,
    fetch_js);

    if method.to_lowercase().as_str() == "get" {
        js_code.to_string() + &format!(r#"<noscript><a href="{}">{}</a></noscript>"#, url, ROBOTS_TXT)
    } else {
        js_code
    }
}

// Auxiliary function to transfer data to route
pub fn fetch_shortcode(
    url: &str,
    method: Option<&str>,
    json_body: Option<&str>,
) -> String {

    let method = method.unwrap_or("GET");
    let json_body = json_body.unwrap_or("{}");

    let client = reqwest::Client::new();

    let data_to_route = async {
        let response = match method.to_lowercase().as_str() {
            "get" => client.get(url)
                .send()
                .await,
            "post" => client.post(url)
                .header("Content-Type", "application/json")
                .body(json_body.to_owned())
                .send()
                .await,
                _ => return format!("Invalid method: {}", method),
            };

        match response {
            Ok(res) => {
                if res.status().is_success() {
                    res.text().await.unwrap_or_else(|_| "Failed to read response body".into())
                } else {
                    format!("Request failed with status: {}", res.status())
                }
            }
            Err(e) => format!("Request error: {}", e),
        }
    };

    // Use `block_in_place` to run the async function
    // within the blocking context
    tokio::task::block_in_place(||
        // We need to access the current runtime to
        // run the async function
        tokio::runtime::Handle::current()
            .block_on(data_to_route)
    )
}