Sfoglia il codice sorgente

js added back to emails. Folder endpoints fix

Yurii Sokolovskyi 3 mesi fa
parent
commit
807ae5da81
2 ha cambiato i file con 325 aggiunte e 39 eliminazioni
  1. 231 0
      src/js.rs
  2. 94 39
      src/main.rs

+ 231 - 0
src/js.rs

@@ -0,0 +1,231 @@
+pub fn email_scripts() -> String {
+    r#"
+<script>
+        let numberingVisible = true;
+        let blankLinesVisible = false;
+
+        function applyNumberingVisibility() {{
+            const purplenumbers = document.querySelectorAll('.purplenumber');
+            purplenumbers.forEach(element => {{
+                element.classList.toggle('transparent', !numberingVisible);
+            }});
+        }}
+
+        function applyBlankLinesVisibility() {{
+            const content = document.getElementById('content');
+
+            if (blankLinesVisible) {{
+                if (window.originalBrElements) {{
+                    window.originalBrElements.forEach(br => {{
+                        const parent = br.parent;
+                        if (parent) {{
+                            parent.insertBefore(br.element, parent.children[br.index]);
+                        }}
+                    }});
+                    window.originalBrElements = [];
+                }}
+
+                if (window.originalStyles) {{
+                    window.originalStyles.forEach(item => {{
+                        item.element.style.marginTop = item.styles.marginTop;
+                        item.element.style.marginBottom = item.styles.marginBottom;
+                    }});
+                    window.originalStyles = [];
+                }}
+            }} else {{
+                const brElements = content.querySelectorAll('br');
+                window.originalBrElements = [];
+                brElements.forEach(br => {{
+                    const parent = br.parentNode;
+                    const index = Array.prototype.indexOf.call(parent.children, br);
+                    window.originalBrElements.push({{element: br, parent, index}});
+                    parent.removeChild(br);
+                }});
+
+                const allElements = document.querySelectorAll('*');
+                window.originalStyles = [];
+                allElements.forEach(element => {{
+                    window.originalStyles.push({{
+                        element: element,
+                        styles: {{
+                            marginTop: element.style.marginTop,
+                            marginBottom: element.style.marginBottom
+                        }}
+                    }});
+                    element.style.marginTop = '0';
+                    element.style.marginBottom = '0';
+                }});
+            }}
+        }}
+
+        function showElement(body_id, summary_id) {{
+            const summaries = document.querySelectorAll('.message-meta');
+            const innerElements = document.querySelectorAll('.email-body');
+            const zoom_ins = document.querySelectorAll('.zoom_in');
+            const zoom_outs = document.querySelectorAll('.zoom_out');
+
+            summaries.forEach(summary => {{
+                summary.style.display = 'none';
+            }});
+            innerElements.forEach(element => {{
+                element.style.display = 'none';
+            }});
+
+            zoom_ins.forEach(element => {{
+                element.style.display = 'none';
+            }});
+
+            zoom_outs.forEach(element => {{
+                element.style.display = 'block';
+            }});
+
+            document.getElementById(body_id).style.display = 'block';
+            document.getElementById(summary_id).style.display = 'block';
+
+            window.scrollTo(0, 0);
+        }}
+
+        function showSummaries() {{
+            const summaries = document.querySelectorAll('.message-meta');
+            const innerElements = document.querySelectorAll('.email-body');
+            const zoom_ins = document.querySelectorAll('.zoom_in');
+            const zoom_outs = document.querySelectorAll('.zoom_out');
+
+            summaries.forEach(summary => {{
+                summary.style.display = 'block';
+            }});
+
+            innerElements.forEach(element => {{
+                element.style.display = 'none';
+            }});
+
+            zoom_ins.forEach(element => {{
+                element.style.display = 'block';
+            }});
+
+            zoom_outs.forEach(element => {{
+                element.style.display = 'none';
+            }});
+
+            window.scrollTo(0, 0);
+        }}
+
+        function jumpToItem() {{
+            const id = document.getElementById('textfield').value;
+            if (id) {{
+                window.location.href = '#' + id;
+            }}
+        }}
+
+
+
+        function openNewWindow() {{
+            const newWindow = window.open('', '', 'width=250,height=200');
+            const popupContent = `
+                <!DOCTYPE html>
+                <html>
+                <head>
+                    <title>Viewspecs</title>
+                    <style>
+                        .popup-content {{
+                            font-family: Arial, sans-serif;
+                            font-size: 14px;
+                            margin: 20px;
+                        }}
+                        .popup-content fieldset {{
+                            border: 1px solid #000;
+                            padding: 10px;
+                        }}
+                        .popup-content legend {{
+                            padding: 0 10px;
+                            font-weight: bold;
+                        }}
+                        .popup-content label {{
+                            display: block;
+                            margin-bottom: 5px;
+                        }}
+                        .popup-content input {{
+                            margin-right: 5px;
+                        }}
+                        .accept-button {{
+                            margin-top: 10px;
+                            padding: 5px 10px;
+                            font-size: 14px;
+                            cursor: pointer;
+                        }}
+                    </style>
+                </head>
+                <body>
+                    <div class="popup-content">
+                        <fieldset>
+                            <legend>Show</legend>
+                            <label> Blank lines (y/z)</label>
+                            <label> Numbering (m/n)</label>
+                        </fieldset>
+                        <button class="accept-button" onclick="window.close()">Accept</button>
+                    </div>
+                </body>
+                </html>
+            `;
+            newWindow.document.open();
+            newWindow.document.write(popupContent);
+            newWindow.document.close();
+        }}
+
+        document.addEventListener('DOMContentLoaded', function() {{
+            const textfield = document.getElementById('textfield');
+            textfield.addEventListener('keydown', function(event) {{
+                if (event.key === 'Enter') {{
+                    jumpToItem();
+                }}
+            }});
+
+            const jumpButton = document.querySelector('input[type="button"][value="Jump Item:"]');
+            jumpButton.addEventListener('click', jumpToItem);
+
+            // Apply stored states on load
+            blankLinesVisible = JSON.parse(localStorage.getItem('blankLinesVisible') || 'true');
+            numberingVisible = JSON.parse(localStorage.getItem('numberingVisible') || 'true');
+            applyBlankLinesVisibility();
+            applyNumberingVisibility();
+
+            document.getElementById('numbering').addEventListener('change', function() {{
+                numberingVisible = this.checked;
+                applyNumberingVisibility();
+            }});
+
+            document.getElementById('blankLines').addEventListener('change', function() {{
+                blankLinesVisible = this.checked;
+                applyBlankLinesVisibility();
+            }});
+        }});
+
+        // Keyboard shortcuts
+        document.addEventListener('keydown', function(event) {{
+            const numberingCheckbox = document.getElementById('numbering');
+            const blankLinesCheckbox = document.getElementById('blankLines');
+
+            if (event.key === 'm') {{
+                numberingVisible = true;
+                if (numberingCheckbox) numberingCheckbox.checked = true;
+                applyNumberingVisibility();
+            }}
+            if (event.key === 'n') {{
+                numberingVisible = false;
+                if (numberingCheckbox) numberingCheckbox.checked = false;
+                applyNumberingVisibility();
+            }}
+            if (event.key === 'y') {{
+                blankLinesVisible = true;
+                if (blankLinesCheckbox) blankLinesCheckbox.checked = true;
+                applyBlankLinesVisibility();
+            }}
+            if (event.key === 'z') {{
+                blankLinesVisible = false;
+                if (blankLinesCheckbox) blankLinesCheckbox.checked = false;
+                applyBlankLinesVisibility();
+            }}
+        }});
+    </script>
+    "#.to_string()
+}

+ 94 - 39
src/main.rs

@@ -6,6 +6,7 @@ use models::*;
 use std::ffi::{OsStr, OsString};
 use std::{fs, thread};
 use std::fs::{create_dir_all, File, OpenOptions};
+use std::future::Future;
 use std::io::{BufReader, Write};
 use std::net::SocketAddr;
 use std::time::Duration;
@@ -18,6 +19,8 @@ use mailparse::parse_mail;
 use regex::Regex;
 use crate::templates::util::parse_email;
 use crate::util::{compress_and_save_file, read_and_decompress_file};
+use crate::imap::{delete_folder, rename_folder, create_folder};
+use crate::js::email_scripts;
 
 #[cfg(not(target_os = "wasi"))]
 use axum::extract::Query;
@@ -29,11 +32,9 @@ use axum::http::{Method, StatusCode};
 use axum::response::{IntoResponse, Response};
 #[cfg(not(target_os = "wasi"))]
 use axum::routing::{get, post};
+use axum::routing::any;
 #[cfg(not(target_os = "wasi"))]
 use tower_http::cors::{Any, CorsLayer};
-#[cfg(not(target_os = "wasi"))]
-use imap::create_folder;
-
 
 #[cfg(target_os = "wasi")]
 use bytecodec::DecodeExt;
@@ -44,7 +45,6 @@ use lettre::message::{Mailbox, Message as LettreMessage, MultiPart, SinglePart};
 use tokio::runtime::Runtime;
 #[cfg(target_os = "wasi")]
 use wasmedge_wasi_socket::{Shutdown, TcpListener, TcpStream};
-use crate::imap::{delete_folder, rename_folder};
 #[cfg(target_os = "wasi")]
 use crate::server_wasi::{handle_client};
 #[cfg(target_os = "wasi")]
@@ -61,6 +61,7 @@ mod indexes;
 mod imap;
 mod server;
 mod server_wasi;
+mod js;
 
 pub fn append_ext(ext: impl AsRef<OsStr>, path: &PathBuf) -> PathBuf {
     let mut os_string: OsString = path.into();
@@ -136,7 +137,8 @@ pub fn message_to_html(msg: StrMessage) -> String{
                     <title>Email</title>
                     <meta http-equiv='Permissions-Policy' content='interest-cohort=()'/>
                     <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0' />
-                </head>      
+                </head>   
+                {js}   
                 <body>         
                     <div id="email-body" class="email-body" style="position: relative;">
                         <style>
@@ -149,6 +151,7 @@ pub fn message_to_html(msg: StrMessage) -> String{
                 </body>
                 </html>
             "#,
+        js = email_scripts(),
         styles = styles,
         body_content = body_content,
     )
@@ -225,6 +228,89 @@ pub fn add_email(path: PathBuf, uid: u32) -> anyhow::Result<()>{
     Ok(())
 }
 
+async fn delete_email(message_id: String) -> anyhow::Result<()>{
+    let message = Indexes::delete_from_messages(message_id);
+    match message {
+        Ok(message) => {
+            let _ = Indexes::delete_from_threads(message.clone());
+            let lists: Vec<String> = fs::read_dir(Config::global().out_dir.clone())?
+                .filter_map(Result::ok)
+                .filter(|entry| entry.metadata().map(|m| m.is_dir()).unwrap_or(false))
+                .filter_map(|entry| entry.file_name().into_string().ok())
+                .collect();
+            let _ = Indexes::persist_indexes(lists);
+
+            let message_html_name = Config::global().out_dir.clone()
+                .join(message.list.clone())
+                .join("messages")
+                .join(message.hash.clone() + ".html");
+            let message_xml_name = Config::global().out_dir.clone()
+                .join(message.list.clone())
+                .join("messages_xml")
+                .join(message.hash.clone() + ".xml");
+            let _ = fs::remove_file(message_html_name);
+            let _ = fs::remove_file(message_xml_name);
+
+            let _ = imap::delete_email_by_uid(message.list.clone(), message.uid.clone()).await;
+        }
+        Err(_) => {}
+    }
+
+    Ok(())
+}
+
+// create folder both locally and remotely
+pub async fn create_folder_lar(name: String) -> anyhow::Result<()>{
+    match create_folder(name.clone()).await {
+        Ok(_) => {
+            let folder_path = Config::global().out_dir.clone().join(name.clone());
+            if !folder_path.exists() {
+                return match fs::create_dir_all(folder_path) {
+                    Ok(_) => Indexes::persist_indexes(vec![name.clone()]),
+                    Err(_) => Err(anyhow!("Cannot create folder locally")),
+                }
+            }
+            Ok(())
+        }
+        Err(_) => Err(anyhow!("Cannot create folder remotely"))
+    }
+}
+
+// delete folder both locally and remotely
+pub async fn delete_folder_lar(name: String) -> anyhow::Result<()>{
+    match delete_folder(name.clone()).await {
+        Ok(_) => {
+            let folder_path = Config::global().out_dir.clone().join(name);
+            if folder_path.exists() {
+                return match fs::remove_dir_all(folder_path) {
+                    Ok(_) => Ok(()),
+                    Err(_) => Err(anyhow!("Cannot delete folder locally")),
+                }
+            }
+            Ok(())
+        }
+        Err(_) => Err(anyhow!("Cannot delete folder remotely"))
+    }
+}
+
+// rename folder both locally and remotely
+pub async fn rename_folder_lar(old_name: String, new_name: String) -> anyhow::Result<()>{
+    match rename_folder(old_name.clone(), new_name.clone()).await {
+        Ok(_) => {
+            let old_path = Config::global().out_dir.clone().join(old_name);
+            let new_path = Config::global().out_dir.clone().join(new_name);
+            if old_path.exists() {
+                return match fs::rename(old_path, new_path) {
+                    Ok(_) => Ok(()),
+                    Err(_) => Err(anyhow!("Cannot rename folder locally"))
+                }
+            }
+            Ok(())
+        }
+        Err(_) => Err(anyhow!("Cannot rename folder remotely"))
+    }
+}
+
 fn parse_args(){
     let args = arg::Args::from_env();
     
@@ -286,37 +372,6 @@ async fn run(){
     }
 }
 
-async fn delete_email(message_id: String) -> anyhow::Result<()>{
-    let message = Indexes::delete_from_messages(message_id);
-    match message {
-        Ok(message) => {
-            let _ = Indexes::delete_from_threads(message.clone());
-            let lists: Vec<String> = fs::read_dir(Config::global().out_dir.clone())?
-                .filter_map(Result::ok)
-                .filter(|entry| entry.metadata().map(|m| m.is_dir()).unwrap_or(false))
-                .filter_map(|entry| entry.file_name().into_string().ok())
-                .collect();
-            let _ = Indexes::persist_indexes(lists);
-
-            let message_html_name = Config::global().out_dir.clone()
-                .join(message.list.clone())
-                .join("messages")
-                .join(message.hash.clone() + ".html");
-            let message_xml_name = Config::global().out_dir.clone()
-                .join(message.list.clone())
-                .join("messages_xml")
-                .join(message.hash.clone() + ".xml");
-            let _ = fs::remove_file(message_html_name);
-            let _ = fs::remove_file(message_xml_name);
-                
-            let _ = imap::delete_email_by_uid(message.list.clone(), message.uid.clone()).await;
-        }
-        Err(_) => {}
-    }
-    
-    Ok(())
-}
-
 #[cfg(target_os = "wasi")]
 #[tokio::main(flavor = "current_thread")]
 async fn main() -> anyhow::Result<()> {
@@ -489,7 +544,7 @@ struct CreateFolderRequest {
 }
 #[cfg(not(target_os = "wasi"))]
 async fn create_folder_handle(Json(data): Json<CreateFolderRequest>) -> impl IntoResponse {
-    match create_folder(data.name.clone()).await {
+    match create_folder_lar(data.name.clone()).await {
         Ok(_) => {
             (StatusCode::OK, "OK").into_response()
         }
@@ -509,7 +564,7 @@ struct DeleteFolderRequest {
 }
 #[cfg(not(target_os = "wasi"))]
 async fn delete_folder_handle(Json(data): Json<DeleteFolderRequest>) -> impl IntoResponse {
-    match delete_folder(data.name.clone()).await {
+    match delete_folder_lar(data.name.clone()).await {
         Ok(_) => {
             (StatusCode::OK, "OK").into_response()
         }
@@ -530,7 +585,7 @@ struct RenameFolderRequest {
 }
 #[cfg(not(target_os = "wasi"))]
 async fn rename_folder_handle(Json(data): Json<RenameFolderRequest>) -> impl IntoResponse {
-    match rename_folder(data.old_name.clone(), data.new_name.clone()).await {
+    match rename_folder_lar(data.old_name.clone(), data.new_name.clone()).await {
         Ok(_) => {
             (StatusCode::OK, "OK").into_response()
         }