This commit is contained in:
nnduc
2026-10-24 14:03:52 +07:00
commit 4fe046de58
4790 changed files with 311146 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
[upload]
title = Upload
choose = Choose a file to upload
info = Large pictures will be resized and compressed
error_filesize = File too large
error_failed = Upload failed
error_not_allowed = You are not allowed to upload a file
error_resource_constraint = You exceeded the quota
drag_drop = Drag and drop your file in this area
max_size = "Max file size %s"
upload_xmpp = Uploading to your account
encrypted_warning = You will send an unencrypted file in an encrypted discussion. Are you sure about it?
+111
View File
@@ -0,0 +1,111 @@
#upload img.preview_picture {
max-height: 35rem;
max-width: calc(100% - 2rem);
margin: 0 auto;
display: block;
}
#upload img.preview_picture[src] {
margin: 1rem auto;
border-radius: 0.5rem;
margin-top: 0;
}
#upload img.preview_picture[src] ~ li:not(.file) {
display: none;
}
#upload:not(.encrypted) #encrypted_warning {
display: none;
}
#upload div.drop {
width: auto;
margin: 0.25rem 1rem;
display: block;
box-sizing: border-box;
border-radius: 1.5rem;
min-height: 30rem;
background-color: rgba(0, 0, 0, 0.15);
border: 0.5rem solid transparent;
border-style: dashed;
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
}
#upload input[type=file] {
display: none;
}
#upload div.drop.dropped {
border-color: gray;
}
#upload li.file {
padding: 0;
}
#upload li.file span.primary {
margin-right: 1.5rem;
}
#upload li.file:not(.preview) span.primary {
display: none;
}
#upload + footer {
display: flex;
}
#upload_progress {
float: left;
text-align: left;
width: 100%;
flex: 1;
margin-top: 0.25rem;
}
#upload_progress li span:has(i:empty) {
display: none;
}
#upload_progress li {
padding: 0;
}
#upload_progress div {
margin-left: 0;
}
#upload_progress span.primary {
opacity: 0.5;
}
#upload_progress p {
font-size: 1.75rem;
opacity: 0.75;
}
body.dropped:after {
content: '';
width: 100%;
height: 100%;
display: block;
position: absolute;
z-index: 3;
box-sizing: border-box;
font-family: 'Material Symbols';
content: "\e2c6";
font-size: 10rem;
text-align: center;
color: white;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(1rem);
background-color: rgba(17, 17, 17, 0.75);
}
+453
View File
@@ -0,0 +1,453 @@
var Upload = {
xhr: null,
initiated: [],
attached: [],
failed: [],
progressed: [],
get: null,
prependName: null,
name: null,
file: null,
canvas: null,
thumbhash: null,
uploadButton: null,
init: function (appendDate) {
Upload.launchInitiated();
if (Upload.file) {
let splited = Upload.name.split('.');
let extension = splited.pop();
let name = splited.join('.');
if (Upload.prependName) {
name = Upload.prependName + '_' + name;
}
if (name.length > 128) {
name = name.substring(0, 32) + '_' + MovimUtils.hash(name);
}
if (appendDate) {
let now = new Date();
now = now.toISOString().replace(/[-:]/g, '_').replaceAll('_', '');
now = now.substring(0, now.length - 5);
name += '_' + now;
}
Upload.name = name + '.' + extension;
Upload_ajaxPrepare({
name: Upload.name,
size: Upload.file.size,
type: Upload.file.type
});
}
},
openFile: function () {
Upload.clear();
document.querySelector('input#file').click();
},
openImage: function () {
Upload.clear();
document.querySelector('input#image').click();
},
attach: function (func) {
if (typeof (func) === "function") {
this.attached.push(func);
}
},
initiate: function (func) {
if (typeof (func) === "function") {
this.initiated.push(func);
}
},
fail: function (func) {
if (typeof (func) === "function") {
this.failed.push(func);
}
},
progress: function (func) {
if (typeof (func) === "function") {
this.progressed.push(func);
}
},
launchInitiated: function () {
for (var i = 0; i < Upload.initiated.length; i++) {
Upload.initiated[i]();
}
},
launchAttached: function () {
for (var i = 0; i < Upload.attached.length; i++) {
Upload.attached[i]({
id: Upload.id,
thumbhash: Upload.thumbhash ?? null,
thumbhashWidth: Upload.canvas ? Upload.canvas.width : null,
thumbhashHeight: Upload.canvas ? Upload.canvas.height : null
});
}
},
launchFailed: function () {
for (var i = 0; i < Upload.failed.length; i++) {
Upload.failed[i]();
}
},
launchProgressed: function (percent) {
for (var i = 0; i < Upload.progressed.length; i++) {
Upload.progressed[i](percent);
}
},
preview: function (file) {
Upload.canvas = null;
Upload.uploadButton = document.querySelector('#upload_button');
var resolvedFile = file ? file : document.getElementById('file').files[0];
var resolvedFile = resolvedFile ? resolvedFile : document.getElementById('image').files[0];
Upload.name = resolvedFile.name;
Upload.check(resolvedFile);
},
check: function (file) {
if (!file.type.match(/image.*/)) {
console.log("Not a picture !");
Upload.prepare(file);
} else {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', function (ev) {
MovimUtils.getOrientation(file, function (orientation) {
Upload.compress(ev.target.result, file, orientation);
});
});
};
},
compress: function (src, file, orientation) {
Upload.setCompress(null);
var image = new Image();
image.addEventListener('load', function () {
if (file.size > SMALL_PICTURE_LIMIT) {
var limit = 1920;
var width = image.naturalWidth;
var height = image.naturalHeight;
var ratio = Math.min(limit / width, limit / height);
if (ratio < 1) {
width = Math.round(width * ratio);
height = Math.round(height * ratio);
}
Upload.canvas = document.createElement('canvas');
if (orientation > 4 && orientation < 9) {
Upload.canvas.width = height;
Upload.canvas.height = width;
} else {
Upload.canvas.width = width;
Upload.canvas.height = height;
}
ctx = Upload.canvas.getContext("2d");
MovimUtils.applyOrientation(ctx, orientation, width, height);
ctx.drawImage(image, 0, 0, width, height);
if (typeof Upload.canvas.toBlob == 'function') {
if (file.type != 'image/jpeg') {
Upload.name += '.jpg';
}
Upload.setCompress('photo_size_select_small');
Upload.canvas.toBlob(
function (blob) {
Upload.prepare(blob);
},
'image/jpeg',
0.85
);
} else {
Upload.prepare(file);
}
} else {
// Also create the canvas for other usages like drawing
Upload.canvas = document.createElement('canvas');
Upload.canvas.width = image.naturalWidth;
Upload.canvas.height = image.naturalHeight;
ctx = Upload.canvas.getContext("2d");
ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight);
// Always create a fresh Blob from the canvas for iOS Safari.
// The original File reference becomes invalid after async gaps,
// producing Content-Length: 0 uploads on Safari.
if (typeof Upload.canvas.toBlob == 'function') {
Upload.canvas.toBlob(
function (blob) {
Upload.prepare(blob);
},
file.type,
1.0
);
} else {
Upload.prepare(file);
}
}
});
image.src = src;
},
prepare: function (file) {
Upload.file = file;
var preview = document.querySelector('#upload img.preview_picture');
var fileInfo = document.querySelector('#upload li.file');
// If the preview system is there
if (preview) {
var toDraw = fileInfo.querySelector('span.primary');
fileInfo.classList.remove('preview');
fileInfo.querySelector('p.name').innerText = Upload.name;
var type = file.type ? file.type + ' · ' : '';
fileInfo.querySelector('p.desc').innerText = type + MovimUtils.humanFileSize(file.size);
if (Upload.file.type.match(/image.*/)) {
preview.src = URL.createObjectURL(Upload.file);
// Thumbhash
preview.addEventListener('load', e => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const scale = 100 / Math.max(Upload.canvas.width, Upload.canvas.height);
canvas.width = Math.round(Upload.canvas.width * scale);
canvas.height = Math.round(Upload.canvas.height * scale);
context.drawImage(preview, 0, 0, canvas.width, canvas.height);
const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
Upload.thumbhash = MovimUtils.arrayBufferToBase64(rgbaToThumbHash(pixels.width, pixels.height, pixels.data));
});
toDraw.addEventListener('click', e => {
Draw.initCanvas = Upload.canvas ?? preview;
Draw_ajaxHttpGet();
Dialog_ajaxClear();
Upload.abort();
});
fileInfo.classList.add('preview');
} else {
preview.src = '';
}
}
if (Upload.uploadButton) {
if (!document.querySelector('#upload p.limit') || document.querySelector('#upload p.limit').dataset.limit >= file.size) {
Upload.uploadButton.classList.remove('disabled');
} else {
Upload.uploadButton.classList.add('disabled');
}
}
},
request: function (route, id) {
Upload.xhr = new XMLHttpRequest();
Upload.id = id;
if (Upload.uploadButton) {
Upload.uploadButton.classList.add('disabled');
}
Upload.xhr.upload.addEventListener('progress', function (evt) {
var percent = Math.floor(evt.loaded / evt.total * 100);
Upload.launchProgressed(percent);
Upload.setProgress('arrow_upload_progress', percent == 100 ? '' : percent + '%');
}, false);
Upload.xhr.onreadystatechange = function () {
if (Upload.xhr.readyState == 4
&& (Upload.xhr.status >= 200 && Upload.xhr.status < 400)) {
Dialog.clear();
Upload.launchAttached();
Upload.clear();
if (Upload.uploadButton) {
Upload.uploadButton.classList.remove('disabled');
}
} else if (Upload.xhr.readyState == 4
&& (Upload.xhr.status >= 400 || Upload.xhr.status == 0)
&& Upload.file != null) {
Upload.launchFailed();
Upload_ajaxFailed();
Upload.setProgress('error', '');
if (Upload.uploadButton) {
Upload.uploadButton.classList.remove('disabled');
}
}
}
Upload.xhr.open("POST", route, true);
if (Upload.file != null) {
const formData = new FormData();
formData.append(Upload.id, Upload.file, Upload.name);
Upload.xhr.send(formData);
Upload.setProgress('arrow_upload_ready', '');
}
},
setCompress: function (icon) {
if (document.querySelector('#upload_progress')) {
document.querySelector('#upload_progress span.primary.compress i').innerText = icon;
}
},
setProgress: function (icon, text) {
if (document.querySelector('#upload_progress')) {
document.querySelector('#upload_progress span.primary.upload i').innerText = icon;
document.querySelector('#upload_progress li p').innerText = text;
}
},
abort: function () {
if (Upload.xhr) Upload.xhr.abort();
Upload.clear();
},
clear: function () {
if (document.getElementById('file')) {
document.getElementById('file').value = null;
}
if (document.getElementById('image')) {
document.getElementById('image').value = null;
}
Upload.name = null;
Upload.prependName = null;
Upload.file = null;
},
attachEvents: function () {
if (Upload.file) {
Upload.preview(Upload.file);
}
if (typeof Chat != 'undefined' && Chat.isEncrypted()) {
document.querySelector('#upload').classList.add('encrypted');
}
document.querySelector('#upload div.drop').addEventListener('drop', ev => {
ev.preventDefault();
if (ev.dataTransfer.items) {
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
if (ev.dataTransfer.items[i].kind === 'file') {
var file = ev.dataTransfer.items[i].getAsFile();
Upload.preview(file);
}
}
}
});
var dropArea = document.querySelector('#upload div.drop');
dropArea.querySelector('img.preview_picture')
.addEventListener('drop', ev => ev.preventDefault());
dropArea.addEventListener('dragover', ev => {
ev.preventDefault();
dropArea.classList.add('dropped');
}, false);
dropArea.addEventListener('drop', ev => {
ev.preventDefault();
dropArea.classList.remove('dropped');
}, false);
dropArea.addEventListener('dragleave', ev => {
ev.preventDefault();
dropArea.classList.remove('dropped');
}, false);
}
}
/**
* Handle the paste event
*/
MovimEvents.registerWindow('paste', 'upload', (e) => {
if (Upload.file != null) return;
const clipboardItems = e.clipboardData.items;
const items = [].slice
.call(clipboardItems)
.filter(function (item) {
return item.type.indexOf('image') !== -1;
});
if (items.length === 0) {
return;
}
const item = items[0];
Upload.file = item.getAsFile();
Upload_ajaxGetPanel();
});
/**
* Handle the global drop event
*/
MovimEvents.registerBody('dragover', 'upload', (ev) => {
if (document.getElementById('upload')) return;
ev.preventDefault();
if (ev.dataTransfer.items[0] && ev.dataTransfer.items[0].kind == 'file') {
document.body.classList.add('dropped');
}
});
MovimEvents.registerBody('drop', 'upload', (ev) => {
if (document.getElementById('upload')) return;
ev.preventDefault();
if (ev.dataTransfer.items.length > 0
&& ev.dataTransfer.items[0].kind === 'file') {
var file = ev.dataTransfer.items[0].getAsFile();
Upload.file = file;
Upload.name = file.name;
Upload_ajaxGetPanel();
}
document.body.classList.remove('dropped');
});
MovimEvents.registerBody('dragleave', 'upload', (ev) => {
if (document.getElementById('upload')) return;
ev.preventDefault();
document.body.classList.remove('dropped');
});