* @property {number} size
*/
-// https://stackoverflow.com/a/901144/3642588
-const params = new Proxy(new URLSearchParams(window.location.search), {
- get: (searchParams, prop) => searchParams.get(prop),
-});
+/**
+ * @typedef {object} DATA
+ * @property {string} href
+ * @property {string} uri_prefix
+ * @property {PathItem[]} paths
+ * @property {boolean} allow_upload
+ * @property {boolean} allow_delete
+ * @property {boolean} allow_search
+ * @property {boolean} dir_exists
+ */
-const dirEmptyNote = params.q ? 'No results' : DATA.dir_exists ? 'Empty folder' : 'Folder will be created when a file is uploaded';
+/**
+ * @type {DATA} DATA
+ */
+var DATA;
+
+/**
+ * @type {PARAMS}
+ * @typedef {object} PARAMS
+ * @property {string} q
+ * @property {string} sort
+ * @property {string} order
+ */
+const PARAMS = Object.fromEntries(new URLSearchParams(window.location.search).entries());
+
+const dirEmptyNote = PARAMS.q ? 'No results' : DATA.dir_exists ? 'Empty folder' : 'Folder will be created when a file is uploaded';
/**
* @type Element
*/
let $pathsTable;
+/**
+ * @type Element
+ */
+let $pathsTableHead;
/**
* @type Element
*/
}
}
+/**
+ * Render path table thead
+ */
+function renderPathsTableHead() {
+ const headerItems = [
+ {
+ name: "name",
+ props: `colspan="2"`,
+ text: "Name",
+ },
+ {
+ name: "mtime",
+ props: ``,
+ text: "Last Modified",
+ },
+ {
+ name: "size",
+ props: ``,
+ text: "Size",
+ }
+ ];
+ $pathsTableHead.insertAdjacentHTML("beforeend", `
+ <tr>
+ ${headerItems.map(item => {
+ let svg = `<svg width="12" height="12" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M11.5 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L11 2.707V14.5a.5.5 0 0 0 .5.5zm-7-14a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V1.5a.5.5 0 0 1 .5-.5z"/></svg>`;
+ let order = "asc";
+ if (PARAMS.sort === item.name) {
+ if (PARAMS.order === "asc") {
+ order = "desc";
+ svg = `<svg width="12" height="12" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z"/></svg>`
+ } else {
+ svg = `<svg width="12" height="12" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z"/></svg>`
+ }
+ }
+ const qs = new URLSearchParams({...PARAMS, order, sort: item.name }).toString();
+ const icon = `<span>${svg}</span>`
+ return `<th class="cell-${item.name}" ${item.props}><a href="?${qs}">${item.text}${icon}</a></th>`
+ }).join("\n")}
+ <th class="cell-actions">Actions</th>
+ </tr>
+ `);
+}
+
+/**
+ * Render path table tbody
+ */
+function renderPathsTableBody() {
+ if (DATA.paths && DATA.paths.length > 0) {
+ const len = DATA.paths.length;
+ if (len > 0) {
+ $pathsTable.classList.remove("hidden");
+ }
+ for (let i = 0; i < len; i++) {
+ addPath(DATA.paths[i], i);
+ }
+ } else {
+ $emptyFolder.textContent = dirEmptyNote;
+ $emptyFolder.classList.remove("hidden");
+ }
+}
+
/**
* Add pathitem
* @param {PathItem} file
function ready() {
document.title = `Index of ${DATA.href} - Dufs`;
$pathsTable = document.querySelector(".paths-table")
+ $pathsTableHead = document.querySelector(".paths-table thead");
$pathsTableBody = document.querySelector(".paths-table tbody");
$uploadersTable = document.querySelector(".uploaders-table");
$emptyFolder = document.querySelector(".empty-folder");
if (DATA.allow_search) {
document.querySelector(".searchbar").classList.remove("hidden");
- if (params.q) {
- document.getElementById('search').value = params.q;
+ if (PARAMS.q) {
+ document.getElementById('search').value = PARAMS.q;
}
}
-
addBreadcrumb(DATA.href, DATA.uri_prefix);
- if (Array.isArray(DATA.paths)) {
- const len = DATA.paths.length;
- if (len > 0) {
- $pathsTable.classList.remove("hidden");
- }
- for (let i = 0; i < len; i++) {
- addPath(DATA.paths[i], i);
- }
- if (len == 0) {
- $emptyFolder.textContent = dirEmptyNote;
- $emptyFolder.classList.remove("hidden");
- }
- }
+ renderPathsTableHead();
+ renderPathsTableBody();
+
if (DATA.allow_upload) {
dropzone();
if (DATA.allow_delete) {
};
use hyper::{Body, Method, StatusCode, Uri};
use serde::Serialize;
+use std::collections::HashMap;
use std::fs::Metadata;
use std::io::SeekFrom;
use std::net::SocketAddr;
let path = path.as_path();
let query = req.uri().query().unwrap_or_default();
+ let query_params: HashMap<String, String> = form_urlencoded::parse(query.as_bytes())
+ .map(|(k, v)| (k.to_string(), v.to_string()))
+ .collect();
let (is_miss, is_dir, is_file, size) = match fs::metadata(path).await.ok() {
Some(meta) => (false, meta.is_dir(), meta.is_file(), meta.len()),
Method::GET | Method::HEAD => {
if is_dir {
if render_try_index {
- if query == "zip" {
+ if query_params.contains_key("zip") {
self.handle_zip_dir(path, head_only, &mut res).await?;
- } else if allow_search && query.starts_with("q=") {
- let q = decode_uri(&query[2..]).unwrap_or_default();
- self.handle_search_dir(path, &q, head_only, &mut res)
+ } else if allow_search && query_params.contains_key("q") {
+ self.handle_search_dir(path, &query_params, head_only, &mut res)
.await?;
} else {
- self.handle_render_index(path, headers, head_only, &mut res)
- .await?;
+ self.handle_render_index(
+ path,
+ &query_params,
+ headers,
+ head_only,
+ &mut res,
+ )
+ .await?;
}
} else if render_index || render_spa {
- self.handle_render_index(path, headers, head_only, &mut res)
+ self.handle_render_index(path, &query_params, headers, head_only, &mut res)
.await?;
- } else if query == "zip" {
+ } else if query_params.contains_key("zip") {
self.handle_zip_dir(path, head_only, &mut res).await?;
- } else if allow_search && query.starts_with("q=") {
- let q = decode_uri(&query[2..]).unwrap_or_default();
- self.handle_search_dir(path, &q, head_only, &mut res)
+ } else if allow_search && query_params.contains_key("q") {
+ self.handle_search_dir(path, &query_params, head_only, &mut res)
.await?;
} else {
- self.handle_ls_dir(path, true, head_only, &mut res).await?;
+ self.handle_ls_dir(path, true, &query_params, head_only, &mut res)
+ .await?;
}
} else if is_file {
self.handle_send_file(path, headers, head_only, &mut res)
self.handle_render_spa(path, headers, head_only, &mut res)
.await?;
} else if allow_upload && req_path.ends_with('/') {
- self.handle_ls_dir(path, false, head_only, &mut res).await?;
+ self.handle_ls_dir(path, false, &query_params, head_only, &mut res)
+ .await?;
} else {
status_not_found(&mut res);
}
&self,
path: &Path,
exist: bool,
+ query_params: &HashMap<String, String>,
head_only: bool,
res: &mut Response,
) -> BoxResult<()> {
}
}
};
- self.send_index(path, paths, exist, head_only, res)
+ self.send_index(path, paths, exist, query_params, head_only, res)
}
async fn handle_search_dir(
&self,
path: &Path,
- search: &str,
+ query_params: &HashMap<String, String>,
head_only: bool,
res: &mut Response,
) -> BoxResult<()> {
let hidden = Arc::new(self.args.hidden.to_vec());
let hidden = hidden.clone();
let running = self.running.clone();
- let search = search.to_lowercase();
+ let search = query_params.get("q").unwrap().to_lowercase();
let search_paths = tokio::task::spawn_blocking(move || {
let mut it = WalkDir::new(&path_buf).into_iter();
let mut paths: Vec<PathBuf> = vec![];
paths.push(item);
}
}
- self.send_index(path, paths, true, head_only, res)
+ self.send_index(path, paths, true, query_params, head_only, res)
}
async fn handle_zip_dir(
async fn handle_render_index(
&self,
path: &Path,
+ query_params: &HashMap<String, String>,
headers: &HeaderMap<HeaderValue>,
head_only: bool,
res: &mut Response,
self.handle_send_file(&index_path, headers, head_only, res)
.await?;
} else if self.args.render_try_index {
- self.handle_ls_dir(path, true, head_only, res).await?;
+ self.handle_ls_dir(path, true, query_params, head_only, res)
+ .await?;
} else {
status_not_found(res)
}
path: &Path,
mut paths: Vec<PathItem>,
exist: bool,
+ query_params: &HashMap<String, String>,
head_only: bool,
res: &mut Response,
) -> BoxResult<()> {
- paths.sort_unstable();
+ if let Some(sort) = query_params.get("sort") {
+ if sort == "name" {
+ paths.sort_by(|v1, v2| {
+ alphanumeric_sort::compare_str(v1.name.to_lowercase(), v2.name.to_lowercase())
+ })
+ } else if sort == "mtime" {
+ paths.sort_by(|v1, v2| v1.mtime.cmp(&v2.mtime))
+ } else if sort == "size" {
+ paths.sort_by(|v1, v2| v1.size.unwrap_or(0).cmp(&v2.size.unwrap_or(0)))
+ }
+ if query_params
+ .get("order")
+ .map(|v| v == "desc")
+ .unwrap_or_default()
+ {
+ paths.reverse()
+ }
+ } else {
+ paths.sort_unstable();
+ }
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
let data = IndexData {
href,