parent
c6c78a16c5
commit
dd6973468c
10 changed files with 357 additions and 126 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -277,6 +277,15 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "content_inspector"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -442,6 +451,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
"content_inspector",
|
||||||
"diqwest",
|
"diqwest",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
|
@ -40,6 +40,7 @@ async-stream = "0.3"
|
||||||
walkdir = "2.3"
|
walkdir = "2.3"
|
||||||
form_urlencoded = "1.0"
|
form_urlencoded = "1.0"
|
||||||
alphanumeric-sort = "1.4"
|
alphanumeric-sort = "1.4"
|
||||||
|
content_inspector = "0.2.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tls"]
|
default = ["tls"]
|
||||||
|
|
|
@ -108,11 +108,10 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
padding: 3em 1em 0;
|
padding: 3.3em 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-folder {
|
.empty-folder {
|
||||||
padding-top: 1rem;
|
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +201,25 @@ body {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 5rem);
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-editable {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.path a {
|
.path a {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
|
|
|
@ -7,59 +7,97 @@
|
||||||
<link rel="icon" type="image/x-icon" href="__ASSERTS_PREFIX__favicon.ico">
|
<link rel="icon" type="image/x-icon" href="__ASSERTS_PREFIX__favicon.ico">
|
||||||
<link rel="stylesheet" href="__ASSERTS_PREFIX__index.css">
|
<link rel="stylesheet" href="__ASSERTS_PREFIX__index.css">
|
||||||
<script>
|
<script>
|
||||||
DATA = __INDEX_DATA__
|
DATA = __INDEX_DATA__
|
||||||
</script>
|
</script>
|
||||||
<script src="__ASSERTS_PREFIX__index.js"></script>
|
<script src="__ASSERTS_PREFIX__index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<div class="breadcrumb"></div>
|
<div class="breadcrumb"></div>
|
||||||
<div class="toolbox">
|
<div class="toolbox">
|
||||||
<div>
|
<div>
|
||||||
<a href="?zip" class="zip-root hidden" title="Download folder as a .zip file">
|
<a href="?zip" class="zip-root hidden" title="Download folder as a .zip file">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
|
<path
|
||||||
|
d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="" class="download hidden" title="Download file" download="">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
|
<path
|
||||||
|
d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
|
||||||
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="control upload-file hidden" title="Upload files">
|
<div class="control upload-file hidden" title="Upload files">
|
||||||
<label for="file">
|
<label for="file">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/></svg>
|
<svg width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
|
<path
|
||||||
|
d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
||||||
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<input type="file" id="file" name="file" multiple>
|
<input type="file" id="file" name="file" multiple>
|
||||||
</div>
|
</div>
|
||||||
<div class="control new-folder hidden" title="New folder">
|
<div class="control new-folder hidden" title="New folder">
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16">
|
<svg width="16" height="16" viewBox="0 0 16 16">
|
||||||
<path d="m.5 3 .04.87a1.99 1.99 0 0 0-.342 1.311l.637 7A2 2 0 0 0 2.826 14H9v-1H2.826a1 1 0 0 1-.995-.91l-.637-7A1 1 0 0 1 2.19 4h11.62a1 1 0 0 1 .996 1.09L14.54 8h1.005l.256-2.819A2 2 0 0 0 13.81 3H9.828a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 6.172 1H2.5a2 2 0 0 0-2 2zm5.672-1a1 1 0 0 1 .707.293L7.586 3H2.19c-.24 0-.47.042-.683.12L1.5 2.98a1 1 0 0 1 1-.98h3.672z"/>
|
<path
|
||||||
<path d="M13.5 10a.5.5 0 0 1 .5.5V12h1.5a.5.5 0 1 1 0 1H14v1.5a.5.5 0 1 1-1 0V13h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z"/>
|
d="m.5 3 .04.87a1.99 1.99 0 0 0-.342 1.311l.637 7A2 2 0 0 0 2.826 14H9v-1H2.826a1 1 0 0 1-.995-.91l-.637-7A1 1 0 0 1 2.19 4h11.62a1 1 0 0 1 .996 1.09L14.54 8h1.005l.256-2.819A2 2 0 0 0 13.81 3H9.828a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 6.172 1H2.5a2 2 0 0 0-2 2zm5.672-1a1 1 0 0 1 .707.293L7.586 3H2.19c-.24 0-.47.042-.683.12L1.5 2.98a1 1 0 0 1 1-.98h3.672z" />
|
||||||
|
<path
|
||||||
|
d="M13.5 10a.5.5 0 0 1 .5.5V12h1.5a.5.5 0 1 1 0 1H14v1.5a.5.5 0 1 1-1 0V13h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form class="searchbar hidden">
|
<form class="searchbar hidden">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/></svg>
|
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<input id="search" name="q" type="text" maxlength="128" autocomplete="off" tabindex="1">
|
<input id="search" name="q" type="text" maxlength="128" autocomplete="off" tabindex="1">
|
||||||
<input type="submit" hidden />
|
<input type="submit" hidden />
|
||||||
</form>
|
</form>
|
||||||
|
<div class="save-btn hidden" title="Save file">
|
||||||
|
<svg viewBox="0 0 1024 1024" width="24" height="24">
|
||||||
|
<path
|
||||||
|
d="M426.666667 682.666667v42.666666h170.666666v-42.666666h-170.666666z m-42.666667-85.333334h298.666667v128h42.666666V418.133333L605.866667 298.666667H298.666667v426.666666h42.666666v-128h42.666667z m260.266667-384L810.666667 379.733333V810.666667H213.333333V213.333333h430.933334zM341.333333 341.333333h85.333334v170.666667H341.333333V341.333333z"
|
||||||
|
fill="#444444" p-id="8311"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="empty-folder hidden"></div>
|
<div class="index-page hidden">
|
||||||
<table class="uploaders-table hidden">
|
<div class="empty-folder hidden"></div>
|
||||||
<thead>
|
<table class="uploaders-table hidden">
|
||||||
<tr>
|
<thead>
|
||||||
<th class="cell-name" colspan="2">Name</th>
|
<tr>
|
||||||
<th class="cell-status">Progress</th>
|
<th class="cell-name" colspan="2">Name</th>
|
||||||
</tr>
|
<th class="cell-status">Progress</th>
|
||||||
</thead>
|
</tr>
|
||||||
</table>
|
</thead>
|
||||||
<table class="paths-table hidden">
|
</table>
|
||||||
<thead>
|
<table class="paths-table hidden">
|
||||||
</thead>
|
<thead>
|
||||||
<tbody>
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="editor-page hidden">
|
||||||
|
<div class="not-editable hidden"></div>
|
||||||
|
<textarea class="editor hidden" cols="10"></textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener("DOMContentLoaded", ready);
|
window.addEventListener("DOMContentLoaded", ready);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
193
assets/index.js
193
assets/index.js
|
@ -7,9 +7,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} DATA
|
* @typedef {IndexDATA|EditDATA} DATA
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} IndexDATA
|
||||||
* @property {string} href
|
* @property {string} href
|
||||||
* @property {string} uri_prefix
|
* @property {string} uri_prefix
|
||||||
|
* @property {"Index"} kind
|
||||||
* @property {PathItem[]} paths
|
* @property {PathItem[]} paths
|
||||||
* @property {boolean} allow_upload
|
* @property {boolean} allow_upload
|
||||||
* @property {boolean} allow_delete
|
* @property {boolean} allow_delete
|
||||||
|
@ -18,6 +23,14 @@
|
||||||
* @property {boolean} dir_exists
|
* @property {boolean} dir_exists
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} EditDATA
|
||||||
|
* @property {string} href
|
||||||
|
* @property {string} uri_prefix
|
||||||
|
* @property {"Edit"} kind
|
||||||
|
* @property {string} editable
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {DATA} DATA
|
* @type {DATA} DATA
|
||||||
*/
|
*/
|
||||||
|
@ -57,11 +70,43 @@ let $emptyFolder;
|
||||||
/**
|
/**
|
||||||
* @type Element
|
* @type Element
|
||||||
*/
|
*/
|
||||||
let $newFolder;
|
let $editor;
|
||||||
/**
|
|
||||||
* @type Element
|
function ready() {
|
||||||
*/
|
document.title = `Index of ${DATA.href} - Dufs`;
|
||||||
let $searchbar;
|
$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");
|
||||||
|
$editor = document.querySelector(".editor");
|
||||||
|
|
||||||
|
addBreadcrumb(DATA.href, DATA.uri_prefix);
|
||||||
|
|
||||||
|
if (DATA.kind == "Index") {
|
||||||
|
|
||||||
|
document.querySelector(".index-page").classList.remove("hidden");
|
||||||
|
|
||||||
|
if (DATA.allow_search) {
|
||||||
|
setupSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DATA.allow_archive) {
|
||||||
|
document.querySelector(".zip-root").classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPathsTableHead();
|
||||||
|
renderPathsTableBody();
|
||||||
|
|
||||||
|
if (DATA.allow_upload) {
|
||||||
|
dropzone();
|
||||||
|
setupUpload();
|
||||||
|
}
|
||||||
|
} else if (DATA.kind == "Edit") {
|
||||||
|
setupEditor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Uploader {
|
class Uploader {
|
||||||
/**
|
/**
|
||||||
|
@ -83,12 +128,12 @@ class Uploader {
|
||||||
|
|
||||||
upload() {
|
upload() {
|
||||||
const { idx, name } = this;
|
const { idx, name } = this;
|
||||||
const url = getUrl(name);
|
const url = newUrl(name);
|
||||||
const encodedName = encodedStr(name);
|
const encodedName = encodedStr(name);
|
||||||
$uploadersTable.insertAdjacentHTML("beforeend", `
|
$uploadersTable.insertAdjacentHTML("beforeend", `
|
||||||
<tr id="upload${idx}" class="uploader">
|
<tr id="upload${idx}" class="uploader">
|
||||||
<td class="path cell-icon">
|
<td class="path cell-icon">
|
||||||
${getSvg()}
|
${getPathSvg()}
|
||||||
</td>
|
</td>
|
||||||
<td class="path cell-name">
|
<td class="path cell-name">
|
||||||
<a href="${url}">${encodedName}</a>
|
<a href="${url}">${encodedName}</a>
|
||||||
|
@ -105,7 +150,7 @@ class Uploader {
|
||||||
|
|
||||||
ajax() {
|
ajax() {
|
||||||
Uploader.runnings += 1;
|
Uploader.runnings += 1;
|
||||||
const url = getUrl(this.name);
|
const url = newUrl(this.name);
|
||||||
this.lastUptime = Date.now();
|
this.lastUptime = Date.now();
|
||||||
const ajax = new XMLHttpRequest();
|
const ajax = new XMLHttpRequest();
|
||||||
ajax.upload.addEventListener("progress", e => this.progress(e), false);
|
ajax.upload.addEventListener("progress", e => this.progress(e), false);
|
||||||
|
@ -272,7 +317,7 @@ function renderPathsTableBody() {
|
||||||
*/
|
*/
|
||||||
function addPath(file, index) {
|
function addPath(file, index) {
|
||||||
const encodedName = encodedStr(file.name);
|
const encodedName = encodedStr(file.name);
|
||||||
let url = getUrl(file.name)
|
let url = newUrl(file.name)
|
||||||
let actionDelete = "";
|
let actionDelete = "";
|
||||||
let actionDownload = "";
|
let actionDownload = "";
|
||||||
let actionMove = "";
|
let actionMove = "";
|
||||||
|
@ -316,10 +361,10 @@ function addPath(file, index) {
|
||||||
$pathsTableBody.insertAdjacentHTML("beforeend", `
|
$pathsTableBody.insertAdjacentHTML("beforeend", `
|
||||||
<tr id="addPath${index}">
|
<tr id="addPath${index}">
|
||||||
<td class="path cell-icon">
|
<td class="path cell-icon">
|
||||||
${getSvg(file.path_type)}
|
${getPathSvg(file.path_type)}
|
||||||
</td>
|
</td>
|
||||||
<td class="path cell-name">
|
<td class="path cell-name">
|
||||||
<a href="${url}">${encodedName}</a>
|
<a href="${url}?edit" target="_blank">${encodedName}</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="cell-mtime">${formatMtime(file.mtime)}</td>
|
<td class="cell-mtime">${formatMtime(file.mtime)}</td>
|
||||||
<td class="cell-size">${formatSize(file.size).join(" ")}</td>
|
<td class="cell-size">${formatSize(file.size).join(" ")}</td>
|
||||||
|
@ -339,19 +384,16 @@ async function deletePath(index) {
|
||||||
if (!confirm(`Delete \`${file.name}\`?`)) return;
|
if (!confirm(`Delete \`${file.name}\`?`)) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(getUrl(file.name), {
|
const res = await fetch(newUrl(file.name), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
if (res.status >= 200 && res.status < 300) {
|
await assertFetch(res);
|
||||||
document.getElementById(`addPath${index}`).remove();
|
document.getElementById(`addPath${index}`).remove();
|
||||||
DATA.paths[index] = null;
|
DATA.paths[index] = null;
|
||||||
if (!DATA.paths.find(v => !!v)) {
|
if (!DATA.paths.find(v => !!v)) {
|
||||||
$pathsTable.classList.add("hidden");
|
$pathsTable.classList.add("hidden");
|
||||||
$emptyFolder.textContent = dirEmptyNote;
|
$emptyFolder.textContent = dirEmptyNote;
|
||||||
$emptyFolder.classList.remove("hidden");
|
$emptyFolder.classList.remove("hidden");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(await res.text())
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(`Cannot delete \`${file.name}\`, ${err.message}`);
|
alert(`Cannot delete \`${file.name}\`, ${err.message}`);
|
||||||
|
@ -368,7 +410,7 @@ async function movePath(index) {
|
||||||
const file = DATA.paths[index];
|
const file = DATA.paths[index];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const fileUrl = getUrl(file.name);
|
const fileUrl = newUrl(file.name);
|
||||||
const fileUrlObj = new URL(fileUrl)
|
const fileUrlObj = new URL(fileUrl)
|
||||||
|
|
||||||
const prefix = DATA.uri_prefix.slice(0, -1);
|
const prefix = DATA.uri_prefix.slice(0, -1);
|
||||||
|
@ -388,11 +430,8 @@ async function movePath(index) {
|
||||||
"Destination": newFileUrl,
|
"Destination": newFileUrl,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (res.status >= 200 && res.status < 300) {
|
await assertFetch(res);
|
||||||
location.href = newFileUrl.split("/").slice(0, -1).join("/")
|
location.href = newFileUrl.split("/").slice(0, -1).join("/")
|
||||||
} else {
|
|
||||||
throw new Error(await res.text())
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(`Cannot move \`${filePath}\` to \`${newPath}\`, ${err.message}`);
|
alert(`Cannot move \`${filePath}\` to \`${newPath}\`, ${err.message}`);
|
||||||
}
|
}
|
||||||
|
@ -426,12 +465,13 @@ function dropzone() {
|
||||||
* Setup searchbar
|
* Setup searchbar
|
||||||
*/
|
*/
|
||||||
function setupSearch() {
|
function setupSearch() {
|
||||||
|
const $searchbar = document.querySelector(".searchbar");
|
||||||
$searchbar.classList.remove("hidden");
|
$searchbar.classList.remove("hidden");
|
||||||
$searchbar.addEventListener("submit", event => {
|
$searchbar.addEventListener("submit", event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const formData = new FormData($searchbar);
|
const formData = new FormData($searchbar);
|
||||||
const q = formData.get("q");
|
const q = formData.get("q");
|
||||||
let href = getUrl();
|
let href = baseUrl();
|
||||||
if (q) {
|
if (q) {
|
||||||
href += "?q=" + q;
|
href += "?q=" + q;
|
||||||
}
|
}
|
||||||
|
@ -442,10 +482,8 @@ function setupSearch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup upload
|
|
||||||
*/
|
|
||||||
function setupUpload() {
|
function setupUpload() {
|
||||||
|
const $newFolder = document.querySelector(".new-folder");
|
||||||
$newFolder.classList.remove("hidden");
|
$newFolder.classList.remove("hidden");
|
||||||
$newFolder.addEventListener("click", () => {
|
$newFolder.addEventListener("click", () => {
|
||||||
const name = prompt("Enter folder name");
|
const name = prompt("Enter folder name");
|
||||||
|
@ -460,19 +498,61 @@ function setupUpload() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setupEditor() {
|
||||||
|
document.querySelector(".editor-page").classList.remove("hidden");;
|
||||||
|
|
||||||
|
const $download = document.querySelector(".download")
|
||||||
|
$download.classList.remove("hidden");
|
||||||
|
$download.href = baseUrl()
|
||||||
|
|
||||||
|
if (!DATA.editable) {
|
||||||
|
const $notEditable = document.querySelector(".not-editable");
|
||||||
|
$notEditable.classList.remove("hidden");
|
||||||
|
$notEditable.textContent = "File is binary or too large.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $saveBtn = document.querySelector(".save-btn");
|
||||||
|
$saveBtn.classList.remove("hidden");
|
||||||
|
$saveBtn.addEventListener("click", saveChange);
|
||||||
|
|
||||||
|
$editor.classList.remove("hidden");
|
||||||
|
try {
|
||||||
|
const res = await fetch(baseUrl());
|
||||||
|
await assertFetch(res);
|
||||||
|
const text = await res.text();
|
||||||
|
$editor.value = text;
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Failed get file, ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save editor change
|
||||||
|
*/
|
||||||
|
async function saveChange() {
|
||||||
|
try {
|
||||||
|
await fetch(baseUrl(), {
|
||||||
|
method: "PUT",
|
||||||
|
body: $editor.value,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Failed to save file, ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a folder
|
* Create a folder
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
*/
|
*/
|
||||||
async function createFolder(name) {
|
async function createFolder(name) {
|
||||||
const url = getUrl(name);
|
const url = newUrl(name);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: "MKCOL",
|
method: "MKCOL",
|
||||||
});
|
});
|
||||||
if (res.status >= 200 && res.status < 300) {
|
await assertFetch(res);
|
||||||
location.href = url;
|
location.href = url;
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(`Cannot create folder \`${name}\`, ${err.message}`);
|
alert(`Cannot create folder \`${name}\`, ${err.message}`);
|
||||||
}
|
}
|
||||||
|
@ -492,15 +572,18 @@ async function addFileEntries(entries, dirs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getUrl(name) {
|
function newUrl(name) {
|
||||||
let url = location.href.split('?')[0];
|
let url = baseUrl();
|
||||||
if (!url.endsWith("/")) url += "/";
|
if (!url.endsWith("/")) url += "/";
|
||||||
if (!name) return url;
|
|
||||||
url += name.split("/").map(encodeURIComponent).join("/");
|
url += name.split("/").map(encodeURIComponent).join("/");
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSvg(path_type) {
|
function baseUrl() {
|
||||||
|
return location.href.split('?')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathSvg(path_type) {
|
||||||
switch (path_type) {
|
switch (path_type) {
|
||||||
case "Dir":
|
case "Dir":
|
||||||
return `<svg height="16" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>`;
|
return `<svg height="16" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>`;
|
||||||
|
@ -558,30 +641,8 @@ function encodedStr(rawStr) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function ready() {
|
async function assertFetch(res) {
|
||||||
document.title = `Index of ${DATA.href} - Dufs`;
|
if (!(res.status >= 200 && res.status < 300)) {
|
||||||
$pathsTable = document.querySelector(".paths-table")
|
throw new Error(await res.text())
|
||||||
$pathsTableHead = document.querySelector(".paths-table thead");
|
|
||||||
$pathsTableBody = document.querySelector(".paths-table tbody");
|
|
||||||
$uploadersTable = document.querySelector(".uploaders-table");
|
|
||||||
$emptyFolder = document.querySelector(".empty-folder");
|
|
||||||
$newFolder = document.querySelector(".new-folder");
|
|
||||||
$searchbar = document.querySelector(".searchbar");
|
|
||||||
|
|
||||||
if (DATA.allow_search) {
|
|
||||||
setupSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DATA.allow_archive) {
|
|
||||||
document.querySelector(".zip-root").classList.remove("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
addBreadcrumb(DATA.href, DATA.uri_prefix);
|
|
||||||
renderPathsTableHead();
|
|
||||||
renderPathsTableBody();
|
|
||||||
|
|
||||||
if (DATA.allow_upload) {
|
|
||||||
dropzone();
|
|
||||||
setupUpload();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncSeekExt, AsyncWrite};
|
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite};
|
||||||
use tokio::{fs, io};
|
use tokio::{fs, io};
|
||||||
use tokio_util::io::StreamReader;
|
use tokio_util::io::StreamReader;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -43,6 +43,7 @@ const INDEX_JS: &str = include_str!("../assets/index.js");
|
||||||
const FAVICON_ICO: &[u8] = include_bytes!("../assets/favicon.ico");
|
const FAVICON_ICO: &[u8] = include_bytes!("../assets/favicon.ico");
|
||||||
const INDEX_NAME: &str = "index.html";
|
const INDEX_NAME: &str = "index.html";
|
||||||
const BUF_SIZE: usize = 65536;
|
const BUF_SIZE: usize = 65536;
|
||||||
|
const TEXT_MAX_SIZE: u64 = 4194304; // 4M
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
args: Arc<Args>,
|
args: Arc<Args>,
|
||||||
|
@ -232,8 +233,12 @@ impl Server {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
} else if is_file {
|
} else if is_file {
|
||||||
self.handle_send_file(path, headers, head_only, &mut res)
|
if query_params.contains_key("edit") {
|
||||||
.await?;
|
self.handle_edit_file(path, head_only, &mut res).await?;
|
||||||
|
} else {
|
||||||
|
self.handle_send_file(path, headers, head_only, &mut res)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
} else if render_spa {
|
} else if render_spa {
|
||||||
self.handle_render_spa(path, headers, head_only, &mut res)
|
self.handle_render_spa(path, headers, head_only, &mut res)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -673,6 +678,41 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_edit_file(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
head_only: bool,
|
||||||
|
res: &mut Response,
|
||||||
|
) -> BoxResult<()> {
|
||||||
|
let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
|
||||||
|
let (file, meta) = (file?, meta?);
|
||||||
|
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
|
||||||
|
let mut buffer: Vec<u8> = vec![];
|
||||||
|
file.take(1024).read_to_end(&mut buffer).await?;
|
||||||
|
let editable = meta.len() <= TEXT_MAX_SIZE && content_inspector::inspect(&buffer).is_text();
|
||||||
|
let data = EditData {
|
||||||
|
href,
|
||||||
|
kind: DataKind::Edit,
|
||||||
|
uri_prefix: self.args.uri_prefix.clone(),
|
||||||
|
allow_upload: self.args.allow_upload,
|
||||||
|
allow_delete: self.args.allow_delete,
|
||||||
|
editable,
|
||||||
|
};
|
||||||
|
res.headers_mut()
|
||||||
|
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
||||||
|
let output = self
|
||||||
|
.html
|
||||||
|
.replace("__ASSERTS_PREFIX__", &self.assets_prefix)
|
||||||
|
.replace("__INDEX_DATA__", &serde_json::to_string(&data).unwrap());
|
||||||
|
res.headers_mut()
|
||||||
|
.typed_insert(ContentLength(output.as_bytes().len() as u64));
|
||||||
|
if head_only {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
*res.body_mut() = output.into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_propfind_dir(
|
async fn handle_propfind_dir(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
@ -855,6 +895,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
|
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
|
||||||
let data = IndexData {
|
let data = IndexData {
|
||||||
|
kind: DataKind::Index,
|
||||||
href,
|
href,
|
||||||
uri_prefix: self.args.uri_prefix.clone(),
|
uri_prefix: self.args.uri_prefix.clone(),
|
||||||
allow_upload: self.args.allow_upload,
|
allow_upload: self.args.allow_upload,
|
||||||
|
@ -1018,9 +1059,16 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
enum DataKind {
|
||||||
|
Index,
|
||||||
|
Edit,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct IndexData {
|
struct IndexData {
|
||||||
href: String,
|
href: String,
|
||||||
|
kind: DataKind,
|
||||||
uri_prefix: String,
|
uri_prefix: String,
|
||||||
allow_upload: bool,
|
allow_upload: bool,
|
||||||
allow_delete: bool,
|
allow_delete: bool,
|
||||||
|
@ -1030,6 +1078,16 @@ struct IndexData {
|
||||||
paths: Vec<PathItem>,
|
paths: Vec<PathItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct EditData {
|
||||||
|
href: String,
|
||||||
|
kind: DataKind,
|
||||||
|
uri_prefix: String,
|
||||||
|
allow_upload: bool,
|
||||||
|
allow_delete: bool,
|
||||||
|
editable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
struct PathItem {
|
struct PathItem {
|
||||||
path_type: PathType,
|
path_type: PathType,
|
||||||
|
|
|
@ -11,9 +11,12 @@ use std::time::{Duration, Instant};
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const BIN_FILE: &str = "😀.bin";
|
||||||
|
|
||||||
/// File names for testing purpose
|
/// File names for testing purpose
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub static FILES: &[&str] = &["test.txt", "test.html", "index.html", "😀.bin"];
|
pub static FILES: &[&str] = &["test.txt", "test.html", "index.html", BIN_FILE];
|
||||||
|
|
||||||
/// Directory names for testing directory don't exist
|
/// Directory names for testing directory don't exist
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -42,10 +45,17 @@ pub static DIRECTORIES: &[&str] = &["dir1/", "dir2/", "dir3/", DIR_NO_INDEX, DIR
|
||||||
pub fn tmpdir() -> TempDir {
|
pub fn tmpdir() -> TempDir {
|
||||||
let tmpdir = assert_fs::TempDir::new().expect("Couldn't create a temp dir for tests");
|
let tmpdir = assert_fs::TempDir::new().expect("Couldn't create a temp dir for tests");
|
||||||
for file in FILES {
|
for file in FILES {
|
||||||
tmpdir
|
if *file == BIN_FILE {
|
||||||
.child(file)
|
tmpdir
|
||||||
.write_str(&format!("This is {file}"))
|
.child(file)
|
||||||
.expect("Couldn't write to file");
|
.write_binary(b"bin\0\0123")
|
||||||
|
.expect("Couldn't write to file");
|
||||||
|
} else {
|
||||||
|
tmpdir
|
||||||
|
.child(file)
|
||||||
|
.write_str(&format!("This is {file}"))
|
||||||
|
.expect("Couldn't write to file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for directory in DIRECTORIES {
|
for directory in DIRECTORIES {
|
||||||
if *directory == DIR_ASSETS {
|
if *directory == DIR_ASSETS {
|
||||||
|
@ -58,10 +68,17 @@ pub fn tmpdir() -> TempDir {
|
||||||
if *directory == DIR_NO_INDEX && *file == "index.html" {
|
if *directory == DIR_NO_INDEX && *file == "index.html" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tmpdir
|
if *file == BIN_FILE {
|
||||||
.child(format!("{directory}{file}"))
|
tmpdir
|
||||||
.write_str(&format!("This is {directory}{file}"))
|
.child(format!("{directory}{file}"))
|
||||||
.expect("Couldn't write to file");
|
.write_binary(b"bin\0\0123")
|
||||||
|
.expect("Couldn't write to file");
|
||||||
|
} else {
|
||||||
|
tmpdir
|
||||||
|
.child(format!("{directory}{file}"))
|
||||||
|
.write_str(&format!("This is {directory}{file}"))
|
||||||
|
.expect("Couldn't write to file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
mod fixtures;
|
mod fixtures;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use fixtures::{server, Error, TestServer};
|
use fixtures::{server, Error, TestServer, BIN_FILE};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use utils::retrive_edit_file;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn get_dir(server: TestServer) -> Result<(), Error> {
|
fn get_dir(server: TestServer) -> Result<(), Error> {
|
||||||
|
@ -103,12 +104,12 @@ fn get_dir_search(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn get_dir_search2(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
fn get_dir_search2(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(format!("{}?q={}", server.url(), "😀.bin"))?;
|
let resp = reqwest::blocking::get(format!("{}?q={BIN_FILE}", server.url()))?;
|
||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
let paths = utils::retrieve_index_paths(&resp.text()?);
|
let paths = utils::retrieve_index_paths(&resp.text()?);
|
||||||
assert!(!paths.is_empty());
|
assert!(!paths.is_empty());
|
||||||
for p in paths {
|
for p in paths {
|
||||||
assert!(p.contains("😀.bin"));
|
assert!(p.contains(BIN_FILE));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -177,6 +178,24 @@ fn get_file_404(server: TestServer) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn get_file_edit(server: TestServer) -> Result<(), Error> {
|
||||||
|
let resp = fetch!(b"GET", format!("{}index.html?edit", server.url())).send()?;
|
||||||
|
assert_eq!(resp.status(), 200);
|
||||||
|
let editable = retrive_edit_file(&resp.text().unwrap()).unwrap();
|
||||||
|
assert!(editable);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn get_file_edit_bin(server: TestServer) -> Result<(), Error> {
|
||||||
|
let resp = fetch!(b"GET", format!("{}{BIN_FILE}?edit", server.url())).send()?;
|
||||||
|
assert_eq!(resp.status(), 200);
|
||||||
|
let editable = retrive_edit_file(&resp.text().unwrap()).unwrap();
|
||||||
|
assert!(!editable);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn head_file_404(server: TestServer) -> Result<(), Error> {
|
fn head_file_404(server: TestServer) -> Result<(), Error> {
|
||||||
let resp = fetch!(b"HEAD", format!("{}404", server.url())).send()?;
|
let resp = fetch!(b"HEAD", format!("{}404", server.url())).send()?;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
mod fixtures;
|
mod fixtures;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use fixtures::{server, Error, TestServer, DIR_NO_FOUND, DIR_NO_INDEX, FILES};
|
use fixtures::{server, Error, TestServer, BIN_FILE, DIR_NO_FOUND, DIR_NO_INDEX, FILES};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
@ -56,11 +56,10 @@ fn render_try_index3(
|
||||||
#[case(server(&["--render-try-index"] as &[&str]), false)]
|
#[case(server(&["--render-try-index"] as &[&str]), false)]
|
||||||
#[case(server(&["--render-try-index", "--allow-search"] as &[&str]), true)]
|
#[case(server(&["--render-try-index", "--allow-search"] as &[&str]), true)]
|
||||||
fn render_try_index4(#[case] server: TestServer, #[case] searched: bool) -> Result<(), Error> {
|
fn render_try_index4(#[case] server: TestServer, #[case] searched: bool) -> Result<(), Error> {
|
||||||
let resp = reqwest::blocking::get(format!("{}{}?q={}", server.url(), DIR_NO_INDEX, "😀.bin"))?;
|
let resp = reqwest::blocking::get(format!("{}{}?q={}", server.url(), DIR_NO_INDEX, BIN_FILE))?;
|
||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
let paths = utils::retrieve_index_paths(&resp.text()?);
|
let paths = utils::retrieve_index_paths(&resp.text()?);
|
||||||
assert!(!paths.is_empty());
|
assert_eq!(paths.iter().all(|v| v.contains(BIN_FILE)), searched);
|
||||||
assert_eq!(paths.iter().all(|v| v.contains("😀.bin")), searched);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,24 +25,13 @@ macro_rules! fetch {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn retrieve_index_paths(index: &str) -> IndexSet<String> {
|
pub fn retrieve_index_paths(content: &str) -> IndexSet<String> {
|
||||||
retrieve_index_paths_impl(index).unwrap_or_default()
|
let value = retrive_json(content).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn encode_uri(v: &str) -> String {
|
|
||||||
let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
|
|
||||||
parts.join("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn retrieve_index_paths_impl(index: &str) -> Option<IndexSet<String>> {
|
|
||||||
let lines: Vec<&str> = index.lines().collect();
|
|
||||||
let line = lines.iter().find(|v| v.contains("DATA ="))?;
|
|
||||||
let line_col = line.find("DATA =").unwrap() + 6;
|
|
||||||
let value: Value = line[line_col..].parse().ok()?;
|
|
||||||
let paths = value
|
let paths = value
|
||||||
.get("paths")?
|
.get("paths")
|
||||||
.as_array()?
|
.unwrap()
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|v| {
|
.flat_map(|v| {
|
||||||
let name = v.get("name")?.as_str()?;
|
let name = v.get("name")?.as_str()?;
|
||||||
|
@ -54,5 +43,26 @@ fn retrieve_index_paths_impl(index: &str) -> Option<IndexSet<String>> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Some(paths)
|
paths
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn retrive_edit_file(content: &str) -> Option<bool> {
|
||||||
|
let value = retrive_json(content)?;
|
||||||
|
let value = value.get("editable").unwrap();
|
||||||
|
Some(value.as_bool().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn encode_uri(v: &str) -> String {
|
||||||
|
let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
|
||||||
|
parts.join("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retrive_json(content: &str) -> Option<Value> {
|
||||||
|
let lines: Vec<&str> = content.lines().collect();
|
||||||
|
let line = lines.iter().find(|v| v.contains("DATA ="))?;
|
||||||
|
let line_col = line.find("DATA =").unwrap() + 6;
|
||||||
|
let value: Value = line[line_col..].parse().unwrap();
|
||||||
|
Some(value)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue