diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 738faed172..d36e7d7fea 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -37,6 +37,7 @@ Changelog
* Move translations in `nl_NL` to `nl` (Loïc Teixeira, Coen van der Kamp)
* Add documentation for how to redirect to a separate page on Form builder submissions using ``RoutablePageMixin`` (Nick Smith)
* Refactored index listing views and made column sort-by headings more consistent (Matt Westcott)
+ * Added the ability to customise the pre-filled Image title on upload and it now defaults to the filename without the file extension (LB Johnston)
* Fix: Delete button is now correct colour on snippets and modeladmin listings (Brandon Murch)
* Fix: Ensure that StreamBlock / ListBlock-level validation errors are counted towards error counts (Matt Westcott)
* Fix: InlinePanel add button is now keyboard navigatable (Jesse Menn)
diff --git a/docs/advanced_topics/images/index.rst b/docs/advanced_topics/images/index.rst
index 8876ac7ed7..5633d2ae0c 100644
--- a/docs/advanced_topics/images/index.rst
+++ b/docs/advanced_topics/images/index.rst
@@ -13,3 +13,4 @@ Images
feature_detection
image_serve_view
focal_points
+ title_generation_on_upload
diff --git a/docs/advanced_topics/images/title_generation_on_upload.md b/docs/advanced_topics/images/title_generation_on_upload.md
new file mode 100644
index 0000000000..ad2affba60
--- /dev/null
+++ b/docs/advanced_topics/images/title_generation_on_upload.md
@@ -0,0 +1,108 @@
+# Title generation on upload
+
+Override how the title is set when adding a single image, multiple images or uploading an image within a chooser modal.
+
+When a file is dropped into the multi-upload page or selected on the single file selection form a title will be automatically populated into the Title field. The default behaviour is to use the image's filename excluding the extension.
+
+You can customise the resolved value of this title using a JavaScript [event listener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) which will listen to the `'wagtail:images-upload'` event.
+
+The simplest way to add JavaScript to the editor is via the [`insert_global_admin_js` hook](../../reference/hooks.html#insert-global-admin-js), however any JavaScript that adds the event listener will work.
+
+## DOM Event
+
+The event name to listen for is `'wagtail:images-upload'`. It will be dispatched on the image upload `form`. The event's `detail` attribute will contain:
+
+- `data` - An object which includes the `title` to be used. It is the filename with the extension removed.
+- `maxTitleLength` - An integer (or `null`) which is the maximum length of the `Image` model title field.
+- `filename` - The original filename without the extension removed.
+
+To modify the generated `Image` title, access and update `event.detail.data.title`, no return value is needed.
+
+For single image uploads, the custom event will only run if the title does not already have a value so that we do not overwrite whatever the user has typed.
+
+You can prevent the default behaviour by calling `event.preventDefault()`. For the single upload page or modals, this will not pre-fill any value into the title. For multiple upload, this will avoid any title submission and use the filename title only (with file extension) as a title is required to save the image.
+
+The event will 'bubble' up so that you can simply add a global `document` listener to capture all of these events, or you can scope your listener or handler logic as needed to ensure you only adjust titles in some specific scenarios.
+
+See MDN for more information about [custom JavasScript events](https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events).
+
+## Code Examples
+
+### Removing any url unsafe characters from the title
+
+```python
+# wagtail_hooks.py
+from django.utils.safestring import mark_safe
+
+from wagtail.core import hooks
+
+
+@hooks.register("insert_global_admin_js")
+def get_global_admin_js():
+ return mark_safe(
+ """
+
+ """
+ )
+```
+
+### Changing generated titles on the page editor only to remove dashes/underscores
+
+Using the [`insert_editor_js` hook](../../reference/hooks.html#insert-editor-js) instead so that this script will not run on the `Image` upload page, only on page editors.
+
+```python
+# wagtail_hooks.py
+from django.utils.safestring import mark_safe
+
+from wagtail.core import hooks
+
+
+@hooks.register("insert_editor_js")
+def get_global_admin_js():
+ return mark_safe(
+ """
+
+ """
+ )
+```
+
+### Stopping pre-filling of title based on filename
+
+```python
+# wagtail_hooks.py
+from django.utils.safestring import mark_safe
+
+from wagtail.core import hooks
+
+
+@hooks.register("insert_global_admin_js")
+def get_global_admin_js():
+ return mark_safe(
+ """
+
+ """
+ )
+```
diff --git a/docs/releases/2.15.rst b/docs/releases/2.15.rst
index 1d5e071490..cdf1dc7c1c 100644
--- a/docs/releases/2.15.rst
+++ b/docs/releases/2.15.rst
@@ -53,6 +53,7 @@ Other features
* Translations in ``nl_NL`` are moved to the ``nl`` po files. ``nl_NL`` translation files are deleted. Projects that use ``LANGUAGE_CODE = 'nl-nl'`` will automatically fallback to ``nl``. (Loïc Teixeira, Coen van der Kamp)
* Add documentation for how to redirect to a separate page on Form builder submissions using ``RoutablePageMixin`` (Nick Smith)
* Refactored index listing views and made column sort-by headings more consistent (Matt Westcott)
+ * Added the ability to customise the pre-filled Image title on upload and it now defaults to the filename without the file extension (LB Johnston)
Bug fixes
~~~~~~~~~
diff --git a/wagtail/admin/views/generic/multiple_upload.py b/wagtail/admin/views/generic/multiple_upload.py
index f81837e04f..e33c391a87 100644
--- a/wagtail/admin/views/generic/multiple_upload.py
+++ b/wagtail/admin/views/generic/multiple_upload.py
@@ -125,7 +125,7 @@ class AddView(PermissionCheckedMixin, TemplateView):
# Build a form for validation
upload_form_class = self.get_upload_form_class()
form = upload_form_class({
- 'title': request.FILES['files[]'].name,
+ 'title': request.POST.get('title', request.FILES['files[]'].name),
'collection': request.POST.get('collection'),
}, {
'file': request.FILES['files[]'],
diff --git a/wagtail/images/static_src/wagtailimages/js/add-multiple.js b/wagtail/images/static_src/wagtailimages/js/add-multiple.js
index 183b0f89ff..c9825fbf8b 100644
--- a/wagtail/images/static_src/wagtailimages/js/add-multiple.js
+++ b/wagtail/images/static_src/wagtailimages/js/add-multiple.js
@@ -97,6 +97,35 @@ $(function() {
}
},
+ /**
+ * Allow a custom title to be defined by an event handler for this form.
+ * If event.preventDefault is called, the original behaviour of using the raw
+ * filename (with extension) as the title is preserved.
+ *
+ * @example
+ * document.addEventListener('wagtail:images-upload', function(event) {
+ * // remove file extension
+ * var newTitle = (event.detail.data.title || '').replace(/\.[^\.]+$/, "");
+ * event.detail.data.title = newTitle;
+ * });
+ *
+ * @param {HtmlElement[]} form
+ * @returns {{name: 'string', value: *}[]}
+ */
+ formData: function(form) {
+ var filename = this.files[0].name;
+ var data = { title: filename.replace(/\.[^\.]+$/, '') };
+ var maxTitleLength = window.fileupload_opts.max_title_length;
+
+ var event = form.get(0).dispatchEvent(new CustomEvent(
+ 'wagtail:images-upload',
+ { bubbles: true, cancelable: true, detail: { data: data, filename: filename, maxTitleLength: maxTitleLength } }
+ ));
+
+ // default behaviour (title is just file name)
+ return event ? form.serializeArray().concat({ name:'title', value: data.title }) : form.serializeArray();
+ },
+
done: function(e, data) {
var itemElement = $(data.context);
var response = JSON.parse(data.result);
diff --git a/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js b/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js
index 8b46c3f4b3..cdc47ec0c8 100644
--- a/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js
+++ b/wagtail/images/static_src/wagtailimages/js/image-chooser-modal.js
@@ -34,11 +34,26 @@ function ajaxifyImageUploadForm(modal) {
fileWidget.on('change', function () {
var titleWidget = $('#id_image-chooser-upload-title', modal.body);
var title = titleWidget.val();
+ // do not override a title that already exists (from manual editing or previous upload)
if (title === '') {
// The file widget value example: `C:\fakepath\image.jpg`
var parts = fileWidget.val().split('\\');
- var fileName = parts[parts.length - 1];
- titleWidget.val(fileName);
+ var filename = parts[parts.length - 1];
+
+ // allow event handler to override filename (used for title) & provide maxLength as int to event
+ var maxTitleLength = parseInt(titleWidget.attr('maxLength') || '0', 10) || null;
+ var data = { title: filename.replace(/\.[^\.]+$/, '') };
+
+ // allow an event handler to customise data or call event.preventDefault to stop any title pre-filling
+ var form = fileWidget.closest('form').get(0);
+ var event = form.dispatchEvent(new CustomEvent(
+ 'wagtail:images-upload',
+ { bubbles: true, cancelable: true, detail: { data: data, filename: filename, maxTitleLength: maxTitleLength } }
+ ));
+
+ if (!event) return; // do not set a title if event.preventDefault(); is called by handler
+
+ titleWidget.val(data.title);
}
});
}
diff --git a/wagtail/images/templates/wagtailimages/images/add.html b/wagtail/images/templates/wagtailimages/images/add.html
index 849048d391..c88d35a71a 100644
--- a/wagtail/images/templates/wagtailimages/images/add.html
+++ b/wagtail/images/templates/wagtailimages/images/add.html
@@ -11,6 +11,31 @@
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}