Source: upload-modal.js

  1. /* global $, libreviews, config */
  2. 'use strict';
  3. /**
  4. * Creates an overlay modal dialog which lets the user upload a single file via
  5. * the upload API.
  6. *
  7. * @param {Function} [successCallback]
  8. * Callback to run after a successful upload.
  9. * @param {Function} [errorCallback]
  10. * Callback to run after a failed upload (does not run if modal is closed).
  11. */
  12. exports.uploadModal = function uploadModal(successCallback, errorCallback) {
  13. // Obtain template for the dialog (a simple jQuery object).
  14. const $modal = getTemplate();
  15. $('body').append($modal);
  16. // Initially the modal just shows the upload button; once a file is selected,
  17. // we expand to show the metadata form
  18. $('#upload-input').change(expandModal);
  19. // If the user selects "Someone else's work", we flip to the second page to
  20. // let them enter author/source information
  21. $('#upload-modal-other').click(showPage2);
  22. // If the user selects "My own work", we set all metadata back to the default
  23. // values
  24. $('#upload-modal-ownwork').click(resetMetadata);
  25. $('#upload-modal-description').blur(libreviews.trimInput);
  26. // If the user clicks "Cancel" on page 2, we flip back to page 1, and we
  27. // de-select the radio buttons unless we have complete data from a previous
  28. // "OK" action
  29. $('#upload-modal-cancel-metadata').click(cancelPage2);
  30. // When the user clicks "Start upload", we first perform some manual
  31. // "required" checks - we don't use our standard library here to keep the
  32. // modal's UI compact (the "*" indicators would take up additional space).
  33. //
  34. // we send all the data to the upload API, which may return multiple errors.
  35. // Predictable errors (bad file type, actual MIME type doesn't match claimed
  36. // MIME type) will be reported to the user. Unknown errors will fall back to a
  37. // generic error message.
  38. //
  39. // Once the operation succeeds or fails, the respective callbacks are run.
  40. $('#upload-modal-start-upload').click(function(event) {
  41. event.preventDefault();
  42. startUpload(successCallback, errorCallback);
  43. });
  44. // We use our standard validation library for the page 2 metadata form. If it
  45. // reports no errors, the confirmMetadata callback updates the hidden fields
  46. // in the form on page 1.
  47. $('#upload-modal-confirm-metadata').attachRequiredFieldHandler({
  48. formSelector: '#upload-metadata-form',
  49. callback: confirmMetadata
  50. });
  51. // Open the modal dialog
  52. $modal.modal();
  53. // Lock in the inputs so user can tab through them but not exit the modal
  54. $modal.lockTab();
  55. // Clean up on each close
  56. $('#upload-modal').on($.modal.BEFORE_CLOSE, function() {
  57. $modal.remove();
  58. });
  59. };
  60. const
  61. __ = libreviews.msg,
  62. msg = {
  63. head: __('upload and insert media'),
  64. select: __('select file'),
  65. start: __('start upload'),
  66. placeholder: {
  67. description: __('enter description'),
  68. creator: __('enter creator name'),
  69. source: __('enter source'),
  70. license: __('select license')
  71. },
  72. creator: __('creator'),
  73. ownwork: __('my own work'),
  74. other: __('someone else\'s work'),
  75. specified: __('someone else\'s work specified'),
  76. source: __('source'),
  77. license: __('license'),
  78. licenses: {
  79. 'fair-use': __('fair use short'),
  80. 'cc-0': __('cc-0 short'),
  81. 'cc-by': __('cc-by short'),
  82. 'cc-by-sa': __('cc-by-sa short')
  83. },
  84. required: {
  85. rights: __('please specify rights'),
  86. description: __('please enter description')
  87. },
  88. ok: __('ok'),
  89. cancel: __('cancel'),
  90. error: __('could not complete action')
  91. },
  92. getTemplate = () => $(
  93. `<div id="upload-modal" class="hidden-regular">
  94. <form class="pure-form" id="upload-modal-form">
  95. <div id="upload-modal-page-1">
  96. <div class="upload-modal-buttondiv">
  97. <h3>${msg.head}</h3>
  98. <input type="file" name="files" id="upload-input" accept="image/*,video/webm,video/ogg,audio/*" class="hidden">
  99. <label id="upload-modal-label" for="upload-input" data-upload-count class="pure-button button-rounded" tabindex="0" data-focusable>
  100. <span class="fa fa-fw fa-file-image-o spaced-icon" id="upload-icon">&nbsp;</span><span id="upload-label-text">${msg.select}</span></label>
  101. </div>
  102. <div id="upload-modal-page-1-expansion" class="hidden-regular">
  103. <p>
  104. <textarea id="upload-modal-description" name="description" class="pure-input-1" placeholder="${msg.placeholder.description}"></textarea>
  105. <p>
  106. <table>
  107. <tr class="input-row">
  108. <td><input type="radio" id="upload-modal-ownwork" name="ownwork" value="1"></td>
  109. <td><label for="upload-modal-ownwork" class="inline-label">${msg.ownwork}</label></td>
  110. </tr>
  111. <tr class="input-row">
  112. <td>
  113. <input type="radio" id="upload-modal-other" name="ownwork" value=""></td>
  114. <td><label for="upload-modal-other" class="inline-label" id="upload-modal-other-label">${msg.other}</label></td>
  115. </tr>
  116. </table>
  117. <input type="hidden" name="language" value="${config.language}">
  118. <input type="hidden" id="upload-modal-license" name="license" value="cc-by-sa">
  119. <input type="hidden" id="upload-modal-creator" name="creator" value="">
  120. <input type="hidden" id="upload-modal-source" name="source" value="">
  121. <p>
  122. <div id="upload-modal-need-description" class="upload-modal-error error hidden-regular">${msg.required.description}</div>
  123. <div id="upload-modal-need-rights" class="upload-modal-error error hidden-regular">${msg.required.rights}</div>
  124. <div class="upload-modal-buttondiv">
  125. <button id="upload-modal-start-upload" disabled class="pure-button pure-button-primary button-rounded" type="submit"><span class="fa fa-fw fa-cloud-upload spaced-icon">&nbsp;</span>${msg.start}</button><span id="upload-modal-spinner" class="fa fa-spinner fa-spin hidden-regular"></span>
  126. </div>
  127. <div class="error" id="upload-errors"></div>
  128. </div>
  129. </form>
  130. </div>
  131. <div id="upload-modal-page-2" class="hidden-regular">
  132. <span id="upload-modal-cancel-metadata"><span class="fa fa-chevron-left fa-fw">&nbsp;</span> ${msg.cancel}
  133. </span>
  134. <p>
  135. <p>
  136. <form id="upload-metadata-form" class="pure-form">
  137. <label for="upload-metadata-creator">${msg.creator}<span class="required"> *</span></label><br>
  138. <input id="upload-metadata-creator" data-required type="text" class="pure-input-1" placeholder="${msg.placeholder.creator}">
  139. <p>
  140. <label for="upload-metadata-source">${msg.source}<span class="required"> *</span></label><br>
  141. <input id="upload-metadata-source" data-required type="text" class="pure-input-1" name="source" placeholder="${msg.placeholder.source}">
  142. <p>
  143. <label for="upload-metadata-license">${msg.license}<span class="required"> *</span></label><br>
  144. <select id="upload-metadata-license" data-required class="pure-input-1" name="license">
  145. <option value="" disabled selected>${msg.placeholder.license}</option>
  146. <option value="fair-use">${msg.licenses['fair-use']}</option>
  147. <option value="cc-0">${msg.licenses['cc-0']}</option>
  148. <option value="cc-by">${msg.licenses['cc-by']}</option>
  149. <option value="cc-by-sa">${msg.licenses['cc-by-sa']}</option>
  150. </select>
  151. <p>
  152. <button id="upload-modal-confirm-metadata" data-check-required class="pure-button pure-button-primary button-rounded">
  153. ${msg.ok}
  154. </button>
  155. </form>
  156. </div>
  157. </div>`
  158. ),
  159. enableSpinner = () => $('#upload-modal-spinner').removeClass('hidden-regular'),
  160. disableSpinner = () => $('#upload-modal-spinner').addClass('hidden-regular'),
  161. enableUpload = () => $('#upload-modal-start-upload').prop('disabled', false),
  162. disableUpload = () => $('#upload-modal-start-upload').prop('disabled', true);
  163. function expandModal() {
  164. let files = $('#upload-input')[0].files;
  165. $('#upload-label-text').text(files[0].name);
  166. enableUpload();
  167. $('#upload-modal-page-1-expansion').slideDown(200);
  168. $('#upload-modal-description').focus();
  169. }
  170. function showPage2() {
  171. $('#upload-modal-page-1').hide(200);
  172. $('#upload-modal-page-2').show(200);
  173. $('#upload-metadata-creator').focus();
  174. }
  175. function resetMetadata() {
  176. $('#upload-modal-license').val('cc-by-sa');
  177. $('#upload-modal-source').val('');
  178. $('#upload-modal-creator').val('');
  179. }
  180. function cancelPage2() {
  181. $('#upload-modal-page-2').hide(200);
  182. $('#upload-modal-page-1').show(200);
  183. // Reset radio selection in case we don't have all the required data
  184. if (!$('#upload-modal-license').val() || !$('#upload-modal-creator').val() ||
  185. !$('#upload-modal-source').val())
  186. $('#upload-modal-other').prop('checked', false);
  187. }
  188. function startUpload(successCallback, errorCallback) {
  189. const hasDescription = Boolean($('#upload-modal-description').val());
  190. const hasRights = $('#upload-modal-ownwork').prop('checked') ||
  191. $('#upload-modal-other').prop('checked');
  192. $('#upload-modal-need-description').toggle(!hasDescription);
  193. $('#upload-modal-need-rights').toggle(!hasRights);
  194. if (!hasDescription || !hasRights)
  195. return;
  196. const form = $('#upload-modal-form')[0];
  197. const data = new FormData(form);
  198. enableSpinner();
  199. disableUpload();
  200. $('#upload-errors').empty();
  201. $.ajax({
  202. url: '/api/actions/upload',
  203. data,
  204. cache: false,
  205. contentType: false,
  206. processData: false,
  207. type: 'POST',
  208. })
  209. .done(data => {
  210. disableSpinner();
  211. $.modal.close();
  212. if (successCallback)
  213. successCallback(data.uploads);
  214. })
  215. .fail(data => {
  216. const errorArray = [];
  217. disableSpinner();
  218. enableUpload();
  219. let showGenericError = true;
  220. if (data && data.responseJSON && data.responseJSON.errors) {
  221. errorArray.push(data.responseJSON.errors);
  222. let allErrors = '';
  223. for (let error of data.responseJSON.errors) {
  224. if (error.displayMessage) {
  225. allErrors += error.displayMessage + '<br>';
  226. showGenericError = false;
  227. }
  228. }
  229. if (allErrors)
  230. $('#upload-errors').html(allErrors);
  231. } else {
  232. errorArray.push('Unknown error');
  233. }
  234. if (showGenericError)
  235. $('#upload-errors').html(msg.error);
  236. if (errorCallback)
  237. errorCallback(errorArray);
  238. });
  239. }
  240. function confirmMetadata(event) {
  241. const license = $('#upload-metadata-license').val(),
  242. source = $('#upload-metadata-source').val(),
  243. creator = $('#upload-metadata-creator').val();
  244. $('#upload-modal-license').val(license);
  245. $('#upload-modal-source').val(source);
  246. $('#upload-modal-creator').val(creator);
  247. const info = `
  248. ${msg.specified}<br>
  249. <table id="upload-modal-metadata-info">
  250. <tr><td><b>${msg.creator}</b></td><td>${creator}</td></tr>
  251. <tr><td><b>${msg.source}</b></td><td>${source}</td></tr>
  252. <tr><td><b>${msg.license}</b></td><td>${msg.licenses[license]}</td></tr>
  253. </table>
  254. `;
  255. $('#upload-modal-other-label').html(info);
  256. $('#upload-modal-page-2').hide(200);
  257. $('#upload-modal-page-1').show(200);
  258. event.preventDefault();
  259. }