feat: File renaming
parent
98ae384b81
commit
d98b087de2
|
@ -13,10 +13,10 @@ A basic node.js file manager.
|
|||
- [x] File uploads
|
||||
- [ ] Bulk file uploads
|
||||
- [ ] Large file uploads (sharded)
|
||||
- [ ] File/folder renaming
|
||||
- [x] File/folder renaming
|
||||
- [x] Bulk file/folder selection
|
||||
- [x] Delete
|
||||
- [ ] Recursive directory delete
|
||||
- [x] Recursive directory delete
|
||||
- [ ] Move
|
||||
- [ ] Copy
|
||||
- [x] Download archive
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
function htmlEscape(text) {
|
||||
const p = document.createElement("p");
|
||||
p.innerText = text;
|
||||
return p.innerHTML;
|
||||
}
|
|
@ -1,14 +1,8 @@
|
|||
/* jshint esversion: 6 */
|
||||
|
||||
function htmlEscape(text) {
|
||||
const p = document.createElement("p");
|
||||
p.innerText = text;
|
||||
return p.innerHTML;
|
||||
}
|
||||
|
||||
let $select = $(".multi-select");
|
||||
|
||||
let setSelected = (files) => {
|
||||
$select.on("change-files", (e, files) => {
|
||||
$(".multi-files-value").val(JSON.stringify(files.map((f) => f.name)));
|
||||
if (files.length == 0) {
|
||||
$(".multi-files").html(
|
||||
|
@ -19,18 +13,15 @@ let setSelected = (files) => {
|
|||
$(".multi-files").html(
|
||||
files
|
||||
.map((f) => {
|
||||
const badge = `<span class="badge rounded-pill bg-secondary badge-alignment">
|
||||
${filesize(f.size)}
|
||||
</span>`;
|
||||
return `
|
||||
<li class="list-group-item d-flex align-items-start justify-content-between">
|
||||
<span class="name">${htmlEscape(f.name)}</span>
|
||||
${
|
||||
f.type == "directory"
|
||||
? ``
|
||||
: `<span class="badge rounded-pill bg-secondary badge-alignment">${filesize(
|
||||
f.size
|
||||
)}</span>`
|
||||
}
|
||||
</li>
|
||||
`;
|
||||
<li class="list-group-item d-flex align-items-start justify-content-between">
|
||||
<span class="name">${htmlEscape(f.name)}</span>
|
||||
${f.type == "directory" ? `` : badge}
|
||||
</li>
|
||||
`;
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
|
@ -44,7 +35,7 @@ let setSelected = (files) => {
|
|||
} else {
|
||||
$(".multi-files-total").val(filesize(totalSize));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const updateSelected = () => {
|
||||
let $selected = $(".multi-select:checked");
|
||||
|
@ -57,7 +48,7 @@ const updateSelected = () => {
|
|||
});
|
||||
});
|
||||
|
||||
setSelected(files);
|
||||
$select.trigger("change-files", [files]);
|
||||
};
|
||||
|
||||
$select.on("change", updateSelected);
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* jshint esversion: 6 */
|
||||
|
||||
const updateRenameValue = ($inputs, $value) => {
|
||||
let files = [];
|
||||
$inputs.each((i, ele) => {
|
||||
files.push({
|
||||
original: $(ele).data("original"),
|
||||
new: $(ele).val(),
|
||||
});
|
||||
});
|
||||
$value.val(JSON.stringify(files));
|
||||
};
|
||||
|
||||
$select.on("change-files", (e, files) => {
|
||||
if (files.length == 0) {
|
||||
$(".rename-files").html(
|
||||
`<ul class="list-group"><li class="list-group-item text-muted">No files selected</li></ul>`
|
||||
);
|
||||
return;
|
||||
}
|
||||
$(".rename-files").html(
|
||||
files
|
||||
.map((f) => {
|
||||
return `
|
||||
<div class="mb-3">
|
||||
<label class="form-label">
|
||||
<strike>${htmlEscape(f.name)}</strike>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control rename-files-input"
|
||||
data-original="${htmlEscape(f.name)}"
|
||||
value="${htmlEscape(f.name)}">
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
$(".rename-files-input").on("keydown", (e) => {
|
||||
if (e.keyCode == 13) {
|
||||
e.preventDefault();
|
||||
const $next = $(e.target).parent().nextAll().find("input")[0];
|
||||
if ($next) {
|
||||
$next.focus();
|
||||
if ($next.type == "text") {
|
||||
$next.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".rename-files").each((i, ele) => {
|
||||
const $value = $(ele).parent().find(".rename-files-value");
|
||||
const $inputs = $(ele)
|
||||
.find(".rename-files-input")
|
||||
.on("focus blur change", (e) => {
|
||||
updateRenameValue($inputs, $value);
|
||||
});
|
||||
updateRenameValue($inputs, $value);
|
||||
});
|
||||
});
|
||||
|
||||
updateSelected();
|
51
index.js
51
index.js
|
@ -453,6 +453,57 @@ app.get("/*@download", (req, res) => {
|
|||
});
|
||||
});
|
||||
|
||||
app.post("/*@rename", (req, res) => {
|
||||
res.filename = req.params[0];
|
||||
|
||||
let files = JSON.parse(req.body.files);
|
||||
if (!files || !files.map) {
|
||||
req.flash("error", "No files selected.");
|
||||
res.redirect("back");
|
||||
return;
|
||||
}
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
fs.access(relative(res.filename), fs.constants.W_OK, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
let promises = files.map((f) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.rename(
|
||||
relative(res.filename, f.original),
|
||||
relative(res.filename, f.new),
|
||||
(err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
req.flash("success", "Files renamed. ");
|
||||
res.redirect("back");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(err);
|
||||
req.flash("error", "Unable to rename some files: " + err);
|
||||
res.redirect("back");
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(err);
|
||||
req.flash("error", err.toString());
|
||||
res.redirect("back");
|
||||
});
|
||||
});
|
||||
|
||||
const shellable = process.env.SHELL != "false" && process.env.SHELL;
|
||||
const cmdable = process.env.CMD != "false" && process.env.CMD;
|
||||
if (shellable || cmdable) {
|
||||
|
|
|
@ -17,12 +17,16 @@
|
|||
<script src="/@assets/xterm-addon-fit/lib/xterm-addon-fit.js" defer></script>
|
||||
<!-- file size math -->
|
||||
<script src="/@assets/filesize/filesize.js" defer></script>
|
||||
<!-- common utilities -->
|
||||
<script src="/@assets/common.js" defer></script>
|
||||
<!-- navbar, tooltip -->
|
||||
<link rel="stylesheet" href="/@assets/navbar.css" />
|
||||
<script src="/@assets/tooltip.js" defer></script>
|
||||
<!-- list, multi-select -->
|
||||
<link rel="stylesheet" href="/@assets/list.css" />
|
||||
<script src="/@assets/multi.js" defer></script>
|
||||
<!-- rename -->
|
||||
<script src="/@assets/rename.js" defer></script>
|
||||
<!-- upload -->
|
||||
<link rel="stylesheet" href="/@assets/upload.css" />
|
||||
<script src="/@assets/upload.js" defer></script>
|
||||
|
|
|
@ -57,4 +57,5 @@
|
|||
{{> dialogue-cmd}}
|
||||
|
||||
{{> dialogue-download}}
|
||||
{{> dialogue-rename}}
|
||||
{{> dialogue-delete}}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<form action="@rename" method="post">
|
||||
<div id="rename" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title">Rename files or folders</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="rename-files">
|
||||
|
||||
</div>
|
||||
<input type="hidden" name="files" value="" class="rename-files-value" />
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="reset" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Rename</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -30,13 +30,19 @@
|
|||
<div class="btn-group me-2" role="group">
|
||||
<a class="btn btn-success" href="@download" data-bs-toggle="modal" data-bs-target="#download" title="Download file archive" data-bs-placement="top">
|
||||
{{octicon "file-zip"}}
|
||||
<span class="d-none d-md-inline align-text-bottom">Download</span>
|
||||
<span class="d-none d-lg-inline align-text-bottom">Download</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group me-2" role="group">
|
||||
<a class="btn btn-secondary" href="@rename" data-bs-toggle="modal" data-bs-target="#rename" title="Rename selected files" data-bs-placement="top">
|
||||
{{octicon "pencil"}}
|
||||
<span class="d-none d-lg-inline align-text-bottom">Rename</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group me-2" role="group">
|
||||
<a class="btn btn-danger" href="@delete" data-bs-toggle="modal" data-bs-target="#delete" title="Delete selected files" data-bs-placement="top">
|
||||
{{octicon "trash"}}
|
||||
<span class="d-none d-md-inline align-text-bottom">Delete</span>
|
||||
<span class="d-none d-lg-inline align-text-bottom">Delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue