From 599b869991e8b0c77d29875e0a1ed4bc4e8f6de3 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 5 Aug 2018 15:58:07 +0200 Subject: [PATCH] Add media clipboard action to set category Close #2658 --- com.woltlab.wcf/clipboardAction.xml | 9 + .../templates/__mediaSetCategoryDialog.tpl | 28 +++ com.woltlab.wcf/templates/mediaJavaScript.tpl | 3 +- .../templates/__mediaSetCategoryDialog.tpl | 28 +++ .../files/acp/templates/mediaJavaScript.tpl | 3 +- .../install/files/acp/templates/mediaList.tpl | 6 +- .../Core/Controller/Media/List.js | 68 ++++--- .../js/WoltLabSuite/Core/Media/Clipboard.js | 173 ++++++++++++++++++ .../WoltLabSuite/Core/Media/Manager/Base.js | 49 +++-- .../WoltLabSuite/Core/Media/Manager/Editor.js | 22 +-- .../lib/data/media/MediaAction.class.php | 71 +++++++ .../action/MediaClipboardAction.class.php | 22 ++- wcfsetup/install/lang/de.xml | 4 +- wcfsetup/install/lang/en.xml | 2 + 14 files changed, 409 insertions(+), 79 deletions(-) create mode 100644 com.woltlab.wcf/templates/__mediaSetCategoryDialog.tpl create mode 100644 wcfsetup/install/files/acp/templates/__mediaSetCategoryDialog.tpl create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Media/Clipboard.js diff --git a/com.woltlab.wcf/clipboardAction.xml b/com.woltlab.wcf/clipboardAction.xml index a4b084e707..d62400fd38 100644 --- a/com.woltlab.wcf/clipboardAction.xml +++ b/com.woltlab.wcf/clipboardAction.xml @@ -105,6 +105,15 @@ menuManagerDialog-select + + wcf\system\clipboard\action\MediaClipboardAction + 3 + + wcf\acp\page\MediaListPage + menuManagerDialog-editor + menuManagerDialog-select + + diff --git a/com.woltlab.wcf/templates/__mediaSetCategoryDialog.tpl b/com.woltlab.wcf/templates/__mediaSetCategoryDialog.tpl new file mode 100644 index 0000000000..9e0c3348e6 --- /dev/null +++ b/com.woltlab.wcf/templates/__mediaSetCategoryDialog.tpl @@ -0,0 +1,28 @@ +
+
+
+ +
+
+ +
+ +
diff --git a/com.woltlab.wcf/templates/mediaJavaScript.tpl b/com.woltlab.wcf/templates/mediaJavaScript.tpl index 082d395fda..b8c5d5fed7 100644 --- a/com.woltlab.wcf/templates/mediaJavaScript.tpl +++ b/com.woltlab.wcf/templates/mediaJavaScript.tpl @@ -17,7 +17,8 @@ 'wcf.media.search.noResults': '{lang}wcf.media.search.noResults{/lang}', 'wcf.media.upload.error.noImage': '{lang}wcf.media.upload.error.noImage{/lang}', 'wcf.media.upload.error.uploadFailed': '{lang}wcf.media.upload.error.uploadFailed{/lang}', - 'wcf.media.upload.success': '{lang}wcf.media.upload.success{/lang}' + 'wcf.media.upload.success': '{lang}wcf.media.upload.success{/lang}', + 'wcf.media.setCategory': '{lang}wcf.media.setCategory{/lang}' }); Permission.add('admin.content.cms.canManageMedia', {if $__wcf->session->getPermission('admin.content.cms.canManageMedia')}true{else}false{/if}); diff --git a/wcfsetup/install/files/acp/templates/__mediaSetCategoryDialog.tpl b/wcfsetup/install/files/acp/templates/__mediaSetCategoryDialog.tpl new file mode 100644 index 0000000000..9e0c3348e6 --- /dev/null +++ b/wcfsetup/install/files/acp/templates/__mediaSetCategoryDialog.tpl @@ -0,0 +1,28 @@ +
+
+
+ +
+
+ +
+ +
diff --git a/wcfsetup/install/files/acp/templates/mediaJavaScript.tpl b/wcfsetup/install/files/acp/templates/mediaJavaScript.tpl index 77911300c2..bfa7c9750b 100644 --- a/wcfsetup/install/files/acp/templates/mediaJavaScript.tpl +++ b/wcfsetup/install/files/acp/templates/mediaJavaScript.tpl @@ -17,7 +17,8 @@ 'wcf.media.search.noResults': '{lang}wcf.media.search.noResults{/lang}', 'wcf.media.upload.error.noImage': '{lang}wcf.media.upload.error.noImage{/lang}', 'wcf.media.upload.error.uploadFailed': '{lang}wcf.media.upload.error.uploadFailed{/lang}', - 'wcf.media.upload.success': '{lang}wcf.media.upload.success{/lang}' + 'wcf.media.upload.success': '{lang}wcf.media.upload.success{/lang}', + 'wcf.media.setCategory': '{lang}wcf.media.setCategory{/lang}' }); Permission.add('admin.content.cms.canManageMedia', {if $__wcf->session->getPermission('admin.content.cms.canManageMedia')}true{else}false{/if}); diff --git a/wcfsetup/install/files/acp/templates/mediaList.tpl b/wcfsetup/install/files/acp/templates/mediaList.tpl index 62aea3cbdf..2569f6beb6 100644 --- a/wcfsetup/install/files/acp/templates/mediaList.tpl +++ b/wcfsetup/install/files/acp/templates/mediaList.tpl @@ -4,7 +4,11 @@ {include file='mediaJavaScript'} require(['Language', 'WoltLabSuite/Core/Controller/Media/List'], function (Language, ControllerMediaList) { - Language.add('wcf.media.delete.confirmMessage', '{lang __literal=true}wcf.media.delete.confirmMessage{/lang}') + Language.addObject({ + 'wcf.media.delete.confirmMessage': '{lang __literal=true}wcf.media.delete.confirmMessage{/lang}', + 'wcf.media.setCategory': '{lang}wcf.media.setCategory{/lang}' + }); + ControllerMediaList.init({ {if $categoryID} categoryId: {@$categoryID}, diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Media/List.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Media/List.js index 2da40a9aa4..d5cc2cb1a7 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Media/List.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Media/List.js @@ -10,6 +10,7 @@ define([ 'Dom/ChangeListener', 'EventHandler', 'WoltLabSuite/Core/Controller/Clipboard', + 'WoltLabSuite/Core/Media/Clipboard', 'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/List/Upload' ], @@ -17,6 +18,7 @@ define([ DomChangeListener, EventHandler, Clipboard, + MediaClipboard, MediaEditor, MediaListUpload ) { @@ -26,7 +28,9 @@ define([ var Fake = function() {}; Fake.prototype = { init: function() {}, - _clipboardAction: function() {}, + _addButtonEventListeners: function() {}, + _deleteCallback: function() {}, + _deleteMedia: function(mediaIds) {}, _edit: function() {} }; return Fake; @@ -34,6 +38,7 @@ define([ var _mediaEditor; var _tableBody = elById('mediaListTableBody'); + var _clipboardObjectIds = []; /** * @exports WoltLabSuite/Core/Controller/Media/List @@ -46,12 +51,12 @@ define([ multiple: true }); - Clipboard.setup({ - hasMarkedItems: options.hasMarkedItems || false, - pageClassName: 'wcf\\acp\\page\\MediaListPage' - }); + MediaClipboard.init( + 'wcf\\acp\\page\\MediaListPage', + options.hasMarkedItems || false, + this + ); - EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this)); EventHandler.add('com.woltlab.wcf.media.upload', 'removedErroneousUploadRow', this._deleteCallback.bind(this)); var deleteAction = new WCF.Action.Delete('wcf\\data\\media\\MediaAction', '.jsMediaRow'); @@ -107,43 +112,34 @@ define([ }, /** - * Handles successful clipboard actions. + * Is called when a media edit icon is clicked. * - * @param {object} actionData + * @param {Event} event */ - _clipboardAction: function(actionData) { - // only consider events if the action has been executed - if (actionData.responseData === null) { - return; - } - - if (actionData.data.actionName === 'com.woltlab.wcf.media.delete') { - var mediaIds = actionData.responseData.objectIDs; - - var mediaRows = elByClass('jsMediaRow'); - for (var i = 0; i < mediaRows.length; i++) { - var media = mediaRows[i]; - var mediaID = ~~elData(elByClass('jsClipboardItem', media)[0], 'object-id'); - - if (mediaIds.indexOf(mediaID) !== -1) { - elRemove(media); - i--; - } - } - - if (!mediaRows.length) { - window.location.reload(); - } - } + _edit: function(event) { + _mediaEditor.edit(elData(event.currentTarget, 'object-id')); }, /** - * Is called when a media edit icon is clicked. + * Is called after the media files with the given ids have been deleted via clipboard. * - * @param {Event} event + * @param {int[]} mediaIds ids of deleted media files */ - _edit: function(event) { - _mediaEditor.edit(elData(event.currentTarget, 'object-id')); + clipboardDeleteMedia: function(mediaIds) { + var mediaRows = elByClass('jsMediaRow'); + for (var i = 0; i < mediaRows.length; i++) { + var media = mediaRows[i]; + var mediaID = ~~elData(elByClass('jsClipboardItem', media)[0], 'object-id'); + + if (mediaIds.indexOf(mediaID) !== -1) { + elRemove(media); + i--; + } + } + + if (!mediaRows.length) { + window.location.reload(); + } } } }); \ No newline at end of file diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Clipboard.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Clipboard.js new file mode 100644 index 0000000000..7bb6045fce --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Clipboard.js @@ -0,0 +1,173 @@ +/** + * Initializes modules required for media clipboard. + * + * @author Matthias Schmidt + * @copyright 2001-2018 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Media/Clipboard + */ +define([ + 'Ajax', + 'Dom/ChangeListener', + 'EventHandler', + 'Language', + 'Ui/Dialog', + 'Ui/Notification', + 'WoltLabSuite/Core/Controller/Clipboard', + 'WoltLabSuite/Core/Media/Editor', + 'WoltLabSuite/Core/Media/List/Upload' + ], + function( + Ajax, + DomChangeListener, + EventHandler, + Language, + UiDialog, + UiNotification, + Clipboard, + MediaEditor, + MediaListUpload + ) { + "use strict"; + + if (!COMPILER_TARGET_DEFAULT) { + var Fake = function() {}; + Fake.prototype = { + init: function() {}, + _ajaxSetup: function() {}, + _ajaxSuccess: function() {}, + _clipboardAction: function() {}, + _dialogSetup: function() {}, + _edit: function() {}, + _setCategory: function() {} + }; + return Fake; + } + + var _clipboardObjectIds = []; + var _mediaManager; + + /** + * @exports WoltLabSuite/Core/Media/Clipboard + */ + return { + init: function(pageClassName, hasMarkedItems, mediaManager) { + Clipboard.setup({ + hasMarkedItems: hasMarkedItems, + pageClassName: pageClassName + }); + + _mediaManager = mediaManager; + + EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this)); + }, + + /** + * Returns the data used to setup the AJAX request object. + * + * @return {object} setup data + */ + _ajaxSetup: function() { + return { + data: { + className: 'wcf\\data\\media\\MediaAction' + } + } + }, + + /** + * Handles successful AJAX request. + * + * @param {object} data response data + */ + _ajaxSuccess: function(data) { + switch (data.actionName) { + case 'getSetCategoryDialog': + UiDialog.open(this, data.returnValues.template); + + break; + + case 'setCategory': + UiDialog.close(this); + + UiNotification.show(); + + Clipboard.reload(); + + break; + } + }, + + /** + * Returns the data used to setup the dialog. + * + * @return {object} setup data + */ + _dialogSetup: function() { + return { + id: 'mediaSetCategoryDialog', + options: { + onSetup: function(content) { + elBySel('button', content).addEventListener(WCF_CLICK_EVENT, function(event) { + event.preventDefault(); + + this._setCategory(~~elBySel('select[name="categoryID"]', content).value); + + event.currentTarget.disabled = true; + }.bind(this)); + }.bind(this), + title: Language.get('wcf.media.setCategory') + }, + source: null + } + }, + + /** + * Handles successful clipboard actions. + * + * @param {object} actionData + */ + _clipboardAction: function(actionData) { + var mediaIds = actionData.data.parameters.objectIDs; + + switch (actionData.data.actionName) { + case 'com.woltlab.wcf.media.delete': + // only consider events if the action has been executed + if (actionData.responseData !== null) { + _mediaManager.clipboardDeleteMedia(mediaIds); + } + + break; + + case 'com.woltlab.wcf.media.insert': + _mediaManager.clipboardInsertMedia(mediaIds); + + break; + + case 'com.woltlab.wcf.media.setCategory': + _clipboardObjectIds = mediaIds; + + Ajax.api(this, { + actionName: 'getSetCategoryDialog' + }); + + break; + } + }, + + /** + * Sets the category of the marked media files. + * + * @param {int} categoryID selected category id + */ + _setCategory: function(categoryID) { + Ajax.api(this, { + actionName: 'setCategory', + objectIDs: _clipboardObjectIds, + parameters: { + categoryID: categoryID + } + }); + } + } +}); \ No newline at end of file diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Base.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Base.js index 76672ed8a8..f7f05fa88d 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Base.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Base.js @@ -12,14 +12,16 @@ define( 'Dom/Util', 'EventHandler', 'Language', 'List', 'Permission', 'Ui/Dialog', 'Ui/Notification', 'WoltLabSuite/Core/Controller/Clipboard', 'WoltLabSuite/Core/Media/Editor', 'WoltLabSuite/Core/Media/Upload', 'WoltLabSuite/Core/Media/Manager/Search', 'StringUtil', - 'WoltLabSuite/Core/Ui/Pagination' + 'WoltLabSuite/Core/Ui/Pagination', + 'WoltLabSuite/Core/Media/Clipboard' ], function( Core, Dictionary, DomChangeListener, DomTraverse, DomUtil, EventHandler, Language, List, Permission, UiDialog, UiNotification, Clipboard, MediaEditor, MediaUpload, MediaManagerSearch, StringUtil, - UiPagination + UiPagination, + MediaClipboard ) { "use strict"; @@ -29,7 +31,6 @@ define( Fake.prototype = { _addButtonEventListeners: function() {}, _click: function() {}, - _clipboardAction: function() {}, _dialogClose: function() {}, _dialogInit: function() {}, _dialogSetup: function() {}, @@ -40,6 +41,7 @@ define( _removeClipboardCheckboxes: function() {}, _setMedia: function() {}, addMedia: function() {}, + clipboardDeleteMedia: function() {}, getDialog: function() {}, getMode: function() {}, getOption: function() {}, @@ -118,23 +120,6 @@ define( UiDialog.open(this); }, - /** - * Reacts to executed clipboard actions. - * - * @param {object} actionData data of the executed clipboard action - */ - _clipboardAction: function(actionData) { - // only consider events if the action has been executed - if (actionData.data.actionName === 'com.woltlab.wcf.media.delete' && actionData.responseData !== null) { - var mediaIds = actionData.responseData.objectIDs; - for (var i = 0, length = mediaIds.length; i < length; i++) { - this.removeMedia(~~mediaIds[i], true); - } - - UiNotification.show(); - } - }, - /** * Is called if the media manager dialog is closed. */ @@ -227,12 +212,11 @@ define( } if (Permission.get('admin.content.cms.canManageMedia') || this._forceClipboard) { - EventHandler.add('com.woltlab.wcf.clipboard', 'com.woltlab.wcf.media', this._clipboardAction.bind(this)); - - Clipboard.setup({ - hasMarkedItems: this._hadInitiallyMarkedItems ? true : false, - pageClassName: 'menuManagerDialog-' + this.getMode() - }); + MediaClipboard.init( + 'menuManagerDialog-' + this.getMode(), + this._hadInitiallyMarkedItems ? true : false, + this + ); } else { this._removeClipboardCheckboxes(); @@ -414,6 +398,19 @@ define( } }, + /** + * Is called after the media files with the given ids have been deleted via clipboard. + * + * @param {int[]} mediaIds ids of deleted media files + */ + clipboardDeleteMedia: function(mediaIds) { + for (var i = 0, length = mediaIds.length; i < length; i++) { + this.removeMedia(~~mediaIds[i], true); + } + + UiNotification.show(); + }, + /** * Returns the id of the currently selected category or `0` if no category is selected. * diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js index 20220bd121..9f0268de65 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Media/Manager/Editor.js @@ -16,7 +16,6 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permi _addButtonEventListeners: function() {}, _buildInsertDialog: function() {}, _click: function() {}, - _clipboardAction: function() {}, _getInsertDialogId: function() {}, _getThumbnailSizes: function() {}, _insertMedia: function() {}, @@ -36,6 +35,7 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permi _removeClipboardCheckboxes: function() {}, _setMedia: function() {}, addMedia: function() {}, + clipboardInsertMedia: function() {}, getDialog: function() {}, getOption: function() {}, removeMedia: function() {}, @@ -179,17 +179,6 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permi MediaManagerEditor._super.prototype._click.call(this, event); }, - /** - * @see WoltLabSuite/Core/Media/Manager/Base#_clipboardAction - */ - _clipboardAction: function(actionData) { - MediaManagerEditor._super.prototype._clipboardAction.call(this, actionData); - - if (actionData.data.actionName === 'com.woltlab.wcf.media.insert') { - this.insertMedia(actionData.data.parameters.objectIDs, true); - } - }, - /** * @see WoltLabSuite/Core/Media/Manager/Base#_dialogShow */ @@ -401,6 +390,15 @@ define(['Core', 'Dictionary', 'Dom/Traverse', 'EventHandler', 'Language', 'Permi this.insertMedia([~~elData(event.currentTarget, 'object-id')]); }, + /** + * Is called to insert the media files with the given ids into an editor. + * + * @param {int[]} mediaIds + */ + clipboardInsertMedia: function(mediaIds) { + this.insertMedia(mediaIds, true); + }, + /** * Prepares insertion of the media files with the given ids. * diff --git a/wcfsetup/install/files/lib/data/media/MediaAction.class.php b/wcfsetup/install/files/lib/data/media/MediaAction.class.php index 9976e12916..be6f37ad44 100644 --- a/wcfsetup/install/files/lib/data/media/MediaAction.class.php +++ b/wcfsetup/install/files/lib/data/media/MediaAction.class.php @@ -8,6 +8,7 @@ use wcf\system\acl\simple\SimpleAclHandler; use wcf\system\category\CategoryHandler; use wcf\system\clipboard\ClipboardHandler; use wcf\system\database\util\PreparedStatementConditionBuilder; +use wcf\system\exception\IllegalLinkException; use wcf\system\exception\PermissionDeniedException; use wcf\system\exception\UserInputException; use wcf\system\language\I18nHandler; @@ -567,4 +568,74 @@ class MediaAction extends AbstractDatabaseObjectAction implements ISearchAction, ClipboardHandler::getInstance()->unmark($mediaIDs, ClipboardHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.media')); } } + + /** + * Validates the `getSetCategoryDialog` action. + * + * @throws PermissionDeniedException if user is not allowed to set category of media files + * @throws IllegalLinkException if no media file categories exist + */ + public function validateGetSetCategoryDialog() { + if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia')) { + throw new PermissionDeniedException(); + } + + if (empty(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category'))) { + throw new IllegalLinkException(); + } + } + + /** + * Returns the dialog to set the category of multiple media files. + * + * @return string[] + */ + public function getSetCategoryDialog() { + $categoryList = (new CategoryNodeTree('com.woltlab.wcf.media.category'))->getIterator(); + $categoryList->setMaxDepth(0); + + return [ + 'template' => WCF::getTPL()->fetch('__mediaSetCategoryDialog', 'wcf', [ + 'categoryList' => $categoryList + ]) + ]; + } + + /** + * Validates the `setCategory` action. + * + * @throws UserInputException if no object ids are given + */ + public function validateSetCategory() { + $this->validateGetSetCategoryDialog(); + + if (empty($this->objects)) { + $this->readObjects(); + + if (empty($this->objects)) { + throw new UserInputException('objectIDs'); + } + } + + $this->readInteger('categoryID', true); + } + + /** + * Sets the category of multiple media files. + */ + public function setCategory() { + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('mediaID IN (?)', [$this->objectIDs]); + + $sql = "UPDATE wcf" . WCF_N . "_media + SET categoryID = ? + " . $conditionBuilder; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array_merge( + [$this->parameters['categoryID'] ?: null], + $conditionBuilder->getParameters() + )); + + $this->unmarkItems(); + } } diff --git a/wcfsetup/install/files/lib/system/clipboard/action/MediaClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/MediaClipboardAction.class.php index 66312c8c8a..b3d079c53b 100644 --- a/wcfsetup/install/files/lib/system/clipboard/action/MediaClipboardAction.class.php +++ b/wcfsetup/install/files/lib/system/clipboard/action/MediaClipboardAction.class.php @@ -2,6 +2,7 @@ namespace wcf\system\clipboard\action; use wcf\data\clipboard\action\ClipboardAction; use wcf\data\media\MediaAction; +use wcf\system\category\CategoryHandler; use wcf\system\WCF; /** @@ -24,7 +25,8 @@ class MediaClipboardAction extends AbstractClipboardAction { */ protected $supportedActions = [ 'delete', - 'insert' + 'insert', + 'setCategory' ]; /** @@ -84,4 +86,22 @@ class MediaClipboardAction extends AbstractClipboardAction { public function validateInsert() { return array_keys($this->objects); } + + /** + * Returns the ids of the media files whose category can be set. + * + * @return integer[] + */ + public function validateSetCategory() { + if (!WCF::getSession()->getPermission('admin.content.cms.canManageMedia')) { + return []; + } + + // category can only be set if any category exists + if (empty(CategoryHandler::getInstance()->getCategories('com.woltlab.wcf.media.category'))) { + return []; + } + + return array_keys($this->objects); + } } diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index c6b4f07ea6..e8587e4ee0 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -2508,7 +2508,8 @@ Fehler sind beispielsweise: - + + @@ -3154,6 +3155,7 @@ E-Mail-Adresse: {@$emailAddress} {* this line ends with a space *} + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 06ecb2e3ca..ee58a1ba98 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -2442,6 +2442,7 @@ Errors are: + @@ -3099,6 +3100,7 @@ Email: {@$emailAddress} {* this line ends with a space *} 1}s{/if}.]]> + -- 2.20.1