mirror of https://github.com/zulip/zulip.git
316 lines
11 KiB
JavaScript
316 lines
11 KiB
JavaScript
"use strict";
|
|
|
|
const Uppy = require("@uppy/core");
|
|
const ProgressBar = require("@uppy/progress-bar");
|
|
const XHRUpload = require("@uppy/xhr-upload");
|
|
|
|
exports.make_upload_absolute = function (uri) {
|
|
if (uri.startsWith(compose.uploads_path)) {
|
|
// Rewrite the URI to a usable link
|
|
return compose.uploads_domain + uri;
|
|
}
|
|
return uri;
|
|
};
|
|
|
|
// Show the upload button only if the browser supports it.
|
|
exports.feature_check = function (upload_button) {
|
|
if (window.XMLHttpRequest && new XMLHttpRequest().upload) {
|
|
upload_button.removeClass("notdisplayed");
|
|
}
|
|
};
|
|
exports.get_translated_status = function (file) {
|
|
const status = i18n.t("Uploading __filename__…", {filename: file.name});
|
|
return "[" + status + "]()";
|
|
};
|
|
|
|
exports.get_item = function (key, config) {
|
|
if (!config) {
|
|
throw Error("Missing config");
|
|
}
|
|
if (config.mode === "compose") {
|
|
switch (key) {
|
|
case "textarea":
|
|
return $("#compose-textarea");
|
|
case "send_button":
|
|
return $("#compose-send-button");
|
|
case "send_status_identifier":
|
|
return "#compose-send-status";
|
|
case "send_status":
|
|
return $("#compose-send-status");
|
|
case "send_status_close_button":
|
|
return $(".compose-send-status-close");
|
|
case "send_status_message":
|
|
return $("#compose-error-msg");
|
|
case "file_input_identifier":
|
|
return "#file_input";
|
|
case "source":
|
|
return "compose-file-input";
|
|
case "drag_drop_container":
|
|
return $("#compose");
|
|
default:
|
|
throw Error(`Invalid key name for mode "${config.mode}"`);
|
|
}
|
|
} else if (config.mode === "edit") {
|
|
if (!config.row) {
|
|
throw Error("Missing row in config");
|
|
}
|
|
switch (key) {
|
|
case "textarea":
|
|
return $("#message_edit_content_" + config.row);
|
|
case "send_button":
|
|
return $("#message_edit_content_" + config.row)
|
|
.closest("#message_edit_form")
|
|
.find(".message_edit_save");
|
|
case "send_status_identifier":
|
|
return "#message-edit-send-status-" + config.row;
|
|
case "send_status":
|
|
return $("#message-edit-send-status-" + config.row);
|
|
case "send_status_close_button":
|
|
return $("#message-edit-send-status-" + config.row).find(".send-status-close");
|
|
case "send_status_message":
|
|
return $("#message-edit-send-status-" + config.row).find(".error-msg");
|
|
case "file_input_identifier":
|
|
return "#message_edit_file_input_" + config.row;
|
|
case "source":
|
|
return "message-edit-file-input";
|
|
case "drag_drop_container":
|
|
return $("#message_edit_form");
|
|
default:
|
|
throw Error(`Invalid key name for mode "${config.mode}"`);
|
|
}
|
|
} else {
|
|
throw Error("Invalid upload mode!");
|
|
}
|
|
};
|
|
|
|
exports.hide_upload_status = function (config) {
|
|
exports.get_item("send_button", config).prop("disabled", false);
|
|
exports.get_item("send_status", config).removeClass("alert-info").hide();
|
|
};
|
|
|
|
exports.show_error_message = function (config, message) {
|
|
if (!message) {
|
|
message = i18n.t("An unknown error occurred.");
|
|
}
|
|
exports.get_item("send_button", config).prop("disabled", false);
|
|
exports
|
|
.get_item("send_status", config)
|
|
.addClass("alert-error")
|
|
.removeClass("alert-info")
|
|
.show();
|
|
exports.get_item("send_status_message", config).text(message);
|
|
};
|
|
|
|
exports.upload_files = function (uppy, config, files) {
|
|
if (files.length === 0) {
|
|
return;
|
|
}
|
|
if (page_params.max_file_upload_size_mib === 0) {
|
|
exports.show_error_message(
|
|
config,
|
|
i18n.t("File and image uploads have been disabled for this organization."),
|
|
);
|
|
return;
|
|
}
|
|
exports.get_item("send_button", config).prop("disabled", true);
|
|
exports
|
|
.get_item("send_status", config)
|
|
.addClass("alert-info")
|
|
.removeClass("alert-error")
|
|
.show();
|
|
exports.get_item("send_status_message", config).html($("<p>").text(i18n.t("Uploading…")));
|
|
exports.get_item("send_status_close_button", config).one("click", () => {
|
|
uppy.getFiles().forEach((file) => {
|
|
compose_ui.replace_syntax(
|
|
exports.get_translated_status(file),
|
|
"",
|
|
exports.get_item("textarea", config),
|
|
);
|
|
});
|
|
compose_ui.autosize_textarea();
|
|
uppy.cancelAll();
|
|
exports.get_item("textarea", config).trigger("focus");
|
|
setTimeout(() => {
|
|
exports.hide_upload_status(config);
|
|
}, 500);
|
|
});
|
|
|
|
for (const file of files) {
|
|
try {
|
|
compose_ui.insert_syntax_and_focus(
|
|
exports.get_translated_status(file),
|
|
exports.get_item("textarea", config),
|
|
);
|
|
compose_ui.autosize_textarea();
|
|
uppy.addFile({
|
|
source: exports.get_item("source", config),
|
|
name: file.name,
|
|
type: file.type,
|
|
data: file,
|
|
});
|
|
} catch (error) {
|
|
// Errors are handled by info-visible and upload-error event callbacks.
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
exports.setup_upload = function (config) {
|
|
const uppy = Uppy({
|
|
debug: false,
|
|
autoProceed: true,
|
|
restrictions: {
|
|
maxFileSize: page_params.max_file_upload_size_mib * 1024 * 1024,
|
|
},
|
|
locale: {
|
|
strings: {
|
|
exceedsSize: i18n.t("This file exceeds maximum allowed size of"),
|
|
failedToUpload: i18n.t("Failed to upload %{file}"),
|
|
},
|
|
},
|
|
});
|
|
uppy.setMeta({
|
|
csrfmiddlewaretoken: csrf_token,
|
|
});
|
|
uppy.use(XHRUpload, {
|
|
endpoint: "/json/user_uploads",
|
|
formData: true,
|
|
fieldName: "file",
|
|
// Number of concurrent uploads
|
|
limit: 5,
|
|
locale: {
|
|
strings: {
|
|
timedOut: i18n.t("Upload stalled for %{seconds} seconds, aborting."),
|
|
},
|
|
},
|
|
});
|
|
|
|
uppy.use(ProgressBar, {
|
|
target: exports.get_item("send_status_identifier", config),
|
|
hideAfterFinish: false,
|
|
});
|
|
|
|
$("body").on("change", exports.get_item("file_input_identifier", config), (event) => {
|
|
const files = event.target.files;
|
|
exports.upload_files(uppy, config, files);
|
|
event.target.value = "";
|
|
});
|
|
|
|
const drag_drop_container = exports.get_item("drag_drop_container", config);
|
|
drag_drop_container.on("dragover", (event) => event.preventDefault());
|
|
drag_drop_container.on("dragenter", (event) => event.preventDefault());
|
|
|
|
drag_drop_container.on("drop", (event) => {
|
|
event.preventDefault();
|
|
const files = event.originalEvent.dataTransfer.files;
|
|
exports.upload_files(uppy, config, files);
|
|
});
|
|
|
|
drag_drop_container.on("paste", (event) => {
|
|
const clipboard_data = event.clipboardData || event.originalEvent.clipboardData;
|
|
if (!clipboard_data) {
|
|
return;
|
|
}
|
|
const items = clipboard_data.items;
|
|
const files = [];
|
|
for (const item of items) {
|
|
if (item.kind !== "file") {
|
|
continue;
|
|
}
|
|
const file = item.getAsFile();
|
|
files.push(file);
|
|
}
|
|
exports.upload_files(uppy, config, files);
|
|
});
|
|
|
|
uppy.on("upload-success", (file, response) => {
|
|
const uri = response.body.uri;
|
|
if (uri === undefined) {
|
|
return;
|
|
}
|
|
const split_uri = uri.split("/");
|
|
const filename = split_uri[split_uri.length - 1];
|
|
if (config.mode === "compose" && !compose_state.composing()) {
|
|
compose_actions.start("stream");
|
|
}
|
|
const absolute_uri = exports.make_upload_absolute(uri);
|
|
const filename_uri = "[" + filename + "](" + absolute_uri + ")";
|
|
compose_ui.replace_syntax(
|
|
exports.get_translated_status(file),
|
|
filename_uri,
|
|
exports.get_item("textarea", config),
|
|
);
|
|
compose_ui.autosize_textarea();
|
|
});
|
|
|
|
uppy.on("complete", () => {
|
|
let uploads_in_progress = false;
|
|
uppy.getFiles().forEach((file) => {
|
|
if (file.progress.uploadComplete) {
|
|
// The uploaded files should be removed since uppy don't allow files in the store
|
|
// to be re-uploaded again.
|
|
uppy.removeFile(file.id);
|
|
} else {
|
|
// Happens when user tries to upload files when there is already an existing batch
|
|
// being uploaded. So when the first batch of files complete, the second batch would
|
|
// still be in progress.
|
|
uploads_in_progress = true;
|
|
}
|
|
});
|
|
|
|
const has_errors = exports.get_item("send_status", config).hasClass("alert-error");
|
|
if (!uploads_in_progress && !has_errors) {
|
|
setTimeout(() => {
|
|
exports.hide_upload_status(config);
|
|
}, 500);
|
|
}
|
|
});
|
|
|
|
uppy.on("info-visible", () => {
|
|
const info = uppy.getState().info;
|
|
if (info.type === "error" && info.message === "No Internet connection") {
|
|
// server_events already handles the case of no internet.
|
|
return;
|
|
}
|
|
|
|
if (info.type === "error" && info.details === "Upload Error") {
|
|
// The server errors come under 'Upload Error'. But we can't handle them
|
|
// here because info object don't contain response.body.msg received from
|
|
// the server. Server errors are hence handled by on('upload-error').
|
|
return;
|
|
}
|
|
|
|
if (info.type === "error") {
|
|
// The remaining errors are mostly frontend errors like file being too large
|
|
// for upload.
|
|
uppy.cancelAll();
|
|
exports.show_error_message(config, info.message);
|
|
}
|
|
});
|
|
|
|
uppy.on("upload-error", (file, error, response) => {
|
|
const message = response ? response.body.msg : null;
|
|
uppy.cancelAll();
|
|
exports.show_error_message(config, message);
|
|
compose_ui.replace_syntax(
|
|
exports.get_translated_status(file),
|
|
"",
|
|
exports.get_item("textarea", config),
|
|
);
|
|
compose_ui.autosize_textarea();
|
|
});
|
|
|
|
uppy.on("restriction-failed", (file) => {
|
|
compose_ui.replace_syntax(
|
|
exports.get_translated_status(file),
|
|
"",
|
|
exports.get_item("textarea", config),
|
|
);
|
|
compose_ui.autosize_textarea();
|
|
});
|
|
|
|
return uppy;
|
|
};
|
|
|
|
window.upload = exports;
|