FEATURE: Uppy image uploader with UppyUploadMixin (#13656)

This PR adds the first use of Uppy in our codebase, hidden behind a enable_experimental_image_uploader site setting. When the setting is enabled only the user card background uploader will use the new uppy-image-uploader component added in this PR.

I've introduced an UppyUpload mixin that has feature parity with the existing Upload mixin, and improves it slightly to deal with multiple/single file distinctions and validations better. For now, this just supports the XHRUpload plugin for uppy, which keeps our existing POST to /uploads.json.
This commit is contained in:
Martin Brennan 2021-07-13 12:22:00 +10:00 committed by GitHub
parent e2d04a8592
commit 7911124d3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1071 additions and 87 deletions

View File

@ -45,10 +45,23 @@ define("@popperjs/core", ["exports"], function (__exports__) {
__exports__.popperGenerator = window.Popper.popperGenerator;
});
define("uppy", ["exports"], function (__exports__) {
define("@uppy/core", ["exports"], function (__exports__) {
__exports__.default = window.Uppy.Core;
__exports__.Plugin = window.Uppy.Plugin;
__exports__.XHRUpload = window.Uppy.XHRUpload;
__exports__.AwsS3 = window.Uppy.AwsS3;
__exports__.AwsS3Multipart = window.Uppy.AwsS3Multipart;
});
define("@uppy/aws-s3", ["exports"], function (__exports__) {
__exports__.default = window.Uppy.AwsS3;
});
define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) {
__exports__.default = window.Uppy.AwsS3Multipart;
});
define("@uppy/xhr-upload", ["exports"], function (__exports__) {
__exports__.default = window.Uppy.XHRUpload;
});
define("@uppy/drop-target", ["exports"], function (__exports__) {
__exports__.default = window.Uppy.DropTarget;
});

View File

@ -0,0 +1,133 @@
import Component from "@ember/component";
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
import { getURLWithCDN } from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import lightbox from "discourse/lib/lightbox";
import { next } from "@ember/runloop";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default Component.extend(UppyUploadMixin, {
classNames: ["image-uploader"],
loadingLightbox: false,
init() {
this._super(...arguments);
this._applyLightbox();
},
willDestroyElement() {
this._super(...arguments);
const elem = $("a.lightbox");
if (elem && typeof elem.magnificPopup === "function") {
$("a.lightbox").magnificPopup("close");
}
},
@discourseComputed("imageUrl", "placeholderUrl")
showingPlaceholder(imageUrl, placeholderUrl) {
return !imageUrl && placeholderUrl;
},
@discourseComputed("placeholderUrl")
placeholderStyle(url) {
if (isEmpty(url)) {
return "".htmlSafe();
}
return `background-image: url(${url})`.htmlSafe();
},
@discourseComputed("imageUrl")
imageCDNURL(url) {
if (isEmpty(url)) {
return "".htmlSafe();
}
return getURLWithCDN(url);
},
@discourseComputed("imageCDNURL")
backgroundStyle(url) {
return `background-image: url(${url})`.htmlSafe();
},
@discourseComputed("imageUrl")
imageBaseName(imageUrl) {
if (isEmpty(imageUrl)) {
return;
}
return imageUrl.split("/").slice(-1)[0];
},
validateUploadedFilesOptions() {
return { imagesOnly: true };
},
uploadDone(upload) {
this.setProperties({
imageUrl: upload.url,
imageId: upload.id,
imageFilesize: upload.human_filesize,
imageFilename: upload.original_filename,
imageWidth: upload.width,
imageHeight: upload.height,
});
this._applyLightbox();
if (this.onUploadDone) {
this.onUploadDone(upload);
}
},
_openLightbox() {
next(() =>
$(this.element.querySelector("a.lightbox")).magnificPopup("open")
);
},
_applyLightbox() {
if (this.imageUrl) {
next(() => lightbox(this.element, this.siteSettings));
}
},
actions: {
toggleLightbox() {
if (this.imageFilename) {
this._openLightbox();
} else {
this.set("loadingLightbox", true);
ajax(`/uploads/lookup-metadata`, {
type: "POST",
data: { url: this.imageUrl },
})
.then((json) => {
this.setProperties({
imageFilename: json.original_filename,
imageFilesize: json.human_filesize,
imageWidth: json.width,
imageHeight: json.height,
});
this._openLightbox();
this.set("loadingLightbox", false);
})
.catch(popupAjaxError);
}
},
trash() {
this.setProperties({ imageUrl: null, imageId: null });
// uppy needs to be reset to allow for more uploads
this._reset();
if (this.onUploadDeleted) {
this.onUploadDeleted();
}
},
},
});

View File

@ -57,6 +57,10 @@ export default Controller.extend({
"model.can_upload_user_card_background"
),
experimentalUserCardImageUpload: readOnly(
"siteSettings.enable_experimental_image_uploader"
),
actions: {
showFeaturedTopicModal() {
showModal("feature-topic-on-profile", {

View File

@ -29,30 +29,28 @@ export function validateUploadedFiles(files, opts) {
}
const upload = files[0];
return validateUploadedFile(upload, opts);
}
export function validateUploadedFile(file, opts) {
// CHROME ONLY: if the image was pasted, sets its name to a default one
if (typeof Blob !== "undefined" && typeof File !== "undefined") {
if (
upload instanceof Blob &&
!(upload instanceof File) &&
upload.type === "image/png"
file instanceof Blob &&
!(file instanceof File) &&
file.type === "image/png"
) {
upload.name = "image.png";
file.name = "image.png";
}
}
opts = opts || {};
opts.type = uploadTypeFromFileName(upload.name);
opts.type = uploadTypeFromFileName(file.name);
return validateUploadedFile(upload, opts);
}
function validateUploadedFile(file, opts) {
if (opts.skipValidation) {
return true;
}
opts = opts || {};
let user = opts.user;
let staff = user && user.staff;
@ -283,27 +281,14 @@ export function getUploadMarkdown(upload) {
export function displayErrorForUpload(data, siteSettings) {
if (data.jqXHR) {
switch (data.jqXHR.status) {
// didn't get headers from server, or browser refuses to tell us
case 0:
bootbox.alert(I18n.t("post.errors.upload"));
return;
// entity too large, usually returned from the web server
case 413:
const type = uploadTypeFromFileName(data.files[0].name);
const max_size_kb = siteSettings[`max_${type}_size_kb`];
bootbox.alert(I18n.t("post.errors.file_too_large", { max_size_kb }));
return;
// the error message is provided by the server
case 422:
if (data.jqXHR.responseJSON.message) {
bootbox.alert(data.jqXHR.responseJSON.message);
} else {
bootbox.alert(data.jqXHR.responseJSON.errors.join("\n"));
}
return;
const didError = displayErrorByResponseStatus(
data.jqXHR.status,
data.jqXHR.responseJSON,
data.files[0].name,
siteSettings
);
if (didError) {
return;
}
} else if (data.errors && data.errors.length > 0) {
bootbox.alert(data.errors.join("\n"));
@ -312,3 +297,49 @@ export function displayErrorForUpload(data, siteSettings) {
// otherwise, display a generic error message
bootbox.alert(I18n.t("post.errors.upload"));
}
export function displayErrorForUppyUpload(response, fileName, siteSettings) {
if (response.body.errors && response.body.errors.length > 0) {
bootbox.alert(response.body.errors.join("\n"));
return;
} else {
const didError = displayErrorByResponseStatus(
response.status,
response.body,
fileName,
siteSettings
);
if (didError) {
return;
}
}
// otherwise, display a generic error message
bootbox.alert(I18n.t("post.errors.upload"));
}
function displayErrorByResponseStatus(status, body, fileName, siteSettings) {
switch (status) {
// didn't get headers from server, or browser refuses to tell us
case 0:
bootbox.alert(I18n.t("post.errors.upload"));
return true;
// entity too large, usually returned from the web server
case 413:
const type = uploadTypeFromFileName(fileName);
const max_size_kb = siteSettings[`max_${type}_size_kb`];
bootbox.alert(I18n.t("post.errors.file_too_large", { max_size_kb }));
return true;
// the error message is provided by the server
case 422:
if (body.message) {
bootbox.alert(body.message);
} else {
bootbox.alert(body.errors.join("\n"));
}
return true;
}
return;
}

View File

@ -0,0 +1,179 @@
import Mixin from "@ember/object/mixin";
import {
displayErrorForUppyUpload,
validateUploadedFile,
} from "discourse/lib/uploads";
import { deepMerge } from "discourse-common/lib/object";
import getUrl from "discourse-common/lib/get-url";
import I18n from "I18n";
import Uppy from "@uppy/core";
import DropTarget from "@uppy/drop-target";
import XHRUpload from "@uppy/xhr-upload";
import { on } from "discourse-common/utils/decorators";
import { warn } from "@ember/debug";
export default Mixin.create({
uploading: false,
uploadProgress: 0,
uppyInstance: null,
autoStartUploads: true,
id: null,
// TODO (martin): this is only used in one place, consider just using
// form data/meta instead uploadUrlParams: "&for_site_setting=true",
uploadUrlParams: "",
// TODO (martin): currently used for backups to turn on auto upload and PUT/XML requests
// and for emojis to do sequential uploads, when we get to replacing those
// with uppy make sure this is used when initializing uppy
uploadOptions() {
return {};
},
uploadDone() {
warn("You should implement `uploadDone`", {
id: "discourse.upload.missing-upload-done",
});
},
validateUploadedFilesOptions() {
return {};
},
@on("willDestroyElement")
_destroy() {
this.messageBus && this.messageBus.unsubscribe("/uploads/" + this.type);
this.uppyInstance && this.uppyInstance.close();
},
@on("didInsertElement")
_initialize() {
this.set("fileInputEl", this.element.querySelector(".hidden-upload-field"));
this.set("allowMultipleFiles", this.fileInputEl.multiple);
this._bindFileInputChangeListener();
if (!this.id) {
warn(
"uppy needs a unique id, pass one in to the component implementing this mixin",
{
id: "discourse.upload.missing-id",
}
);
}
this.set(
"uppyInstance",
new Uppy({
id: this.id,
autoProceed: this.autoStartUploads,
// need to use upload_type because uppy overrides type with the
// actual file type
meta: deepMerge({ upload_type: this.type }, this.data || {}),
onBeforeFileAdded: (currentFile) => {
const validationOpts = deepMerge(
{
bypassNewUserRestriction: true,
user: this.currentUser,
siteSettings: this.siteSettings,
},
this.validateUploadedFilesOptions()
);
const isValid = validateUploadedFile(currentFile, validationOpts);
this.setProperties({ uploadProgress: 0, uploading: isValid });
return isValid;
},
onBeforeUpload: (files) => {
let tooMany = false;
const fileCount = Object.keys(files).length;
const maxFiles = this.getWithDefault(
"maxFiles",
this.siteSettings.simultaneous_uploads
);
if (this.allowMultipleFiles) {
tooMany = maxFiles > 0 && fileCount > maxFiles;
} else {
tooMany = fileCount > 1;
}
if (tooMany) {
bootbox.alert(
I18n.t("post.errors.too_many_dragged_and_dropped_files", {
count: this.allowMultipleFiles ? maxFiles : 1,
})
);
this._reset();
return false;
}
},
})
);
this.uppyInstance.use(DropTarget, { target: this.element });
this.uppyInstance.on("progress", (progress) => {
this.set("uploadProgress", progress);
});
this.uppyInstance.on("upload-success", (_file, response) => {
this.uploadDone(response.body);
this._reset();
});
this.uppyInstance.on("upload-error", (file, error, response) => {
displayErrorForUppyUpload(response, file.name, this.siteSettings);
this._reset();
});
// later we will use the uppy direct s3 uploader based on enable_s3_uploads,
// for now we always just use XHR uploads
this._useXHRUploads();
},
_useXHRUploads() {
this.uppyInstance.use(XHRUpload, {
endpoint: this._xhrUploadUrl(),
headers: {
"X-CSRF-Token": this.session.get("csrfToken"),
},
});
},
_xhrUploadUrl() {
return (
getUrl(this.getWithDefault("uploadUrl", "/uploads")) +
".json?client_id=" +
(this.messageBus && this.messageBus.clientId) +
this.uploadUrlParams
);
},
_bindFileInputChangeListener() {
this.fileInputEl.addEventListener("change", (event) => {
const files = Array.from(event.target.files);
files.forEach((file) => {
try {
this.uppyInstance.addFile({
source: `${this.id} file input`,
name: file.name,
type: file.type,
data: file,
});
} catch (err) {
warn(`error adding files to uppy: ${err}`, {
id: "discourse.upload.uppy-add-files-error",
});
}
});
});
},
_reset() {
this.uppyInstance && this.uppyInstance.reset();
this.setProperties({ uploading: false, uploadProgress: 0 });
},
});

View File

@ -0,0 +1,45 @@
<div class="uploaded-image-preview input-xxlarge" style={{backgroundStyle}}>
{{#if showingPlaceholder}}
<div class="placeholder-overlay" style={{placeholderStyle}}></div>
{{/if}}
<div class="image-upload-controls">
<label class="btn btn-default pad-left no-text {{if uploading "disabled"}}">
{{d-icon "far-image"}}
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*">
</label>
{{#if imageUrl}}
{{d-button
action=(action "trash")
class="btn-danger pad-left no-text"
icon="far-trash-alt"
type="button"
}}
{{d-button
icon="discourse-expand"
title="expand"
type="button"
class="image-uploader-lightbox-btn no-text"
action=(action "toggleLightbox")
disabled=loadingLightbox
}}
{{/if}}
<span class="btn {{unless uploading "hidden"}}">{{i18n "upload_selector.uploading"}} {{uploadProgress}}%</span>
</div>
{{#if imageUrl}}
<a class="lightbox"
href={{imageCDNURL}}
title={{imageFilename}}
rel="nofollow ugc noopener">
<div class="meta">
<span class="informations">
{{imageWidth}}x{{imageHeight}} {{imageFilesize}}
</span>
</div>
</a>
{{/if}}
</div>

View File

@ -59,8 +59,15 @@
<div class="control-group pref-profile-bg">
<label class="control-label">{{i18n "user.change_card_background.title"}}</label>
<div class="controls">
{{image-uploader imageUrl=model.card_background_upload_url
type="card_background"}}
{{#if experimentalUserCardImageUpload}}
{{uppy-image-uploader
imageUrl=model.card_background_upload_url
type="card_background"
id="profile-card-background"
}}
{{else}}
{{image-uploader imageUrl=model.card_background_upload_url type="card_background"}}
{{/if}}
</div>
<div class="instructions">
{{i18n "user.change_card_background.instructions"}}

View File

@ -21,6 +21,11 @@
"@ember/test-helpers": "^2.2.0",
"@glimmer/component": "^1.0.0",
"@popperjs/core": "^2.4.4",
"@uppy/aws-s3": "^1.7.12",
"@uppy/aws-s3-multipart": "^1.8.18",
"@uppy/core": "^1.19.2",
"@uppy/drop-target": "^0.2.4",
"@uppy/xhr-upload": "^1.7.5",
"admin": "^1.0.0",
"bent": "^7.3.12",
"broccoli-asset-rev": "^3.0.0",

View File

@ -0,0 +1,100 @@
import componentTest, {
setupRenderingTest,
} from "discourse/tests/helpers/component-test";
import {
count,
discourseModule,
exists,
} from "discourse/tests/helpers/qunit-helpers";
import { click } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
discourseModule(
"Integration | Component | uppy-image-uploader",
function (hooks) {
setupRenderingTest(hooks);
componentTest("with image", {
template: hbs`
{{uppy-image-uploader imageUrl='/images/avatar.png' placeholderUrl='/not/used.png'}}
`,
async test(assert) {
assert.equal(
count(".d-icon-far-image"),
1,
"it displays the upload icon"
);
assert.equal(
count(".d-icon-far-trash-alt"),
1,
"it displays the trash icon"
);
assert.ok(
!exists(".placeholder-overlay"),
"it does not display the placeholder image"
);
await click(".image-uploader-lightbox-btn");
assert.equal(
$(".mfp-container").length,
1,
"it displays the image lightbox"
);
},
});
componentTest("without image", {
template: hbs`{{uppy-image-uploader}}`,
test(assert) {
assert.equal(
count(".d-icon-far-image"),
1,
"it displays the upload icon"
);
assert.ok(
!exists(".d-icon-far-trash-alt"),
"it does not display trash icon"
);
assert.ok(
!exists(".image-uploader-lightbox-btn"),
"it does not display the button to open image lightbox"
);
},
});
componentTest("with placeholder", {
template: hbs`{{uppy-image-uploader placeholderUrl='/images/avatar.png'}}`,
test(assert) {
assert.equal(
count(".d-icon-far-image"),
1,
"it displays the upload icon"
);
assert.ok(
!exists(".d-icon-far-trash-alt"),
"it does not display trash icon"
);
assert.ok(
!exists(".image-uploader-lightbox-btn"),
"it does not display the button to open image lightbox"
);
assert.equal(
count(".placeholder-overlay"),
1,
"it displays the placeholder image"
);
},
});
}
);

View File

@ -1258,6 +1258,11 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@transloadit/prettier-bytes@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz#cdb5399f445fdd606ed833872fa0cabdbc51686b"
integrity sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==
"@types/body-parser@*":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
@ -1377,6 +1382,79 @@
resolved "https://registry.yarnpkg.com/@types/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz#4151a81b4052c80bc2becbae09f3a9ec010a9c7a"
integrity sha512-Lja2xYuuf2B3knEsga8ShbOdsfNOtzT73GyJmZyY7eGl2+ajOqrs8yM5ze0fsSoYwvA6bw7/Qr7OZ7PEEmYwWg==
"@uppy/aws-s3-multipart@^1.8.18":
version "1.8.18"
resolved "https://registry.yarnpkg.com/@uppy/aws-s3-multipart/-/aws-s3-multipart-1.8.18.tgz#d0b3ede025d06b615ad3df90c3771eed38f68d87"
integrity sha512-m+IJSsDF253igTlQb2vgCTetqd+qgNIBX48i8HGnLj4rBfRd4FjpBpcV7DgfAn7QVOfrQOgOJoz9cGtXdaZ3lA==
dependencies:
"@uppy/companion-client" "^1.10.2"
"@uppy/utils" "^3.6.2"
"@uppy/aws-s3@^1.7.12":
version "1.7.12"
resolved "https://registry.yarnpkg.com/@uppy/aws-s3/-/aws-s3-1.7.12.tgz#5fd3259afc06feb745129ee224f93b618934be63"
integrity sha512-9Q8EMg1vQlDrmhaLs5UUQn4kAszLa8E2c1c4mD2urkpS/jHofdF4geRRtp4g4/xvBwwtnhzGqPX4dIylvoZICQ==
dependencies:
"@uppy/companion-client" "^1.10.2"
"@uppy/utils" "^3.6.2"
"@uppy/xhr-upload" "^1.7.5"
cuid "^2.1.1"
qs-stringify "^1.1.0"
url-parse "^1.4.7"
"@uppy/companion-client@^1.10.2":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@uppy/companion-client/-/companion-client-1.10.2.tgz#a640b3ef90b91751c49bf4b6a7a63c2ebac294f6"
integrity sha512-5RmsNF9UBvUqmqQz48SoiLvkpGmvQTgwNM4bJX8xwVozv/6goRpFrsMJGLwqFcHS/9xj6STKOqrM582g8exVwQ==
dependencies:
"@uppy/utils" "^3.6.2"
namespace-emitter "^2.0.1"
qs-stringify "^1.1.0"
url-parse "^1.4.7"
"@uppy/core@^1.19.2":
version "1.19.2"
resolved "https://registry.yarnpkg.com/@uppy/core/-/core-1.19.2.tgz#0db125586bc663921066d9098a4c9b39355b8135"
integrity sha512-2aHvUMdH8fs2eFn30LzNZDHCKoUNAyC+MXwM2NQeO858o0gj2R4axZbrheXnpXrI9dB6RGELGIGkS+ZhnjQAmA==
dependencies:
"@transloadit/prettier-bytes" "0.0.7"
"@uppy/store-default" "^1.2.7"
"@uppy/utils" "^3.6.2"
cuid "^2.1.1"
lodash.throttle "^4.1.1"
mime-match "^1.0.2"
namespace-emitter "^2.0.1"
preact "8.2.9"
"@uppy/drop-target@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@uppy/drop-target/-/drop-target-0.2.4.tgz#92e4ffa0d34781a37760e98850262c14b8718c2d"
integrity sha512-aRACD7f5jznt7NhLAtw/Nyi94XVjgYnqO3LN2mIbBQrsoQ+mINMzIu4rdVBhpGvk7qNM6961d5jPmPYSUBNiUw==
dependencies:
"@uppy/utils" "^3.6.2"
"@uppy/store-default@^1.2.7":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@uppy/store-default/-/store-default-1.2.7.tgz#41a0b1579f4d5b86c236e7f5e52fdc01960bb011"
integrity sha512-58IG9yk/i/kYQ9uEwAwMFl1H2V3syOoODrYoFfVHlxaqv+9MkXBg2tHE2gk40iaAIxcCErcPxZkBOvkqzO1SQA==
"@uppy/utils@^3.6.2":
version "3.6.2"
resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-3.6.2.tgz#78b02455b9c469d927d22736be5b68cda2600826"
integrity sha512-wGTZma7eywIojfuE1vXlT0fxPSpmCRMkfgFWYc+6TL2FfGqWInmePoB+yal6/M2AnjeKHz6XYMhIpZkjOxFvcw==
dependencies:
abortcontroller-polyfill "^1.4.0"
lodash.throttle "^4.1.1"
"@uppy/xhr-upload@^1.7.5":
version "1.7.5"
resolved "https://registry.yarnpkg.com/@uppy/xhr-upload/-/xhr-upload-1.7.5.tgz#990ba3e698503bd51534a59fd426096e37ef942b"
integrity sha512-Itnc9j9k/PemcmT5KrZ1BEw3pTc6WJg0yyyOcE+hLO8Hjv60Fm7c/I2ZknarOroIjT1WiTSyuxTBPp+9UGkxNA==
dependencies:
"@uppy/companion-client" "^1.10.2"
"@uppy/utils" "^3.6.2"
cuid "^2.1.1"
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@ -1542,6 +1620,11 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abortcontroller-polyfill@^1.4.0:
version "1.7.3"
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5"
integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==
accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@ -4279,6 +4362,11 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
cuid@^2.1.1:
version "2.1.8"
resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0"
integrity sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@ -8038,6 +8126,11 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "^3.0.0"
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@ -8331,6 +8424,13 @@ mime-db@1.47.0, "mime-db@>= 1.43.0 < 2":
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
mime-match@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mime-match/-/mime-match-1.0.2.tgz#3f87c31e9af1a5fd485fb9db134428b23bbb7ba8"
integrity sha1-P4fDHprxpf1IX7nbE0Qosju7e6g=
dependencies:
wildcard "^1.1.0"
mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.30"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
@ -8509,6 +8609,11 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
namespace-emitter@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz#978d51361c61313b4e6b8cf6f3853d08dfa2b17c"
integrity sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==
nan@^2.12.1:
version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
@ -9217,6 +9322,11 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
preact@8.2.9:
version "8.2.9"
resolved "https://registry.yarnpkg.com/preact/-/preact-8.2.9.tgz#813ba9dd45e5d97c5ea0d6c86d375b3be711cc40"
integrity sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -9369,6 +9479,11 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs-stringify@^1.1.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/qs-stringify/-/qs-stringify-1.2.1.tgz#9b39ef6b816bd83309628fc9dad435fc0eccc28b"
integrity sha512-2N5xGLGZUxpgAYq1fD1LmBSCbxQVsXYt5JU0nU3FuPWO8PlCnKNFQwXkZgyB6mrTdg7IbexX4wxIR403dJw9pw==
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -9405,6 +9520,11 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@ -11296,6 +11416,14 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
url-parse@^1.4.7:
version "1.5.1"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
@ -11622,6 +11750,11 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"
wildcard@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-1.1.2.tgz#a7020453084d8cd2efe70ba9d3696263de1710a5"
integrity sha1-pwIEUwhNjNLv5wup02liY94XEKU=
word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"

View File

@ -16,8 +16,12 @@ class UploadsController < ApplicationController
# capture current user for block later on
me = current_user
params.permit(:type, :upload_type)
if params[:type].blank? && params[:upload_type].blank?
raise Discourse::InvalidParameters
end
# 50 characters ought to be enough for the upload type
type = params.require(:type).parameterize(separator: "_")[0..50]
type = (params[:upload_type].presence || params[:type].presence).parameterize(separator: "_")[0..50]
if type == "avatar" && !me.admin? && (SiteSetting.discourse_connect_overrides_avatar || !SiteSetting.allow_uploaded_avatars)
return render json: failed_json, status: 422

View File

@ -267,6 +267,10 @@ basic:
client: true
default: true
hidden: true
enable_experimental_image_uploader:
client: true
default: false
hidden: true
default_theme_id:
default: -1
hidden: true

View File

@ -13,6 +13,7 @@
"@uppy/aws-s3": "^1.7.12",
"@uppy/aws-s3-multipart": "^1.8.18",
"@uppy/core": "^1.19.2",
"@uppy/drop-target": "^0.2.4",
"@uppy/xhr-upload": "^1.7.5",
"ace-builds": "1.4.12",
"blueimp-file-upload": "10.13.0",

View File

@ -23,9 +23,13 @@ describe UploadsController do
let(:fake_jpg) { Rack::Test::UploadedFile.new(file_from_fixtures("fake.jpg")) }
let(:text_file) { Rack::Test::UploadedFile.new(File.new("#{Rails.root}/LICENSE.txt")) }
it 'expects a type' do
it 'expects a type or upload_type' do
post "/uploads.json", params: { file: logo }
expect(response.status).to eq(400)
post "/uploads.json", params: { file: Rack::Test::UploadedFile.new(logo_file), type: "avatar" }
expect(response.status).to eq 200
post "/uploads.json", params: { file: Rack::Test::UploadedFile.new(logo_file), upload_type: "avatar" }
expect(response.status).to eq 200
end
it 'is successful with an image' do

View File

@ -9,3 +9,4 @@ Uppy.Plugin = Uppy.Core.Plugin
Uppy.XHRUpload = require('@uppy/xhr-upload')
Uppy.AwsS3 = require('@uppy/aws-s3')
Uppy.AwsS3Multipart = require('@uppy/aws-s3-multipart')
Uppy.DropTarget = require('@uppy/drop-target')

View File

@ -500,7 +500,7 @@ var MultipartUploader = /*#__PURE__*/function () {
}();
module.exports = MultipartUploader;
},{"@uppy/utils/lib/AbortController":20,"@uppy/utils/lib/delay":26}],3:[function(require,module,exports){
},{"@uppy/utils/lib/AbortController":21,"@uppy/utils/lib/delay":27}],3:[function(require,module,exports){
var _class, _temp;
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
@ -1078,7 +1078,7 @@ module.exports = (_temp = _class = /*#__PURE__*/function (_Plugin) {
return AwsS3Multipart;
}(Plugin), _class.VERSION = "1.8.18", _temp);
},{"./MultipartUploader":2,"@uppy/companion-client":12,"@uppy/core":15,"@uppy/utils/lib/EventTracker":21,"@uppy/utils/lib/RateLimitedQueue":24,"@uppy/utils/lib/emitSocketProgress":27,"@uppy/utils/lib/getSocketHost":34}],4:[function(require,module,exports){
},{"./MultipartUploader":2,"@uppy/companion-client":12,"@uppy/core":15,"@uppy/utils/lib/EventTracker":22,"@uppy/utils/lib/RateLimitedQueue":25,"@uppy/utils/lib/emitSocketProgress":28,"@uppy/utils/lib/getSocketHost":40}],4:[function(require,module,exports){
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
var cuid = require('cuid');
@ -1492,7 +1492,7 @@ module.exports = /*#__PURE__*/function () {
return MiniXHRUpload;
}();
},{"@uppy/companion-client":12,"@uppy/utils/lib/EventTracker":21,"@uppy/utils/lib/NetworkError":22,"@uppy/utils/lib/ProgressTimeout":23,"@uppy/utils/lib/emitSocketProgress":27,"@uppy/utils/lib/getSocketHost":34,"@uppy/utils/lib/isNetworkError":38,"cuid":43}],5:[function(require,module,exports){
},{"@uppy/companion-client":12,"@uppy/utils/lib/EventTracker":22,"@uppy/utils/lib/NetworkError":23,"@uppy/utils/lib/ProgressTimeout":24,"@uppy/utils/lib/emitSocketProgress":28,"@uppy/utils/lib/getSocketHost":40,"@uppy/utils/lib/isNetworkError":44,"cuid":50}],5:[function(require,module,exports){
var _class, _temp;
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
@ -1829,7 +1829,7 @@ module.exports = (_temp = _class = /*#__PURE__*/function (_Plugin) {
return AwsS3;
}(Plugin), _class.VERSION = "1.7.12", _temp);
},{"./MiniXHRUpload":4,"./isXml":6,"@uppy/companion-client":12,"@uppy/core":15,"@uppy/utils/lib/RateLimitedQueue":24,"@uppy/utils/lib/Translator":25,"@uppy/utils/lib/hasProperty":36,"@uppy/utils/lib/settle":40,"qs-stringify":51,"url-parse":54}],6:[function(require,module,exports){
},{"./MiniXHRUpload":4,"./isXml":6,"@uppy/companion-client":12,"@uppy/core":15,"@uppy/utils/lib/RateLimitedQueue":25,"@uppy/utils/lib/Translator":26,"@uppy/utils/lib/hasProperty":42,"@uppy/utils/lib/settle":46,"qs-stringify":58,"url-parse":61}],6:[function(require,module,exports){
/**
* Remove parameters like `charset=utf-8` from the end of a mime type string.
*
@ -2069,7 +2069,7 @@ module.exports = /*#__PURE__*/function (_RequestClient) {
return Provider;
}(RequestClient);
},{"./RequestClient":9,"./tokenStorage":13,"qs-stringify":51,"url-parse":54}],9:[function(require,module,exports){
},{"./RequestClient":9,"./tokenStorage":13,"qs-stringify":58,"url-parse":61}],9:[function(require,module,exports){
'use strict';
var _class, _temp;
@ -2289,7 +2289,7 @@ module.exports = (_temp = _class = /*#__PURE__*/function () {
return RequestClient;
}(), _class.VERSION = "1.10.2", _temp);
},{"./AuthError":7,"@uppy/utils/lib/fetchWithNetworkError":28}],10:[function(require,module,exports){
},{"./AuthError":7,"@uppy/utils/lib/fetchWithNetworkError":29}],10:[function(require,module,exports){
'use strict';
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
@ -2424,7 +2424,7 @@ module.exports = /*#__PURE__*/function () {
return UppySocket;
}();
},{"namespace-emitter":49}],12:[function(require,module,exports){
},{"namespace-emitter":56}],12:[function(require,module,exports){
'use strict';
/**
* Manages communications with Companion
@ -2669,7 +2669,7 @@ module.exports = /*#__PURE__*/function () {
return Plugin;
}();
},{"@uppy/utils/lib/findDOMElement":29,"preact":50}],15:[function(require,module,exports){
},{"@uppy/utils/lib/findDOMElement":30,"preact":57}],15:[function(require,module,exports){
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
@ -4592,7 +4592,7 @@ module.exports = function core(opts) {
module.exports.Uppy = Uppy;
module.exports.Plugin = Plugin;
module.exports.debugLogger = debugLogger;
},{"../package.json":18,"./Plugin":14,"./loggers":16,"./supportsUploadProgress":17,"@transloadit/prettier-bytes":1,"@uppy/store-default":19,"@uppy/utils/lib/Translator":25,"@uppy/utils/lib/findIndex":30,"@uppy/utils/lib/generateFileID":31,"@uppy/utils/lib/getFileNameAndExtension":32,"@uppy/utils/lib/getFileType":33,"cuid":43,"lodash.throttle":47,"mime-match":48,"namespace-emitter":49}],16:[function(require,module,exports){
},{"../package.json":18,"./Plugin":14,"./loggers":16,"./supportsUploadProgress":17,"@transloadit/prettier-bytes":1,"@uppy/store-default":20,"@uppy/utils/lib/Translator":26,"@uppy/utils/lib/findIndex":31,"@uppy/utils/lib/generateFileID":32,"@uppy/utils/lib/getFileNameAndExtension":38,"@uppy/utils/lib/getFileType":39,"cuid":50,"lodash.throttle":54,"mime-match":55,"namespace-emitter":56}],16:[function(require,module,exports){
var getTimeStamp = require('@uppy/utils/lib/getTimeStamp'); // Swallow all logs, except errors.
// default if logger is not set or debug: false
@ -4646,7 +4646,7 @@ module.exports = {
justErrorsLogger: justErrorsLogger,
debugLogger: debugLogger
};
},{"@uppy/utils/lib/getTimeStamp":35}],17:[function(require,module,exports){
},{"@uppy/utils/lib/getTimeStamp":41}],17:[function(require,module,exports){
// Edge 15.x does not fire 'progress' events on uploads.
// See https://github.com/transloadit/uppy/issues/945
// And https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12224510/
@ -4720,6 +4720,171 @@ module.exports={
}
},{}],19:[function(require,module,exports){
var _class, _temp;
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
var _require = require('@uppy/core'),
Plugin = _require.Plugin;
var getDroppedFiles = require('@uppy/utils/lib/getDroppedFiles');
var toArray = require('@uppy/utils/lib/toArray');
/**
* Drop Target plugin
*
*/
module.exports = (_temp = _class = /*#__PURE__*/function (_Plugin) {
_inheritsLoose(DropTarget, _Plugin);
function DropTarget(uppy, opts) {
var _this;
_this = _Plugin.call(this, uppy, opts) || this;
_this.addFiles = function (files) {
var descriptors = files.map(function (file) {
return {
source: _this.id,
name: file.name,
type: file.type,
data: file,
meta: {
// path of the file relative to the ancestor directory the user selected.
// e.g. 'docs/Old Prague/airbnb.pdf'
relativePath: file.relativePath || null
}
};
});
try {
_this.uppy.addFiles(descriptors);
} catch (err) {
_this.uppy.log(err);
}
};
_this.handleDrop = function (event) {
event.preventDefault();
event.stopPropagation();
clearTimeout(_this.removeDragOverClassTimeout); // 2. Remove dragover class
event.currentTarget.classList.remove('uppy-is-drag-over');
_this.setPluginState({
isDraggingOver: false
}); // 3. Add all dropped files
_this.uppy.log('[DropTarget] Files were dropped');
var logDropError = function logDropError(error) {
_this.uppy.log(error, 'error');
};
getDroppedFiles(event.dataTransfer, {
logDropError: logDropError
}).then(function (files) {
return _this.addFiles(files);
});
};
_this.handleDragOver = function (event) {
event.preventDefault();
event.stopPropagation(); // 1. Add a small (+) icon on drop
// (and prevent browsers from interpreting this as files being _moved_ into the browser,
// https://github.com/transloadit/uppy/issues/1978)
event.dataTransfer.dropEffect = 'copy';
clearTimeout(_this.removeDragOverClassTimeout);
event.currentTarget.classList.add('uppy-is-drag-over');
_this.setPluginState({
isDraggingOver: true
});
};
_this.handleDragLeave = function (event) {
event.preventDefault();
event.stopPropagation();
var currentTarget = event.currentTarget;
clearTimeout(_this.removeDragOverClassTimeout); // Timeout against flickering, this solution is taken from drag-drop library.
// Solution with 'pointer-events: none' didn't work across browsers.
_this.removeDragOverClassTimeout = setTimeout(function () {
currentTarget.classList.remove('uppy-is-drag-over');
_this.setPluginState({
isDraggingOver: false
});
}, 50);
};
_this.addListeners = function () {
var target = _this.opts.target;
if (target instanceof Element) {
_this.nodes = [target];
} else if (typeof target === 'string') {
_this.nodes = toArray(document.querySelectorAll(target));
}
if (!_this.nodes && !_this.nodes.length > 0) {
throw new Error("\"" + target + "\" does not match any HTML elements");
}
_this.nodes.forEach(function (node) {
node.addEventListener('dragover', _this.handleDragOver, false);
node.addEventListener('dragleave', _this.handleDragLeave, false);
node.addEventListener('drop', _this.handleDrop, false);
});
};
_this.removeListeners = function () {
if (_this.nodes) {
_this.nodes.forEach(function (node) {
node.removeEventListener('dragover', _this.handleDragOver, false);
node.removeEventListener('dragleave', _this.handleDragLeave, false);
node.removeEventListener('drop', _this.handleDrop, false);
});
}
};
_this.type = 'acquirer';
_this.id = _this.opts.id || 'DropTarget';
_this.title = 'Drop Target'; // Default options
var defaultOpts = {
target: null
}; // Merge default options with the ones set by user
_this.opts = _extends({}, defaultOpts, opts);
_this.removeDragOverClassTimeout = null;
return _this;
}
var _proto = DropTarget.prototype;
_proto.install = function install() {
this.setPluginState({
isDraggingOver: false
});
this.addListeners();
};
_proto.uninstall = function uninstall() {
this.removeListeners();
};
return DropTarget;
}(Plugin), _class.VERSION = "0.2.4", _temp);
},{"@uppy/core":15,"@uppy/utils/lib/getDroppedFiles":33,"@uppy/utils/lib/toArray":47}],20:[function(require,module,exports){
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
/**
@ -4775,7 +4940,7 @@ DefaultStore.VERSION = "1.2.7";
module.exports = function defaultStore() {
return new DefaultStore();
};
},{}],20:[function(require,module,exports){
},{}],21:[function(require,module,exports){
/**
* Little AbortController proxy module so we can swap out the implementation easily later.
*/
@ -4801,7 +4966,7 @@ function createAbortError(message) {
exports.AbortController = AbortController;
exports.AbortSignal = AbortSignal;
exports.createAbortError = createAbortError;
},{"abortcontroller-polyfill/dist/abortcontroller":42}],21:[function(require,module,exports){
},{"abortcontroller-polyfill/dist/abortcontroller":49}],22:[function(require,module,exports){
/**
* Create a wrapper around an event emitter with a `remove` method to remove
* all events that were added using the wrapped emitter.
@ -4833,7 +4998,7 @@ module.exports = /*#__PURE__*/function () {
return EventTracker;
}();
},{}],22:[function(require,module,exports){
},{}],23:[function(require,module,exports){
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); }
@ -4868,7 +5033,7 @@ var NetworkError = /*#__PURE__*/function (_Error) {
}( /*#__PURE__*/_wrapNativeSuper(Error));
module.exports = NetworkError;
},{}],23:[function(require,module,exports){
},{}],24:[function(require,module,exports){
/**
* Helper to abort upload requests if there has not been any progress for `timeout` ms.
* Create an instance using `timer = new ProgressTimeout(10000, onTimeout)`
@ -4911,7 +5076,7 @@ var ProgressTimeout = /*#__PURE__*/function () {
}();
module.exports = ProgressTimeout;
},{}],24:[function(require,module,exports){
},{}],25:[function(require,module,exports){
var findIndex = require('./findIndex');
function createCancelError() {
@ -5093,7 +5258,7 @@ module.exports = /*#__PURE__*/function () {
return RateLimitedQueue;
}();
},{"./findIndex":30}],25:[function(require,module,exports){
},{"./findIndex":31}],26:[function(require,module,exports){
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
var has = require('./hasProperty');
@ -5259,7 +5424,7 @@ module.exports = /*#__PURE__*/function () {
return Translator;
}();
},{"./hasProperty":36}],26:[function(require,module,exports){
},{"./hasProperty":42}],27:[function(require,module,exports){
var _require = require('./AbortController'),
createAbortError = _require.createAbortError;
/**
@ -5299,7 +5464,7 @@ module.exports = function delay(ms, opts) {
}
});
};
},{"./AbortController":20}],27:[function(require,module,exports){
},{"./AbortController":21}],28:[function(require,module,exports){
var throttle = require('lodash.throttle');
function _emitSocketProgress(uploader, progressData, file) {
@ -5321,7 +5486,7 @@ module.exports = throttle(_emitSocketProgress, 300, {
leading: true,
trailing: true
});
},{"lodash.throttle":47}],28:[function(require,module,exports){
},{"lodash.throttle":54}],29:[function(require,module,exports){
var NetworkError = require('./NetworkError');
/**
* Wrapper around window.fetch that throws a NetworkError when appropriate
@ -5337,7 +5502,7 @@ module.exports = function fetchWithNetworkError() {
}
});
};
},{"./NetworkError":22}],29:[function(require,module,exports){
},{"./NetworkError":23}],30:[function(require,module,exports){
var isDOMElement = require('./isDOMElement');
/**
* Find a DOM element.
@ -5360,7 +5525,7 @@ module.exports = function findDOMElement(element, context) {
return element;
}
};
},{"./isDOMElement":37}],30:[function(require,module,exports){
},{"./isDOMElement":43}],31:[function(require,module,exports){
/**
* Array.prototype.findIndex ponyfill for old browsers.
*
@ -5375,7 +5540,7 @@ module.exports = function findIndex(array, predicate) {
return -1;
};
},{}],31:[function(require,module,exports){
},{}],32:[function(require,module,exports){
/**
* Takes a file object and turns it into fileID, by converting file.name to lowercase,
* removing extra characters and adding type, size and lastModified
@ -5422,7 +5587,147 @@ function encodeFilename(name) {
function encodeCharacter(character) {
return character.charCodeAt(0).toString(32);
}
},{}],32:[function(require,module,exports){
},{}],33:[function(require,module,exports){
var webkitGetAsEntryApi = require('./utils/webkitGetAsEntryApi/index');
var fallbackApi = require('./utils/fallbackApi');
/**
* Returns a promise that resolves to the array of dropped files (if a folder is dropped, and browser supports folder parsing - promise resolves to the flat array of all files in all directories).
* Each file has .relativePath prop appended to it (e.g. "/docs/Prague/ticket_from_prague_to_ufa.pdf") if browser supports it. Otherwise it's undefined.
*
* @param {DataTransfer} dataTransfer
* @param {Function} logDropError - a function that's called every time some folder or some file error out (e.g. because of the folder name being too long on Windows). Notice that resulting promise will always be resolved anyway.
*
* @returns {Promise} - Array<File>
*/
module.exports = function getDroppedFiles(dataTransfer, _temp) {
var _ref = _temp === void 0 ? {} : _temp,
_ref$logDropError = _ref.logDropError,
logDropError = _ref$logDropError === void 0 ? function () {} : _ref$logDropError;
// Get all files from all subdirs. Works (at least) in Chrome, Mozilla, and Safari
if (dataTransfer.items && dataTransfer.items[0] && 'webkitGetAsEntry' in dataTransfer.items[0]) {
return webkitGetAsEntryApi(dataTransfer, logDropError); // Otherwise just return all first-order files
}
return fallbackApi(dataTransfer);
};
},{"./utils/fallbackApi":34,"./utils/webkitGetAsEntryApi/index":37}],34:[function(require,module,exports){
var toArray = require('../../toArray'); // .files fallback, should be implemented in any browser
module.exports = function fallbackApi(dataTransfer) {
var files = toArray(dataTransfer.files);
return Promise.resolve(files);
};
},{"../../toArray":47}],35:[function(require,module,exports){
/**
* Recursive function, calls the original callback() when the directory is entirely parsed.
*
* @param {FileSystemDirectoryReader} directoryReader
* @param {Array} oldEntries
* @param {Function} logDropError
* @param {Function} callback - called with ([ all files and directories in that directoryReader ])
*/
module.exports = function getFilesAndDirectoriesFromDirectory(directoryReader, oldEntries, logDropError, _ref) {
var onSuccess = _ref.onSuccess;
directoryReader.readEntries(function (entries) {
var newEntries = [].concat(oldEntries, entries); // According to the FileSystem API spec, getFilesAndDirectoriesFromDirectory() must be called until it calls the onSuccess with an empty array.
if (entries.length) {
setTimeout(function () {
getFilesAndDirectoriesFromDirectory(directoryReader, newEntries, logDropError, {
onSuccess: onSuccess
});
}, 0); // Done iterating this particular directory
} else {
onSuccess(newEntries);
}
}, // Make sure we resolve on error anyway, it's fine if only one directory couldn't be parsed!
function (error) {
logDropError(error);
onSuccess(oldEntries);
});
};
},{}],36:[function(require,module,exports){
/**
* Get the relative path from the FileEntry#fullPath, because File#webkitRelativePath is always '', at least onDrop.
*
* @param {FileEntry} fileEntry
*
* @returns {string|null} - if file is not in a folder - return null (this is to be consistent with .relativePath-s of files selected from My Device). If file is in a folder - return its fullPath, e.g. '/simpsons/hi.jpeg'.
*/
module.exports = function getRelativePath(fileEntry) {
// fileEntry.fullPath - "/simpsons/hi.jpeg" or undefined (for browsers that don't support it)
// fileEntry.name - "hi.jpeg"
if (!fileEntry.fullPath || fileEntry.fullPath === "/" + fileEntry.name) {
return null;
}
return fileEntry.fullPath;
};
},{}],37:[function(require,module,exports){
var toArray = require('../../../toArray');
var getRelativePath = require('./getRelativePath');
var getFilesAndDirectoriesFromDirectory = require('./getFilesAndDirectoriesFromDirectory');
module.exports = function webkitGetAsEntryApi(dataTransfer, logDropError) {
var files = [];
var rootPromises = [];
/**
* Returns a resolved promise, when :files array is enhanced
*
* @param {(FileSystemFileEntry|FileSystemDirectoryEntry)} entry
* @returns {Promise} - empty promise that resolves when :files is enhanced with a file
*/
var createPromiseToAddFileOrParseDirectory = function createPromiseToAddFileOrParseDirectory(entry) {
return new Promise(function (resolve) {
// This is a base call
if (entry.isFile) {
// Creates a new File object which can be used to read the file.
entry.file(function (file) {
file.relativePath = getRelativePath(entry);
files.push(file);
resolve();
}, // Make sure we resolve on error anyway, it's fine if only one file couldn't be read!
function (error) {
logDropError(error);
resolve();
}); // This is a recursive call
} else if (entry.isDirectory) {
var directoryReader = entry.createReader();
getFilesAndDirectoriesFromDirectory(directoryReader, [], logDropError, {
onSuccess: function onSuccess(entries) {
var promises = entries.map(function (entry) {
return createPromiseToAddFileOrParseDirectory(entry);
});
Promise.all(promises).then(function () {
return resolve();
});
}
});
}
});
}; // For each dropped item, - make sure it's a file/directory, and start deepening in!
toArray(dataTransfer.items).forEach(function (item) {
var entry = item.webkitGetAsEntry(); // :entry can be null when we drop the url e.g.
if (entry) {
rootPromises.push(createPromiseToAddFileOrParseDirectory(entry));
}
});
return Promise.all(rootPromises).then(function () {
return files;
});
};
},{"../../../toArray":47,"./getFilesAndDirectoriesFromDirectory":35,"./getRelativePath":36}],38:[function(require,module,exports){
/**
* Takes a full filename string and returns an object {name, extension}
*
@ -5444,7 +5749,7 @@ module.exports = function getFileNameAndExtension(fullFileName) {
extension: fullFileName.slice(lastDot + 1)
};
};
},{}],33:[function(require,module,exports){
},{}],39:[function(require,module,exports){
var getFileNameAndExtension = require('./getFileNameAndExtension');
var mimeTypes = require('./mimeTypes');
@ -5466,7 +5771,7 @@ module.exports = function getFileType(file) {
return 'application/octet-stream';
};
},{"./getFileNameAndExtension":32,"./mimeTypes":39}],34:[function(require,module,exports){
},{"./getFileNameAndExtension":38,"./mimeTypes":45}],40:[function(require,module,exports){
module.exports = function getSocketHost(url) {
// get the host domain
var regex = /^(?:https?:\/\/|\/\/)?(?:[^@\n]+@)?(?:www\.)?([^\n]+)/i;
@ -5474,7 +5779,7 @@ module.exports = function getSocketHost(url) {
var socketProtocol = /^http:\/\//i.test(url) ? 'ws' : 'wss';
return socketProtocol + "://" + host;
};
},{}],35:[function(require,module,exports){
},{}],41:[function(require,module,exports){
/**
* Returns a timestamp in the format of `hours:minutes:seconds`
*/
@ -5493,11 +5798,11 @@ module.exports = function getTimeStamp() {
function pad(str) {
return str.length !== 2 ? 0 + str : str;
}
},{}],36:[function(require,module,exports){
},{}],42:[function(require,module,exports){
module.exports = function has(object, key) {
return Object.prototype.hasOwnProperty.call(object, key);
};
},{}],37:[function(require,module,exports){
},{}],43:[function(require,module,exports){
/**
* Check if an object is a DOM element. Duck-typing based on `nodeType`.
*
@ -5506,7 +5811,7 @@ module.exports = function has(object, key) {
module.exports = function isDOMElement(obj) {
return obj && typeof obj === 'object' && obj.nodeType === Node.ELEMENT_NODE;
};
},{}],38:[function(require,module,exports){
},{}],44:[function(require,module,exports){
function isNetworkError(xhr) {
if (!xhr) {
return false;
@ -5516,7 +5821,7 @@ function isNetworkError(xhr) {
}
module.exports = isNetworkError;
},{}],39:[function(require,module,exports){
},{}],45:[function(require,module,exports){
// ___Why not add the mime-types package?
// It's 19.7kB gzipped, and we only need mime types for well-known extensions (for file previews).
// ___Where to take new extensions from?
@ -5572,7 +5877,7 @@ module.exports = {
gz: 'application/gzip',
dmg: 'application/x-apple-diskimage'
};
},{}],40:[function(require,module,exports){
},{}],46:[function(require,module,exports){
module.exports = function settle(promises) {
var resolutions = [];
var rejections = [];
@ -5595,7 +5900,14 @@ module.exports = function settle(promises) {
};
});
};
},{}],41:[function(require,module,exports){
},{}],47:[function(require,module,exports){
/**
* Converts list into array
*/
module.exports = function toArray(list) {
return Array.prototype.slice.call(list || [], 0);
};
},{}],48:[function(require,module,exports){
var _class, _temp;
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
@ -6335,7 +6647,7 @@ module.exports = (_temp = _class = /*#__PURE__*/function (_Plugin) {
return XHRUpload;
}(Plugin), _class.VERSION = "1.7.5", _temp);
},{"@uppy/companion-client":12,"@uppy/core":15,"@uppy/utils/lib/EventTracker":21,"@uppy/utils/lib/NetworkError":22,"@uppy/utils/lib/ProgressTimeout":23,"@uppy/utils/lib/RateLimitedQueue":24,"@uppy/utils/lib/Translator":25,"@uppy/utils/lib/emitSocketProgress":27,"@uppy/utils/lib/getSocketHost":34,"@uppy/utils/lib/isNetworkError":38,"@uppy/utils/lib/settle":40,"cuid":43}],42:[function(require,module,exports){
},{"@uppy/companion-client":12,"@uppy/core":15,"@uppy/utils/lib/EventTracker":22,"@uppy/utils/lib/NetworkError":23,"@uppy/utils/lib/ProgressTimeout":24,"@uppy/utils/lib/RateLimitedQueue":25,"@uppy/utils/lib/Translator":26,"@uppy/utils/lib/emitSocketProgress":28,"@uppy/utils/lib/getSocketHost":40,"@uppy/utils/lib/isNetworkError":44,"@uppy/utils/lib/settle":46,"cuid":50}],49:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
@ -6665,7 +6977,7 @@ exports.AbortController = AbortController;
exports.AbortSignal = AbortSignal;
exports.default = AbortController;
},{}],43:[function(require,module,exports){
},{}],50:[function(require,module,exports){
/**
* cuid.js
* Collision-resistant UID generator for browsers and node.
@ -6751,7 +7063,7 @@ cuid.fingerprint = fingerprint;
module.exports = cuid;
},{"./lib/fingerprint.js":44,"./lib/getRandomValue.js":45,"./lib/pad.js":46}],44:[function(require,module,exports){
},{"./lib/fingerprint.js":51,"./lib/getRandomValue.js":52,"./lib/pad.js":53}],51:[function(require,module,exports){
var pad = require('./pad.js');
var env = typeof window === 'object' ? window : self;
@ -6765,7 +7077,7 @@ module.exports = function fingerprint () {
return clientId;
};
},{"./pad.js":46}],45:[function(require,module,exports){
},{"./pad.js":53}],52:[function(require,module,exports){
var getRandomValue;
@ -6785,13 +7097,13 @@ if (crypto) {
module.exports = getRandomValue;
},{}],46:[function(require,module,exports){
},{}],53:[function(require,module,exports){
module.exports = function pad (num, size) {
var s = '000000000' + num;
return s.substr(s.length - size);
};
},{}],47:[function(require,module,exports){
},{}],54:[function(require,module,exports){
(function (global){(function (){
/**
* lodash (Custom Build) <https://lodash.com/>
@ -7234,7 +7546,7 @@ function toNumber(value) {
module.exports = throttle;
}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],48:[function(require,module,exports){
},{}],55:[function(require,module,exports){
var wildcard = require('wildcard');
var reMimePartSplit = /[\/\+\.]/;
@ -7260,7 +7572,7 @@ module.exports = function(target, pattern) {
return pattern ? test(pattern.split(';')[0]) : test;
};
},{"wildcard":55}],49:[function(require,module,exports){
},{"wildcard":62}],56:[function(require,module,exports){
/**
* Create an event emitter with namespaces
* @name createNamespaceEmitter
@ -7398,7 +7710,7 @@ module.exports = function createNamespaceEmitter () {
return emitter
}
},{}],50:[function(require,module,exports){
},{}],57:[function(require,module,exports){
!function() {
'use strict';
function VNode() {}
@ -7807,7 +8119,7 @@ module.exports = function createNamespaceEmitter () {
if ('undefined' != typeof module) module.exports = preact; else self.preact = preact;
}();
},{}],51:[function(require,module,exports){
},{}],58:[function(require,module,exports){
var has = Object.prototype.hasOwnProperty
/**
@ -7839,7 +8151,7 @@ module.exports = function queryStringify (obj, prefix) {
return pairs.join('&')
}
},{}],52:[function(require,module,exports){
},{}],59:[function(require,module,exports){
'use strict';
var has = Object.prototype.hasOwnProperty
@ -7959,7 +8271,7 @@ function querystringify(obj, prefix) {
exports.stringify = querystringify;
exports.parse = querystring;
},{}],53:[function(require,module,exports){
},{}],60:[function(require,module,exports){
'use strict';
/**
@ -7999,7 +8311,7 @@ module.exports = function required(port, protocol) {
return port !== 0;
};
},{}],54:[function(require,module,exports){
},{}],61:[function(require,module,exports){
(function (global){(function (){
'use strict';
@ -8465,7 +8777,7 @@ Url.qs = qs;
module.exports = Url;
}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"querystringify":52,"requires-port":53}],55:[function(require,module,exports){
},{"querystringify":59,"requires-port":60}],62:[function(require,module,exports){
/* jshint node: true */
'use strict';
@ -8560,7 +8872,7 @@ module.exports = function(text, test, separator) {
return matcher;
};
},{}],56:[function(require,module,exports){
},{}],63:[function(require,module,exports){
// We need a custom build of Uppy because we do not use webpack for
// our JS modules/build. The only way to get what you want from Uppy
// is to use the webpack modules or to include the entire Uppy project
@ -8572,5 +8884,6 @@ Uppy.Plugin = Uppy.Core.Plugin
Uppy.XHRUpload = require('@uppy/xhr-upload')
Uppy.AwsS3 = require('@uppy/aws-s3')
Uppy.AwsS3Multipart = require('@uppy/aws-s3-multipart')
Uppy.DropTarget = require('@uppy/drop-target')
},{"@uppy/aws-s3":5,"@uppy/aws-s3-multipart":3,"@uppy/core":15,"@uppy/xhr-upload":41}]},{},[56]);
},{"@uppy/aws-s3":5,"@uppy/aws-s3-multipart":3,"@uppy/core":15,"@uppy/drop-target":19,"@uppy/xhr-upload":48}]},{},[63]);

View File

@ -437,6 +437,13 @@
namespace-emitter "^2.0.1"
preact "8.2.9"
"@uppy/drop-target@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@uppy/drop-target/-/drop-target-0.2.4.tgz#92e4ffa0d34781a37760e98850262c14b8718c2d"
integrity sha512-aRACD7f5jznt7NhLAtw/Nyi94XVjgYnqO3LN2mIbBQrsoQ+mINMzIu4rdVBhpGvk7qNM6961d5jPmPYSUBNiUw==
dependencies:
"@uppy/utils" "^3.6.2"
"@uppy/store-default@^1.2.7":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@uppy/store-default/-/store-default-1.2.7.tgz#41a0b1579f4d5b86c236e7f5e52fdc01960bb011"