You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
733 lines
17 KiB
733 lines
17 KiB
function ucFirst(str) {
|
|
if (!str) return str;
|
|
|
|
return str[0].toUpperCase() + str.slice(1);
|
|
}
|
|
let img_page = 1;
|
|
let mediaScanUrl = '/attachment/index?limit=20&page='+img_page+'&type=2';
|
|
// let mediaScanUrl = 'scan.php';
|
|
|
|
class MediaModal {
|
|
constructor (modal = true)
|
|
{
|
|
this.isInit = false;
|
|
this.isModal = modal;
|
|
|
|
this.modalHtml =
|
|
`
|
|
<div class="modal fade modal-full" id="MediaModal" tabindex="-1" role="dialog" aria-labelledby="MediaModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-scrollable" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="MediaModalLabel">资源文件</h5>
|
|
|
|
<button type="button" class="btn btn-sm" data-bs-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true"><i class="la la-times la-lg"></i></span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
|
|
<div class="filemanager">
|
|
|
|
<div class="top-right d-flex justify-content-between">
|
|
|
|
<div class="align-left">
|
|
<div class="breadcrumbs"></div>
|
|
</div>
|
|
|
|
|
|
<div class="align-right">
|
|
<div class="search">
|
|
<input type="search" id="media-search-input" placeholder="查找文件" />
|
|
</div>
|
|
|
|
<button class="btn btn-outline-secondary btn-sm btn-icon me-5 float-end"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target=".upload"
|
|
aria-expanded="false"
|
|
>
|
|
<i class="la la-cloud-upload-alt la-lg"></i>
|
|
上传新文件
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="top-panel">
|
|
|
|
<div class="upload collapse">
|
|
|
|
<button id="upload-close" type="button" class="btn btn-sm btn-light" aria-label="Close" data-bs-toggle="collapse" data-bs-target=".upload" aria-expanded="true">
|
|
<span aria-hidden="true"><i class="la la-times la-lg"></i></span>
|
|
</button>
|
|
|
|
|
|
<h3>拖动或选择文件上传</h3>
|
|
|
|
<input type="file" class="file">
|
|
|
|
<div class="status"></div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<div class="display-panel">
|
|
|
|
<ul class="data" id="media-files"></ul>
|
|
|
|
<div class="nothingfound">
|
|
<div class="nofiles"></div>
|
|
<span>这里没有文件。</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="modal-footer justify-content-between">
|
|
|
|
<div class="align-left">
|
|
|
|
</div>
|
|
|
|
<div class="align-right">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
|
<button type="button" class="btn btn-primary save-btn">添加选中</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
this.response = [],
|
|
this.currentPath = '';
|
|
this.breadcrumbsUrls = [];
|
|
this.filemanager = null;
|
|
this.breadcrumbs = null;
|
|
this.fileList = null;
|
|
this.mediaPath = "/public/media/";
|
|
this.type = "single";
|
|
}
|
|
|
|
addModalHtml() {
|
|
if (this.isModal) $("body").append(this.modalHtml);
|
|
$("#MediaModal .save-btn").on("click", () => this.save());
|
|
}
|
|
|
|
showUploadLoading() {
|
|
$("#MediaModal .upload .status").html(`
|
|
<div class="spinner-border" style="width: 5rem; height: 5rem;margin: 5rem auto; display:block" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>`);
|
|
}
|
|
|
|
hideUploadLoading() {
|
|
$("#MediaModal .upload .status").html('');
|
|
$("#MediaModal .upload .file").prop('outerHTML', $("#MediaModal .upload .file").prop('outerHTML'));
|
|
$(".filemanager input[type=file]").on("change", this.onUpload);
|
|
}
|
|
|
|
save() {
|
|
|
|
let file = $("#MediaModal .files input:checked").eq(0).val();
|
|
if (this.targetInput) {
|
|
$(this.targetInput).val(file).trigger("change");
|
|
}
|
|
|
|
if (file.indexOf("//") == -1) {
|
|
file = this.mediaPath + file;
|
|
}
|
|
|
|
if (this.targetThumb) {
|
|
$(this.targetThumb).attr("src", file);
|
|
}
|
|
|
|
if (this.callback) {
|
|
this.callback(file);
|
|
}
|
|
|
|
if (this.isModal) $("#MediaModal").modal('hide');
|
|
}
|
|
|
|
init() {
|
|
if (!this.isInit) {
|
|
if (this.isModal) this.addModalHtml();
|
|
let self = this;
|
|
|
|
// this.initGallery();
|
|
this.isInit = true;
|
|
|
|
$(".filemanager input[type=file]").on("change", this.onUpload);
|
|
|
|
$(window).trigger( "mediaModal:init", { type:this.type, targetInput:this.targetInput, targetThumb:this.targetThumb, callback:this.callback} );
|
|
}
|
|
}
|
|
|
|
open(element, callback) {
|
|
if (element instanceof Element) {
|
|
this.targetInput = element.dataset.targetInput;
|
|
this.targetThumb = element.dataset.targetThumb;
|
|
if (element.dataset.type) {
|
|
this.type = element.dataset.type;
|
|
}
|
|
} else if (element) {
|
|
this.targetInput = element.targetInput;
|
|
this.targetThumb = element.targetThumb;
|
|
if (element.type) {
|
|
this.type = element.type;
|
|
}
|
|
}
|
|
|
|
this.callback = callback;
|
|
this.init();
|
|
|
|
if (this.isModal) $('#MediaModal').modal('show');
|
|
}
|
|
|
|
|
|
initGallery() {
|
|
this.filemanager = $('.filemanager'),
|
|
this.breadcrumbs = $('.breadcrumbs'),
|
|
this.fileList = this.filemanager.find('.data');
|
|
let _this = this;
|
|
|
|
// Start by fetching the file data from scan.php with an AJAX request
|
|
|
|
$.ajax({
|
|
type: 'get',
|
|
url: mediaScanUrl,
|
|
headers: createHeader(),
|
|
data: {},
|
|
dataType: "json",
|
|
success: function(res){
|
|
// console.log(res, '接口返回');
|
|
if (res.code && res.code != 0) {
|
|
alert(res.msg)
|
|
return ;
|
|
// window.location.href = '/admin.php'
|
|
}
|
|
|
|
let list = res.data;
|
|
let tempData = [];
|
|
list.forEach(function (item, index) {
|
|
tempData[index] = {
|
|
"name": item.name,
|
|
"path": item.url,
|
|
"type": "file",
|
|
"size": item.size
|
|
}
|
|
})
|
|
|
|
_this.response = [{
|
|
items:tempData,
|
|
name:'',
|
|
path:'',
|
|
type:"folder"
|
|
}],
|
|
_this.currentPath = '',
|
|
_this.breadcrumbsUrls = [];
|
|
|
|
var folders = [],
|
|
files = [];
|
|
|
|
$(window).trigger('hashchange');
|
|
}
|
|
});
|
|
|
|
|
|
// $.get('scan.php', function(data) {
|
|
// //$.get('/this.filemanager/scan.php', function(data) {
|
|
//
|
|
// _this.response = [data],
|
|
// _this.currentPath = '',
|
|
// _this.breadcrumbsUrls = [];
|
|
//
|
|
// var folders = [],
|
|
// files = [];
|
|
//
|
|
// $(window).trigger('hashchange');
|
|
// });
|
|
|
|
// This event listener monitors changes on the URL. We use it to
|
|
// capture back/forward navigation in the browser.
|
|
|
|
$(window).on('hashchange', function(){
|
|
|
|
_this.goto(window.location.hash);
|
|
|
|
// We are triggering the event. This will execute
|
|
// this function on page load, so that we show the correct folder:
|
|
|
|
});
|
|
|
|
|
|
// Hiding and showing the search box
|
|
|
|
this.filemanager.find('.search').click(function(){
|
|
|
|
var search = $(this);
|
|
|
|
search.find('span').hide();
|
|
search.find('input[type=search]').show().focus();
|
|
|
|
});
|
|
|
|
|
|
// Listening for keyboard input on the search field.
|
|
// We are using the "input" event which detects cut and paste
|
|
// in addition to keyboard input.
|
|
this.filemanager.find('input[type=search]').on('input', function(e){
|
|
|
|
var folders = [];
|
|
var files = [];
|
|
|
|
var value = this.value.trim();
|
|
|
|
if(value.length) {
|
|
|
|
_this.filemanager.addClass('searching');
|
|
|
|
// Update the hash on every key stroke
|
|
window.location.hash = 'search=' + value.trim();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_this.filemanager.removeClass('searching');
|
|
window.location.hash = encodeURIComponent(_this.currentPath);
|
|
|
|
}
|
|
|
|
}).on('keyup', function(e){
|
|
|
|
// Clicking 'ESC' button triggers focusout and cancels the search
|
|
|
|
var search = $(this);
|
|
|
|
if(e.keyCode == 27) {
|
|
|
|
search.trigger('focusout');
|
|
|
|
}
|
|
|
|
}).focusout(function(e){
|
|
|
|
// Cancel the search
|
|
|
|
var search = $(this);
|
|
|
|
if(!search.val().trim().length) {
|
|
|
|
window.location.hash = encodeURIComponent(_this.currentPath);
|
|
search.hide();
|
|
search.parent().find('span').show();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Clicking on folders
|
|
|
|
this.fileList.on('click', 'li.folders', function(e){
|
|
e.preventDefault();
|
|
|
|
var nextDir = $(this).find('a.folders').attr('href');
|
|
|
|
if(_this.filemanager.hasClass('searching')) {
|
|
|
|
// Building the this.breadcrumbs
|
|
|
|
_this.breadcrumbsUrls = _this.generateBreadcrumbs(nextDir);
|
|
|
|
_this.filemanager.removeClass('searching');
|
|
_this.filemanager.find('input[type=search]').val('').hide();
|
|
_this.filemanager.find('span').show();
|
|
}
|
|
else {
|
|
_this.breadcrumbsUrls.push(nextDir);
|
|
}
|
|
|
|
window.location.hash = encodeURIComponent(nextDir);
|
|
_this.currentPath = nextDir;
|
|
});
|
|
|
|
|
|
// Clicking on this.breadcrumbs
|
|
|
|
this.breadcrumbs.on('click', 'a', function(e){
|
|
e.preventDefault();
|
|
|
|
var index = _this.breadcrumbs.find('a').index($(this)),
|
|
nextDir = _this.breadcrumbsUrls[index];
|
|
|
|
_this.breadcrumbsUrls.length = Number(index);
|
|
|
|
window.location.hash = encodeURIComponent(nextDir);
|
|
|
|
});
|
|
}
|
|
|
|
|
|
// Navigates to the given hash (path)
|
|
|
|
goto(hash) {
|
|
|
|
hash = decodeURIComponent(hash).slice(1).split('=');
|
|
let _this = this;
|
|
|
|
if (hash.length) {
|
|
var rendered = '';
|
|
|
|
// if hash has search in it
|
|
|
|
if (hash[0] === 'search') {
|
|
|
|
this.filemanager.addClass('searching');
|
|
rendered = _this.searchData(_this.response, hash[1].toLowerCase());
|
|
|
|
if (rendered.length) {
|
|
this.currentPath = hash[0];
|
|
this.render(rendered);
|
|
}
|
|
else {
|
|
this.render(rendered);
|
|
}
|
|
|
|
}
|
|
|
|
// if hash is some path
|
|
|
|
else if (hash[0].trim().length) {
|
|
|
|
rendered = this.searchByPath(hash[0]);
|
|
|
|
if (rendered.length) {
|
|
|
|
this.currentPath = hash[0];
|
|
this.breadcrumbsUrls = this.generateBreadcrumbs(hash[0]);
|
|
this.render(rendered);
|
|
|
|
}
|
|
else {
|
|
this.currentPath = hash[0];
|
|
this.breadcrumbsUrls = this.generateBreadcrumbs(hash[0]);
|
|
this.render(rendered);
|
|
}
|
|
|
|
}
|
|
|
|
// if there is no hash
|
|
|
|
else {
|
|
this.currentPath = this.response[0].path;
|
|
this.breadcrumbsUrls.push(this.response[0].path);
|
|
this.render(this.searchByPath(this.response[0].path));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Splits a file path and turns it into clickable breadcrumbs
|
|
_
|
|
generateBreadcrumbs(nextDir){
|
|
var path = nextDir.split('/').slice(0);
|
|
for(var i=1;i<path.length;i++){
|
|
path[i] = path[i-1]+ '/' +path[i];
|
|
}
|
|
return path;
|
|
}
|
|
|
|
|
|
// Locates a file by path
|
|
|
|
searchByPath(dir) {
|
|
var path = dir.split('/'),
|
|
demo = this.response,
|
|
flag = 0;
|
|
|
|
for(var i=0;i<path.length;i++){
|
|
for(var j=0;j<demo.length;j++){
|
|
if(demo[j].name === path[i]){
|
|
flag = 1;
|
|
demo = demo[j].items;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//demo = flag ? demo : [];
|
|
return demo;
|
|
}
|
|
|
|
|
|
// Recursively search through the file tree
|
|
|
|
searchData(data, searchTerms) {
|
|
|
|
let _this = this;
|
|
let folders = [];
|
|
let files = [];
|
|
|
|
let _searchData = function (data, searchTerms) {
|
|
data.forEach(function(d){
|
|
if(d.type === 'folder') {
|
|
|
|
_searchData(d.items,searchTerms);
|
|
|
|
if(d.name.toLowerCase().indexOf(searchTerms) >= 0) {
|
|
folders.push(d);
|
|
}
|
|
}
|
|
else if(d.type === 'file') {
|
|
if(d.name.toLowerCase().indexOf(searchTerms) >= 0) {
|
|
files.push(d);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
_searchData(data, searchTerms);
|
|
|
|
return {folders: folders, files: files};
|
|
}
|
|
|
|
|
|
onUpload(event) {
|
|
|
|
// console.log('上传按钮修改!', this.files);
|
|
|
|
let file;
|
|
if (this.files && this.files[0]) {
|
|
Vvveb.MediaModal.showUploadLoading();
|
|
var reader = new FileReader();
|
|
reader.onload = imageIsLoaded;
|
|
reader.readAsDataURL(this.files[0]);
|
|
//reader.readAsBinaryString(this.files[0]);
|
|
file = this.files[0];
|
|
}
|
|
|
|
function imageIsLoaded(e) {
|
|
|
|
let image = e.target.result;
|
|
|
|
var formData = new FormData();
|
|
formData.append("file", file);
|
|
formData.append("mediaPath", Vvveb.MediaModal.mediaPath + Vvveb.MediaModal.currentPath);
|
|
formData.append("onlyFilename", true);
|
|
formData.append("attachment_cate_id", 0);
|
|
formData.append("type", 2);
|
|
|
|
$.ajax({
|
|
type: "POST",
|
|
url: '/attachment/uploadAndSave',//set your server side upload script url
|
|
data: formData,
|
|
headers: createHeader(),
|
|
processData: false,
|
|
contentType: false,
|
|
success: function (res) {
|
|
let data = res.data;
|
|
Vvveb.MediaModal.addFile({
|
|
name:data.name,
|
|
type:"file",
|
|
path: data.url,
|
|
size:data.size
|
|
},true);
|
|
|
|
Vvveb.MediaModal.hideUploadLoading();
|
|
|
|
var messageModal = new bootstrap.Modal(document.getElementById('message-modal'), {
|
|
keyboard: false
|
|
});
|
|
$('#message-modal').css('z-index', '1065');
|
|
$("#message-modal .modal-body").html('上传成功!');
|
|
messageModal.show()
|
|
|
|
$(window).trigger( "mediaModal:upload", { type:this.type, targetInput:this.targetInput, targetThumb:this.targetThumb, callback:this.callback} );
|
|
},
|
|
error: function (data) {
|
|
alert(data.msg);
|
|
Vvveb.MediaModal.hideUploadLoading();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
addFile(f, selected) {
|
|
let _this= this;
|
|
// _this.bytesToSize(f.size)
|
|
var fileSize = f.size,
|
|
name = _this.escapeHTML(f.name),
|
|
fileType = name.split('.'),
|
|
icon = '<span class="icon file"></span>';
|
|
|
|
fileType = fileType[fileType.length-1];
|
|
|
|
if (fileType == "jpg" || fileType == "jpeg" || fileType == "png" || fileType == "gif" || fileType == "svg" || fileType == "webp") {
|
|
//icon = '<div class="image" style="background-image: url(' + _this.mediaPath + f.path + ');"></div>';
|
|
icon = '<img class="image" loading="lazy" src="' + f.path + '">';
|
|
} else {
|
|
icon = '<span class="icon file f-'+fileType+'">.'+fileType+'</span>';
|
|
}
|
|
//icon = '<span class="icon file f-'+fileType+'">.'+fileType+'</span>';
|
|
|
|
|
|
var file = $('<li class="files">\
|
|
<label class="form-check">\
|
|
<input type="' + ((_this.type == "single") ? "radio" : "checkbox") + '" class="form-check-input" value="' + f.path + '" name="file[]" ' + ((selected == "single") ? "checked" : "") + '><span class="form-check-label"></span>\
|
|
<div href="#\" class="files">'+icon+'<div class="info"><div class="name">'+ name +'</div><span class="details">'+fileSize+'</span>\
|
|
<a href="javascript:void(0);" class="preview-link"><i class="la la-search-plus"></i></a>\
|
|
<div class="preview">\
|
|
<img src="' + f.path + '">\
|
|
<div>\
|
|
<span class="name">'+ name +'</span><span class="details">'+fileSize+'</span>\
|
|
</div>\
|
|
</div>\
|
|
</div>\
|
|
</label>\
|
|
</li>');
|
|
|
|
let fileelement = file.appendTo(_this.fileList);
|
|
if (selected) {
|
|
$("input[type='radio'], input[type='checkbox']", fileelement).prop("checked", true);
|
|
}
|
|
}
|
|
|
|
|
|
render(data) {
|
|
|
|
var scannedFolders = [],
|
|
scannedFiles = [];
|
|
|
|
if(Array.isArray(data)) {
|
|
|
|
data.forEach(function (d) {
|
|
|
|
if (d.type === 'folder') {
|
|
scannedFolders.push(d);
|
|
}
|
|
else if (d.type === 'file') {
|
|
scannedFiles.push(d);
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
else if(typeof data === 'object') {
|
|
|
|
scannedFolders = data.folders;
|
|
scannedFiles = data.files;
|
|
|
|
}
|
|
|
|
|
|
// Empty the old result and make the new one
|
|
|
|
this.fileList.empty();//.hide();
|
|
if(!scannedFolders.length && !scannedFiles.length) {
|
|
this.filemanager.find('.nothingfound').show();
|
|
}
|
|
else {
|
|
this.filemanager.find('.nothingfound').hide();
|
|
}
|
|
|
|
let _this = this;
|
|
|
|
if(scannedFolders.length) {
|
|
|
|
scannedFolders.forEach(function(f) {
|
|
|
|
var itemsLength = f.items.length,
|
|
name = _this.escapeHTML(f.name),
|
|
icon = '<span class="icon folder"></span>';
|
|
|
|
if(itemsLength) {
|
|
icon = '<span class="icon folder full"></span>';
|
|
}
|
|
|
|
if(itemsLength == 1) {
|
|
itemsLength += ' item';
|
|
}
|
|
else if(itemsLength > 1) {
|
|
itemsLength += ' items';
|
|
}
|
|
else {
|
|
itemsLength = 'Empty';
|
|
}
|
|
|
|
var folder = $('<li class="folders"><a href="'+ f.path +'" title="'+ f.path +'" class="folders">'+icon+'<div class="info"><span class="name">' + name + '</span> <span class="details">' + itemsLength + '</span></div></a></li>');
|
|
folder.appendTo(_this.fileList);
|
|
});
|
|
|
|
}
|
|
|
|
if(scannedFiles.length) {
|
|
|
|
scannedFiles.forEach(function(f) {
|
|
|
|
_this.addFile(f);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// Generate the breadcrumbs
|
|
|
|
var url = '';
|
|
|
|
if(this.filemanager.hasClass('searching')){
|
|
|
|
url = '<span>Search results: </span>';
|
|
this.fileList.removeClass('animated');
|
|
|
|
}
|
|
else {
|
|
|
|
this.fileList.addClass('animated');
|
|
|
|
this.breadcrumbsUrls.forEach(function (u, i) {
|
|
|
|
var name = u.split('/');
|
|
|
|
if (i !== _this.breadcrumbsUrls.length - 1) {
|
|
url += '<a href="'+u+'"><span class="folderName">' + name[name.length-1] + '</span></a> <span class="arrow">→</span> ';
|
|
}
|
|
else {
|
|
url += '<span class="folderName">' + name[name.length-1] + '</span>';
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
this.breadcrumbs.html('<a href="/"><i class="la la-home"></i><span class="folderName"> home</span></a>').append(url);
|
|
|
|
|
|
|
|
// Show the generated elements
|
|
|
|
this.fileList.animate({'display':'inline-block'});
|
|
|
|
}
|
|
|
|
|
|
// This function escapes special html characters in names
|
|
|
|
escapeHTML(text) {
|
|
return text.replace(/\&/g,'&').replace(/\</g,'<').replace(/\>/g,'>');
|
|
}
|
|
|
|
|
|
// Convert file sizes from bytes to human readable units
|
|
|
|
bytesToSize(bytes) {
|
|
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
if (bytes == 0) return '0 Bytes';
|
|
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
|
|
}
|
|
}
|
|
/*
|
|
export {
|
|
MediaModal
|
|
}
|
|
*/
|