mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +02:00
got drag n drop to work with firefox -and- chrome and checking in before I manage to break it again
This commit is contained in:
parent
a0707e9d54
commit
a91041ae61
11 changed files with 148 additions and 171 deletions
|
@ -74,7 +74,6 @@ def extract_files(params, files = [])
|
||||||
files
|
files
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
post '/api/upload' do
|
post '/api/upload' do
|
||||||
require_api_credentials
|
require_api_credentials
|
||||||
files = extract_files params
|
files = extract_files params
|
||||||
|
|
|
@ -84,7 +84,7 @@ class Site < Sequel::Model
|
||||||
SCREENSHOT_RESOLUTIONS = ['540x405', '210x158', '100x100', '50x50']
|
SCREENSHOT_RESOLUTIONS = ['540x405', '210x158', '100x100', '50x50']
|
||||||
THUMBNAIL_RESOLUTIONS = ['210x158']
|
THUMBNAIL_RESOLUTIONS = ['210x158']
|
||||||
|
|
||||||
MAX_FILE_SIZE = 10**8 # 100 MB
|
MAX_FILE_SIZE = 10**8 # 100 MB, change dashboard.js dropzone file size limit if you change this
|
||||||
|
|
||||||
CLAMAV_THREAT_MATCHES = [
|
CLAMAV_THREAT_MATCHES = [
|
||||||
/^VBS/,
|
/^VBS/,
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
58
public/img/drag-drop.svg
Normal file
58
public/img/drag-drop.svg
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="67.754738mm"
|
||||||
|
height="3.4416525mm"
|
||||||
|
viewBox="0 0 67.754738 3.4416525"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||||
|
sodipodi:docname="drag-drop.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.1108586"
|
||||||
|
inkscape:cx="71.523663"
|
||||||
|
inkscape:cy="-55.772384"
|
||||||
|
inkscape:window-width="2490"
|
||||||
|
inkscape:window-height="1376"
|
||||||
|
inkscape:window-x="70"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-23.11286,-40.759304)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
|
||||||
|
x="22.954386"
|
||||||
|
y="43.43959"
|
||||||
|
id="text82970"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan82968"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#f6f0e6;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
x="22.954386"
|
||||||
|
y="43.43959">drag and drop files here to upload</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -123,8 +123,6 @@ function hideUploadProgress() {
|
||||||
$('#uploadingOverlay').css('display', 'none')
|
$('#uploadingOverlay').css('display', 'none')
|
||||||
}
|
}
|
||||||
|
|
||||||
allUploadsComplete = false
|
|
||||||
|
|
||||||
$('#createDir').on('shown', function () {
|
$('#createDir').on('shown', function () {
|
||||||
$('#newDirInput').focus();
|
$('#newDirInput').focus();
|
||||||
})
|
})
|
||||||
|
@ -147,167 +145,6 @@ function iconView() {
|
||||||
$('#filesDisplay').removeClass('list-view')
|
$('#filesDisplay').removeClass('list-view')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop handler function to get all files
|
|
||||||
async function getAllFileEntries(dataTransferItemList) {
|
|
||||||
let fileEntries = [];
|
|
||||||
// Use BFS to traverse entire directory/file structure
|
|
||||||
let queue = [];
|
|
||||||
for (let i = 0; i < dataTransferItemList.length; i++) {
|
|
||||||
queue.push(dataTransferItemList[i].webkitGetAsEntry());
|
|
||||||
}
|
|
||||||
while (queue.length > 0) {
|
|
||||||
let entry = queue.shift();
|
|
||||||
if (entry.isFile) {
|
|
||||||
fileEntries.push(entry);
|
|
||||||
} else if (entry.isDirectory) {
|
|
||||||
let reader = entry.createReader();
|
|
||||||
queue.push(...await readAllDirectoryEntries(reader));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all the entries (files or sub-directories) in a directory
|
|
||||||
async function readAllDirectoryEntries(directoryReader) {
|
|
||||||
let entries = [];
|
|
||||||
let readEntries = await readEntriesPromise(directoryReader);
|
|
||||||
while (readEntries.length > 0) {
|
|
||||||
entries.push(...readEntries);
|
|
||||||
readEntries = await readEntriesPromise(directoryReader);
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap readEntries in a promise
|
|
||||||
async function readEntriesPromise(directoryReader) {
|
|
||||||
try {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
directoryReader.readEntries(resolve, reject);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadFile(file, dir, additionalFormData) {
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
// Append additional form data (from other input fields) to each file's FormData
|
|
||||||
for (const [key, value] of Object.entries(additionalFormData)) {
|
|
||||||
formData.append(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var modifiedFileName;
|
|
||||||
|
|
||||||
if (file.webkitRelativePath === '') {
|
|
||||||
modifiedFileName = file.name;
|
|
||||||
} else {
|
|
||||||
modifiedFileName = file.webkitRelativePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir && dir !== '/') {
|
|
||||||
modifiedFileName = dir.replace(/^\//, '') + '/' + modifiedFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedFileName = modifiedFileName.replace(/^\//, '');
|
|
||||||
|
|
||||||
console.log('modifiedFileName: '+modifiedFileName)
|
|
||||||
formData.append(modifiedFileName, file, modifiedFileName);
|
|
||||||
|
|
||||||
$('#uploadFileName').text(modifiedFileName).prepend('<i class="icon-file"></i> ');
|
|
||||||
|
|
||||||
// Send the FormData with the file and additional data
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.result == 'error') {
|
|
||||||
fileUploadErrorCount++;
|
|
||||||
if(fileUploadErrorCount == 1) {
|
|
||||||
alertType('error');
|
|
||||||
}
|
|
||||||
alertAdd(result.message);
|
|
||||||
} else {
|
|
||||||
fileUploadSuccessCount++;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processEntry(entry, dir, additionalFormData) {
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
entry.file((file) => {
|
|
||||||
uploadFile(file, dir, additionalFormData).then(resolve);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadFiles(fileEntries) {
|
|
||||||
alertClear();
|
|
||||||
|
|
||||||
// Collect additional form data
|
|
||||||
const form = document.getElementById('dropzone');
|
|
||||||
let additionalFormData = {};
|
|
||||||
for (let i = 0; i < form.elements.length; i++) {
|
|
||||||
const input = form.elements[i];
|
|
||||||
if (input.name && input.type !== "file") { // Avoid file inputs
|
|
||||||
additionalFormData[input.name] = input.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dir = additionalFormData['dir'] || '';
|
|
||||||
|
|
||||||
var totalFiles = fileEntries.length;
|
|
||||||
$('#progressBar').css('display', 'block')
|
|
||||||
|
|
||||||
fileUploadCount = 0
|
|
||||||
fileUploadErrorCount = 0
|
|
||||||
fileUploadSuccessCount = 0
|
|
||||||
|
|
||||||
for (let entry of fileEntries) {
|
|
||||||
await processEntry(entry, dir, additionalFormData);
|
|
||||||
fileUploadCount++;
|
|
||||||
var progress = (fileUploadCount / totalFiles) * 100;
|
|
||||||
$('#uploadingProgress').css('width', progress+'%');
|
|
||||||
}
|
|
||||||
|
|
||||||
allUploadsComplete = true
|
|
||||||
|
|
||||||
if(fileUploadErrorCount > 0) {
|
|
||||||
alertAdd(fileUploadSuccessCount+'/'+fileUploadCount+' files uploaded successfully.');
|
|
||||||
} else {
|
|
||||||
alertType('success')
|
|
||||||
alertAdd(fileUploadSuccessCount+' files uploaded successfully.');
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadDashboardFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
function reInitDashboardFiles() {
|
|
||||||
var elDrop = document.getElementById('dropzone');
|
|
||||||
|
|
||||||
elDrop.addEventListener('dragover', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
elDrop.addEventListener('drop', async function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
showUploadProgress();
|
|
||||||
let items = await getAllFileEntries(event.dataTransfer.items);
|
|
||||||
await uploadFiles(items);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadDashboardFiles() {
|
|
||||||
$.get('/dashboard/files?dir='+encodeURIComponent($("#dir").val()), function(data) {
|
|
||||||
$('#filesDisplay').html(data);
|
|
||||||
reInitDashboardFiles();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function alertAdd(text) {
|
function alertAdd(text) {
|
||||||
var a = $('#alertDialogue');
|
var a = $('#alertDialogue');
|
||||||
a.css('display', 'block');
|
a.css('display', 'block');
|
||||||
|
@ -327,5 +164,86 @@ function alertType(type){
|
||||||
a.addClass('alert-'+type);
|
a.addClass('alert-'+type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var processedFiles = 0;
|
||||||
|
var uploadedFiles = 0;
|
||||||
|
var uploadedFileErrors = 0;
|
||||||
|
|
||||||
|
function joinPaths(...paths) {
|
||||||
|
return paths
|
||||||
|
.map(path => path.replace(/(^\/|\/$)/g, ''))
|
||||||
|
.filter(path => path !== '')
|
||||||
|
.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function reInitDashboardFiles() {
|
||||||
|
new Dropzone("#uploads", {
|
||||||
|
url: "/api/upload",
|
||||||
|
paramName: 'file',
|
||||||
|
dictDefaultMessage: "",
|
||||||
|
uploadMultiple: false,
|
||||||
|
parallelUploads: 1,
|
||||||
|
maxFilesize: 104857600, // 100MB
|
||||||
|
clickable: false,
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this.on("processing", function(file) {
|
||||||
|
var dir = $('#uploads input[name="dir"]').val();
|
||||||
|
if(file.fullPath) {
|
||||||
|
this.options.paramName = joinPaths(dir,file.fullPath);
|
||||||
|
} else {
|
||||||
|
this.options.paramName = joinPaths(dir, file.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
processedFiles++;
|
||||||
|
$('#uploadFileName').text(this.options.paramName).prepend('<i class="icon-file"></i> ');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("success", function(file) {
|
||||||
|
uploadedFiles++;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("error", function(file, message) {
|
||||||
|
uploadedFiles++;
|
||||||
|
uploadedFileErrors++;
|
||||||
|
alertType('error');
|
||||||
|
if (message && message.message) {
|
||||||
|
alertAdd(message.message);
|
||||||
|
} else {
|
||||||
|
alertAdd(this.options.paramName+' failed to upload');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("queuecomplete", function() {
|
||||||
|
hideUploadProgress();
|
||||||
|
if(uploadedFileErrors > 0) {
|
||||||
|
alertType('error');
|
||||||
|
alertAdd(uploadedFiles-uploadedFileErrors+'/'+uploadedFiles+' files uploaded successfully');
|
||||||
|
} else {
|
||||||
|
alertType('success');
|
||||||
|
alertAdd(uploadedFiles+' files uploaded successfully');
|
||||||
|
}
|
||||||
|
reloadDashboardFiles();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("addedfiles", function(files) {
|
||||||
|
uploadedFiles = 0;
|
||||||
|
uploadedFileErrors = 0;
|
||||||
|
alertClear();
|
||||||
|
showUploadProgress();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadDashboardFiles() {
|
||||||
|
$.get('/dashboard/files?dir='+encodeURIComponent($("#dir").val()), function(data) {
|
||||||
|
$('#filesDisplay').html(data);
|
||||||
|
reInitDashboardFiles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// for first time load
|
// for first time load
|
||||||
reInitDashboardFiles();
|
reInitDashboardFiles();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
2
public/js/dropzone-min.js
vendored
Normal file
2
public/js/dropzone-min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dropzone-min.js.map
Normal file
1
public/js/dropzone-min.js.map
Normal file
File diff suppressed because one or more lines are too long
1
public/js/dropzone.min.js
vendored
1
public/js/dropzone.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -352,7 +352,7 @@
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
.files .list .upload-Boundary.with-instruction {
|
.files .list .upload-Boundary.with-instruction {
|
||||||
background: url(/img/drag-drop.png) no-repeat center center;
|
background: url(/img/drag-drop.svg) no-repeat center center;
|
||||||
|
|
||||||
@media (max-device-width:480px), screen and (max-width:800px) {
|
@media (max-device-width:480px), screen and (max-width:800px) {
|
||||||
background: 0;
|
background: 0;
|
||||||
|
|
|
@ -40,11 +40,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
<form action="/site_files/upload" id="dropzone">
|
<form action="/site_files/upload" class="dropzone" id="uploads">
|
||||||
<div class="dz-message" style="display: none"></div>
|
<div class="dz-message" style="display: none"></div>
|
||||||
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
|
||||||
<input name="dir" type="hidden" value="<%= @dir %>">
|
<input name="dir" type="hidden" value="<%= @dir %>">
|
||||||
<div class="upload-Boundary <%= @file_list.length <= 5 ? 'with-instruction' : '' %>">
|
<div class="upload-Boundary with-instruction">
|
||||||
<% @file_list.each do |file| %>
|
<% @file_list.each do |file| %>
|
||||||
<div class="file filehover">
|
<div class="file filehover">
|
||||||
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
|
<% if file[:is_html] && current_site.screenshot_exists?(file[:path], '210x158') %>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<script src="/js/dropzone-min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.dz-default {
|
.dz-default {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -15,7 +16,6 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="header-Outro with-site-image dashboard">
|
<div class="header-Outro with-site-image dashboard">
|
||||||
<div class="row content wide">
|
<div class="row content wide">
|
||||||
<div class="col col-50 signup-Area">
|
<div class="col col-50 signup-Area">
|
||||||
|
|
Loading…
Add table
Reference in a new issue