mirror of
https://github.com/svenstaro/miniserve
synced 2024-07-08 20:05:56 +00:00
Renders file listing using maud
This commit is contained in:
parent
f7b0d5bd03
commit
984823e6ef
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -655,6 +655,11 @@ name = "linked-hash-map"
|
|||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "literalext"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.1.5"
|
||||
|
@ -685,6 +690,31 @@ name = "matches"
|
|||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "maud"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"actix-web 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"maud_htmlescape 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"maud_macros 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maud_htmlescape"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "maud_macros"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"maud_htmlescape 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.1.3"
|
||||
|
@ -731,6 +761,7 @@ dependencies = [
|
|||
"chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"maud 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nanoid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1877,10 +1908,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
|
||||
"checksum libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)" = "e962c7641008ac010fa60a7dfdc1712449f29c44ef2d4702394aea943ee75047"
|
||||
"checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939"
|
||||
"checksum literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f42dd699527975a1e0d722e0707998671188a0125f2051d2d192fc201184a81"
|
||||
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
|
||||
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
|
||||
"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21"
|
||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
"checksum maud 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "337b4b2512ff8809450badd92cf3b529dc6108e333dfa1626971412f8de5793b"
|
||||
"checksum maud_htmlescape 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0fb85bccffc42302ad1e1ed8679f6a39d1317f775a37fbc3f79bdfbe054bfb7"
|
||||
"checksum maud_macros 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f58751cda7f79eedc668ce60e5bcd88dca49e412ec37545a792e2c399fbca41"
|
||||
"checksum memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e1dd4eaac298c32ce07eb6ed9242eda7d82955b9170b7d6db59b2e02cc63fcb8"
|
||||
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
|
||||
"checksum mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425"
|
||||
|
|
|
@ -31,4 +31,5 @@ nanoid = "0.2.0"
|
|||
alphanumeric-sort = "1.0.6"
|
||||
structopt = "0.2.14"
|
||||
chrono = "0.4.6"
|
||||
chrono-humanize = "0.0.11"
|
||||
chrono-humanize = "0.0.11"
|
||||
maud = { version = "0.20.0", features = ["actix-web"] }
|
|
@ -89,7 +89,7 @@ Sometimes this is just a more practical and quick way than doing things properly
|
|||
|
||||
miniserve-win.exe
|
||||
|
||||
**With Cargo**: If you have a somewhat recent version of Rust and Cargo installed, you can run
|
||||
**With Cargo**: You will need the _nightly_ version of Rust to compile the project. Then you can run
|
||||
|
||||
cargo install miniserve
|
||||
miniserve
|
||||
|
|
238
src/listing.rs
238
src/listing.rs
|
@ -1,16 +1,15 @@
|
|||
use actix_web::{fs, HttpRequest, HttpResponse, Result};
|
||||
use bytesize::ByteSize;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use chrono_humanize::{Accuracy, HumanTime, Tense};
|
||||
use clap::{_clap_count_exprs, arg_enum};
|
||||
use htmlescape::encode_minimal as escape_html_entity;
|
||||
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::renderer;
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
/// Available sorting methods
|
||||
|
@ -35,7 +34,7 @@ arg_enum! {
|
|||
|
||||
#[derive(PartialEq)]
|
||||
/// Possible entry types
|
||||
enum EntryType {
|
||||
pub enum EntryType {
|
||||
/// Entry is a directory
|
||||
Directory,
|
||||
|
||||
|
@ -54,21 +53,21 @@ impl PartialOrd for EntryType {
|
|||
}
|
||||
|
||||
/// Entry
|
||||
struct Entry {
|
||||
pub struct Entry {
|
||||
/// Name of the entry
|
||||
name: String,
|
||||
pub name: String,
|
||||
|
||||
/// Type of the entry
|
||||
entry_type: EntryType,
|
||||
pub entry_type: EntryType,
|
||||
|
||||
/// URL of the entry
|
||||
link: String,
|
||||
pub link: String,
|
||||
|
||||
/// Size in byte of the entry. Only available for EntryType::File
|
||||
size: Option<bytesize::ByteSize>,
|
||||
pub size: Option<bytesize::ByteSize>,
|
||||
|
||||
/// Last modification date
|
||||
last_modification_date: Option<SystemTime>,
|
||||
pub last_modification_date: Option<SystemTime>,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
|
@ -87,6 +86,10 @@ impl Entry {
|
|||
last_modification_date,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.entry_type == EntryType::Directory
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_handler(req: &HttpRequest<crate::MiniserveConfig>) -> Result<fs::NamedFile> {
|
||||
|
@ -104,20 +107,11 @@ pub fn directory_listing<S>(
|
|||
sort_method: SortingMethods,
|
||||
reverse_sort: bool,
|
||||
) -> Result<HttpResponse, io::Error> {
|
||||
let index_of = format!("Index of {}", req.path());
|
||||
let mut body = String::new();
|
||||
let title = format!("Index of {}", req.path());
|
||||
let base = Path::new(req.path());
|
||||
let random_route = format!("/{}", random_route.unwrap_or_default());
|
||||
|
||||
if let Some(parent) = base.parent() {
|
||||
if req.path() != random_route {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr><td><a class=\"root\" href=\"{}\">..</a></td><td></td></tr>",
|
||||
parent.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
let is_root = base.parent().is_none() || req.path() == random_route;
|
||||
let page_parent = base.parent().map(|p| p.display().to_string());
|
||||
|
||||
let mut entries: Vec<Entry> = Vec::new();
|
||||
|
||||
|
@ -190,206 +184,8 @@ pub fn directory_listing<S>(
|
|||
if reverse_sort {
|
||||
entries.reverse();
|
||||
}
|
||||
for entry in entries {
|
||||
let (modification_date, modification_time) = convert_to_utc(entry.last_modification_date);
|
||||
|
||||
match entry.entry_type {
|
||||
EntryType::Directory => {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr>\
|
||||
<td>\
|
||||
<a class=\"directory\" href=\"{}\">{}/</a>\
|
||||
<span class=\"mobile-info\">\
|
||||
<strong>Last modification:</strong> {} {}\
|
||||
</span>\
|
||||
</td>\
|
||||
<td></td>\
|
||||
<td class=\"date-cell\">\
|
||||
<span>{}</span>\
|
||||
<span>{}</span>\
|
||||
<span>{}</span>\
|
||||
</td>\
|
||||
</tr>",
|
||||
entry.link,
|
||||
entry.name,
|
||||
modification_date,
|
||||
modification_time,
|
||||
modification_date,
|
||||
modification_time,
|
||||
humanize_systemtime(entry.last_modification_date)
|
||||
);
|
||||
}
|
||||
EntryType::File => {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr>\
|
||||
<td>\
|
||||
<a class=\"file\" href=\"{}\">{}</a>\
|
||||
<span class=\"mobile-info\">\
|
||||
<strong>Size:</strong> {}\
|
||||
</span>\
|
||||
<span class=\"mobile-info\">\
|
||||
<strong>Last modification:</strong> {} {} <span class=\"history\">({})</span>\
|
||||
</span>\
|
||||
</td>\
|
||||
<td>\
|
||||
{}\
|
||||
</td>\
|
||||
<td class=\"date-cell\">\
|
||||
<span>{}</span>\
|
||||
<span>{}</span>\
|
||||
<span>{}</span>\
|
||||
</td>\
|
||||
</tr>",
|
||||
entry.link,
|
||||
entry.name,
|
||||
entry.size.unwrap(),
|
||||
modification_date,
|
||||
modification_time,
|
||||
humanize_systemtime(entry.last_modification_date),
|
||||
entry.size.unwrap(),
|
||||
modification_date,
|
||||
modification_time,
|
||||
humanize_systemtime(entry.last_modification_date)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let html = format!(
|
||||
"<html>\
|
||||
<head>\
|
||||
<title>{}</title>\
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
|
||||
<style>\
|
||||
body {{\
|
||||
margin: 0;\
|
||||
font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto,\"Helvetica Neue\", Helvetica, Arial, sans-serif;\
|
||||
font-weight: 300;\
|
||||
color: #444444;\
|
||||
padding: 0.125rem;\
|
||||
}}\
|
||||
table {{\
|
||||
width: 100%;\
|
||||
background: white;\
|
||||
border: 0;\
|
||||
table-layout: auto;\
|
||||
}}\
|
||||
table thead {{\
|
||||
background: #efefef;\
|
||||
}}\
|
||||
table tr th,\
|
||||
table tr td {{\
|
||||
padding: 0.5625rem 0.625rem;\
|
||||
font-size: 0.875rem;\
|
||||
color: #777c82;\
|
||||
text-align: left;\
|
||||
line-height: 1.125rem;\
|
||||
width: 33.333%;\
|
||||
}}\
|
||||
table thead tr th {{\
|
||||
padding: 0.5rem 0.625rem 0.625rem;\
|
||||
font-weight: bold;\
|
||||
color: #444444;\
|
||||
}}\
|
||||
table tr:nth-child(even) {{\
|
||||
background: #f6f6f6;\
|
||||
}}\
|
||||
a {{\
|
||||
text-decoration: none;\
|
||||
color: #3498db;\
|
||||
}}\
|
||||
a.root, a.root:visited {{\
|
||||
font-weight: bold;\
|
||||
color: #777c82;\
|
||||
}}\
|
||||
a.directory {{\
|
||||
font-weight: bold;\
|
||||
}}\
|
||||
a:hover {{\
|
||||
text-decoration: underline;\
|
||||
}}\
|
||||
a:visited {{\
|
||||
color: #8e44ad;\
|
||||
}}\
|
||||
td.date-cell {{\
|
||||
display: flex;\
|
||||
width: calc(100% - 1.25rem);\
|
||||
}}\
|
||||
td.date-cell span:first-of-type,\
|
||||
td.date-cell span:nth-of-type(2) {{\
|
||||
flex-basis:4.5rem;\
|
||||
}}\
|
||||
td.date-cell span:nth-of-type(3), .history {{\
|
||||
color: #c5c5c5;\
|
||||
}}\
|
||||
.file, .directory {{\
|
||||
display: block;\
|
||||
}}\
|
||||
.mobile-info {{\
|
||||
display: none;\
|
||||
}}\
|
||||
@media (max-width: 600px) {{\
|
||||
h1 {{\
|
||||
font-size: 1.375em;\
|
||||
}}\
|
||||
td:not(:nth-child(1)), th:not(:nth-child(1)){{\
|
||||
display: none;\
|
||||
}}\
|
||||
.mobile-info {{\
|
||||
display: block;\
|
||||
}}\
|
||||
.file, .directory{{\
|
||||
padding-bottom: 0.5rem;\
|
||||
}}\
|
||||
}}\
|
||||
@media (max-width: 400px) {{\
|
||||
h1 {{\
|
||||
font-size: 1.375em;\
|
||||
}}\
|
||||
}}\
|
||||
</style>\
|
||||
</head>\
|
||||
<body><h1>{}</h1>\
|
||||
<table>\
|
||||
<thead><th>Name</th><th>Size</th><th>Last modification</th></thead>\
|
||||
<tbody>\
|
||||
{}\
|
||||
</tbody></table></body>\n</html>",
|
||||
index_of, index_of, body
|
||||
);
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(html))
|
||||
}
|
||||
|
||||
/// Converts a SystemTime object to a strings tuple (date, time)
|
||||
/// Date is formatted as %e %b, e.g. Jul 12
|
||||
/// Time is formatted as %R, e.g. 22:34
|
||||
///
|
||||
/// If no SystemTime was given, returns a tuple containing empty strings
|
||||
fn convert_to_utc(src_time: Option<SystemTime>) -> (String, String) {
|
||||
src_time
|
||||
.map(|time| DateTime::<Utc>::from(time))
|
||||
.map(|date_time| {
|
||||
(
|
||||
date_time.format("%e %b").to_string(),
|
||||
date_time.format("%R").to_string(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Converts a SystemTime to a string readable by a human,
|
||||
/// i.e. calculates the duration between now() and the given SystemTime,
|
||||
/// and gives a rough approximation of the elapsed time since
|
||||
///
|
||||
/// If no SystemTime was given, returns an empty string
|
||||
fn humanize_systemtime(src_time: Option<SystemTime>) -> String {
|
||||
src_time
|
||||
.and_then(|std_time| SystemTime::now().duration_since(std_time).ok())
|
||||
.and_then(|from_now| Duration::from_std(from_now).ok())
|
||||
.map(|duration| HumanTime::from(duration).to_text_en(Accuracy::Rough, Tense::Past))
|
||||
.unwrap_or_default()
|
||||
.body(renderer::page(&title, entries, is_root, page_parent).into_string()))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(proc_macro_hygiene)]
|
||||
|
||||
use actix_web::{fs, middleware, server, App};
|
||||
use clap::crate_version;
|
||||
use simplelog::{Config, LevelFilter, TermLogger};
|
||||
|
@ -10,6 +12,7 @@ use yansi::{Color, Paint};
|
|||
mod args;
|
||||
mod auth;
|
||||
mod listing;
|
||||
mod renderer;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Configuration of the Miniserve application
|
||||
|
|
230
src/renderer.rs
Normal file
230
src/renderer.rs
Normal file
|
@ -0,0 +1,230 @@
|
|||
use chrono::{DateTime, Duration, Utc};
|
||||
use chrono_humanize::{Accuracy, HumanTime, Tense};
|
||||
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::listing;
|
||||
|
||||
/// Renders the file listing
|
||||
pub fn page(
|
||||
page_title: &str,
|
||||
entries: Vec<listing::Entry>,
|
||||
is_root: bool,
|
||||
page_parent: Option<String>,
|
||||
) -> Markup {
|
||||
html! {
|
||||
(page_header(page_title))
|
||||
body {
|
||||
h1 { (page_title) }
|
||||
table {
|
||||
thead {
|
||||
th { "Name" }
|
||||
th { "Size" }
|
||||
th { "Last modification" }
|
||||
}
|
||||
tbody {
|
||||
@if !is_root {
|
||||
@if let Some(parent) = page_parent {
|
||||
tr {
|
||||
td {
|
||||
a.root href=(parent) {
|
||||
".."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@for entry in entries {
|
||||
(entry_row(entry))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Partial: page header
|
||||
fn page_header(page_title: &str) -> Markup {
|
||||
html! {
|
||||
(DOCTYPE)
|
||||
html {
|
||||
meta charset="utf-8";
|
||||
meta http-equiv="X-UA-Compatible" content="IE=edge";
|
||||
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||
title { (page_title) }
|
||||
style { (css()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Partial: row for an entry
|
||||
fn entry_row(entry: listing::Entry) -> Markup {
|
||||
html! {
|
||||
@let (modification_date, modification_time) = convert_to_utc(entry.last_modification_date);
|
||||
@let last_modification_timer = humanize_systemtime(entry.last_modification_date);
|
||||
tr {
|
||||
td {
|
||||
@if entry.is_dir() {
|
||||
a.directory href=(entry.link) {
|
||||
(entry.name) "/"
|
||||
}
|
||||
} @else {
|
||||
a.file href=(entry.link) {
|
||||
(entry.name)
|
||||
}
|
||||
}
|
||||
@if !entry.is_dir() {
|
||||
@if let Some(size) = entry.size {
|
||||
span .mobile-info {
|
||||
strong { "Size: " }
|
||||
(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
span .mobile-info {
|
||||
strong { "Last modification: " }
|
||||
(modification_date) " "
|
||||
(modification_time) " "
|
||||
span .history { "(" (last_modification_timer) ")" }
|
||||
}
|
||||
}
|
||||
td {
|
||||
@if let Some(size) = entry.size {
|
||||
(size)
|
||||
}
|
||||
}
|
||||
td.date-cell {
|
||||
span {
|
||||
(modification_date)
|
||||
}
|
||||
span {
|
||||
(modification_time)
|
||||
}
|
||||
span {
|
||||
"(" (last_modification_timer) ")"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Partial: CSS
|
||||
fn css() -> Markup {
|
||||
(PreEscaped(r#"
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,"Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
color: #444444;
|
||||
padding: 0.125rem;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
background: white;
|
||||
border: 0;
|
||||
table-layout: auto;
|
||||
}
|
||||
table thead {
|
||||
background: #efefef;
|
||||
}
|
||||
table tr th,
|
||||
table tr td {
|
||||
padding: 0.5625rem 0.625rem;
|
||||
font-size: 0.875rem;
|
||||
color: #777c82;
|
||||
text-align: left;
|
||||
line-height: 1.125rem;
|
||||
width: 33.333%;
|
||||
}
|
||||
table thead tr th {
|
||||
padding: 0.5rem 0.625rem 0.625rem;
|
||||
font-weight: bold;
|
||||
color: #444444;
|
||||
}
|
||||
table tr:nth-child(even) {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #3498db;
|
||||
}
|
||||
a.root, a.root:visited {
|
||||
font-weight: bold;
|
||||
color: #777c82;
|
||||
}
|
||||
a.directory {
|
||||
font-weight: bold;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:visited {
|
||||
color: #8e44ad;
|
||||
}
|
||||
td.date-cell {
|
||||
display: flex;
|
||||
width: calc(100% - 1.25rem);
|
||||
}
|
||||
td.date-cell span:first-of-type,
|
||||
td.date-cell span:nth-of-type(2) {
|
||||
flex-basis:4.5rem;
|
||||
}
|
||||
td.date-cell span:nth-of-type(3), .history {
|
||||
color: #c5c5c5;
|
||||
}
|
||||
.file, .directory {
|
||||
display: block;
|
||||
}
|
||||
.mobile-info {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
h1 {
|
||||
font-size: 1.375em;
|
||||
}
|
||||
td:not(:nth-child(1)), th:not(:nth-child(1)){
|
||||
display: none;
|
||||
}
|
||||
.mobile-info {
|
||||
display: block;
|
||||
}
|
||||
.file, .directory{
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
h1 {
|
||||
font-size: 1.375em;
|
||||
}
|
||||
}"#.to_string()))
|
||||
}
|
||||
|
||||
/// Converts a SystemTime object to a strings tuple (date, time)
|
||||
/// Date is formatted as %e %b, e.g. Jul 12
|
||||
/// Time is formatted as %R, e.g. 22:34
|
||||
///
|
||||
/// If no SystemTime was given, returns a tuple containing empty strings
|
||||
fn convert_to_utc(src_time: Option<SystemTime>) -> (String, String) {
|
||||
src_time
|
||||
.map(DateTime::<Utc>::from)
|
||||
.map(|date_time| {
|
||||
(
|
||||
date_time.format("%e %b").to_string(),
|
||||
date_time.format("%R").to_string(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Converts a SystemTime to a string readable by a human,
|
||||
/// i.e. calculates the duration between now() and the given SystemTime,
|
||||
/// and gives a rough approximation of the elapsed time since
|
||||
///
|
||||
/// If no SystemTime was given, returns an empty string
|
||||
fn humanize_systemtime(src_time: Option<SystemTime>) -> String {
|
||||
src_time
|
||||
.and_then(|std_time| SystemTime::now().duration_since(std_time).ok())
|
||||
.and_then(|from_now| Duration::from_std(from_now).ok())
|
||||
.map(|duration| HumanTime::from(duration).to_text_en(Accuracy::Rough, Tense::Past))
|
||||
.unwrap_or_default()
|
||||
}
|
Loading…
Reference in New Issue
Block a user