{include file='header' pageTitle='wcf.acp.article.list'}
<script data-relocate="true">
- require(['WoltLabSuite/Core/Ui/User/Search/Input'], function(UiUserSearchInput) {
+ require(['Language', 'WoltLabSuite/Core/Ui/User/Search/Input', 'WoltLabSuite/Core/Acp/Ui/Article/InlineEditor'], function(Language, UiUserSearchInput, AcpUiArticleInlineEditor) {
+ Language.addObject({
+ 'wcf.message.status.deleted': '{lang}wcf.message.status.deleted{/lang}'
+ });
+
new UiUserSearchInput(elBySel('input[name="username"]'));
- });
-</script>
-
-<script data-relocate="true">
- $(function() {
- new WCF.Action.Delete('wcf\\data\\article\\ArticleAction', '.jsArticleRow');
- new WCF.Action.Toggle('wcf\\data\\article\\ArticleAction', '.jsArticleRow');
+ new AcpUiArticleInlineEditor();
});
</script>
<tbody>
{foreach from=$objects item=article}
- <tr class="jsArticleRow">
+ <tr class="jsArticleRow" data-object-id="{@$article->articleID}">
<td class="columnIcon">
<a href="{link controller='ArticleEdit' id=$article->articleID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon24 fa-pencil"></span></a>
{if $article->canDelete()}
- <span class="icon icon24 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$article->articleID}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.delete.confirmMessage{/lang}"></span>
+ <a href="#" class="jsButtonRestore jsTooltip" title="{lang}wcf.global.button.restore{/lang}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.restore.confirmMessage{/lang}"{if !$article->isDeleted} style="display: none"{/if}><span class="icon icon24 fa-refresh"></span></a>
+ <a href="#" class="jsButtonDelete jsTooltip" title="{lang}wcf.global.button.delete{/lang}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.delete.confirmMessage{/lang}"{if !$article->isDeleted} style="display: none"{/if}><span class="icon icon24 fa-times"></span></a>
+ <a href="#" class="jsButtonTrash jsTooltip" title="{lang}wcf.global.button.trash{/lang}" data-confirm-message-html="{lang __encode=true}wcf.acp.article.trash.confirmMessage{/lang}"{if $article->isDeleted} style="display: none"{/if}><span class="icon icon24 fa-times"></span></a>
{else}
<span class="icon icon24 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
{/if}
<div class="containerHeadline">
<h3>
+ {if $article->isDeleted}<span class="badge label red jsIconDeleted">{lang}wcf.message.status.deleted{/lang}</span>{/if}
{if $article->publicationStatus == 0}<span class="badge">{lang}wcf.acp.article.publicationStatus.unpublished{/lang}</span>{/if}
{if $article->publicationStatus == 2}<span class="badge" title="{$article->publicationDate|plainTime}">{lang}wcf.acp.article.publicationStatus.delayed{/lang}</span>{/if}
<a href="{link controller='ArticleEdit' id=$article->articleID}{/link}" title="{lang}wcf.acp.article.edit{/lang}" class="jsTooltip">{$article->title}</a>
--- /dev/null
+/**
+ * Handles article trash, restore and delete.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2017 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Acp/Ui/Article/InlineEditor
+ */
+define(['Ajax', 'Dictionary', 'Language', 'Ui/Confirmation', 'Ui/Notification'], function (Ajax, Dictionary, Language, UiConfirmation, UiNotification) {
+ "use strict";
+
+ var _articles = new Dictionary();
+
+ /**
+ * @constructor
+ */
+ function AcpUiArticleInlineEditor() { this.init(); }
+ AcpUiArticleInlineEditor.prototype = {
+ /**
+ * Initializes the ACP inline editor for articles.
+ */
+ init: function () {
+ elBySelAll('.jsArticleRow', undefined, this._initArticle.bind(this));
+ },
+
+ /**
+ * Initializes an article row element.
+ *
+ * @param {Element} article article row element
+ * @protected
+ */
+ _initArticle: function (article) {
+ var objectId = ~~elData(article, 'object-id');
+
+ var buttonDelete = elBySel('.jsButtonDelete', article);
+ buttonDelete.addEventListener(WCF_CLICK_EVENT, this._prompt.bind(this, objectId, 'delete'));
+
+ var buttonRestore = elBySel('.jsButtonRestore', article);
+ buttonRestore.addEventListener(WCF_CLICK_EVENT, this._prompt.bind(this, objectId, 'restore'));
+
+ var buttonTrash = elBySel('.jsButtonTrash', article);
+ buttonTrash.addEventListener(WCF_CLICK_EVENT, this._prompt.bind(this, objectId, 'trash'));
+
+ _articles.set(objectId, {
+ buttons: {
+ delete: buttonDelete,
+ restore: buttonRestore,
+ trash: buttonTrash
+ },
+ element: article
+ });
+ },
+
+ /**
+ * Prompts a user to confirm the clicked action before executing it.
+ *
+ * @param {int} objectId article id
+ * @param {string} actionName action name
+ * @param {Event} event event object
+ * @protected
+ */
+ _prompt: function (objectId, actionName, event) {
+ event.preventDefault();
+
+ var article = _articles.get(objectId);
+
+ UiConfirmation.show({
+ confirm: (function () { this._invoke(objectId, actionName) }).bind(this),
+ message: elData(article.buttons[actionName], 'confirm-message-html'),
+ messageIsHtml: true
+ });
+ },
+
+ /**
+ * Invokes the selected action.
+ *
+ * @param {int} objectId article id
+ * @param {string} actionName action name
+ * @protected
+ */
+ _invoke: function (objectId, actionName) {
+ Ajax.api(this, {
+ actionName: actionName,
+ objectIDs: [objectId]
+ });
+ },
+
+ _ajaxSuccess: function (data) {
+ var article = _articles.get(data.objectIDs[0]);
+ switch (data.actionName) {
+ case 'delete':
+ var tbody = article.element.parentNode;
+ elRemove(article.element);
+
+ if (elBySel('tr', tbody) === null) {
+ window.location.reload();
+ }
+ break;
+
+ case 'restore':
+ elHide(article.buttons.delete);
+ elHide(article.buttons.restore);
+ elShow(article.buttons.trash);
+
+ elRemove(elBySel('.jsIconDeleted', article.element));
+ break;
+
+ case 'trash':
+ elShow(article.buttons.delete);
+ elShow(article.buttons.restore);
+ elHide(article.buttons.trash);
+
+ var badge = elCreate('span');
+ badge.className = 'badge label red jsIconDeleted';
+ badge.textContent = Language.get('wcf.message.status.deleted');
+
+ var h3 = elBySel('.containerHeadline > h3', article.element);
+ h3.insertBefore(badge, h3.firstChild);
+
+ break;
+ }
+
+ UiNotification.show();
+ },
+
+ _ajaxSetup: function () {
+ return {
+ data: {
+ className: 'wcf\\data\\article\\ArticleAction'
+ }
+ }
+ }
+ };
+
+ return AcpUiArticleInlineEditor;
+});
* @property-read integer $comments number of comments on the article
* @property-read integer $views number of times the article has been viewed
* @property-read integer $cumulativeLikes cumulative result of likes (counting `+1`) and dislikes (counting `-1`) for the article
+ * @property-read integer $isDeleted is 1 if the article is in trash bin, otherwise 0
*/
class Article extends DatabaseObject implements ILinkableObject {
/**
use wcf\data\article\content\ArticleContentEditor;
use wcf\data\AbstractDatabaseObjectAction;
use wcf\system\comment\CommentHandler;
+use wcf\system\exception\UserInputException;
use wcf\system\language\LanguageFactory;
use wcf\system\like\LikeHandler;
use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
use wcf\system\search\SearchIndexManager;
use wcf\system\tagging\TagEngine;
+use wcf\system\WCF;
/**
* Executes article related actions.
* @method ArticleEditor getSingleObject()
*/
class ArticleAction extends AbstractDatabaseObjectAction {
+ /**
+ * article editor instance
+ * @var ArticleEditor
+ */
+ public $articleEditor;
+
/**
* @inheritDoc
*/
/**
* @inheritDoc
*/
- protected $requireACP = ['create', 'delete', 'update'];
+ protected $requireACP = ['create', 'delete', 'restore', 'trash', 'update'];
/**
* @inheritDoc
SearchIndexManager::getInstance()->delete('com.woltlab.wcf.article', $articleContentIDs);
}
}
+
+ /**
+ * Validates parameters to move an article to the trash bin.
+ *
+ * @throws UserInputException
+ */
+ public function validateTrash() {
+ WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+
+ $this->articleEditor = $this->getSingleObject();
+ if ($this->articleEditor->isDeleted) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ /**
+ * Moves an article to the trash bin.
+ */
+ public function trash() {
+ $this->articleEditor->update(['isDeleted' => 1]);
+ }
+
+ /**
+ * Validates parameters o restore an article.
+ *
+ * @throws UserInputException
+ */
+ public function validateRestore() {
+ WCF::getSession()->checkPermissions(['admin.content.article.canManageArticle']);
+
+ $this->articleEditor = $this->getSingleObject();
+ if (!$this->articleEditor->isDeleted) {
+ throw new UserInputException('objectIDs');
+ }
+ }
+
+ /**
+ * Restores an article.
+ */
+ public function restore() {
+ $this->articleEditor->update(['isDeleted' => 0]);
+ }
}
<item name="wcf.acp.article.publicationStatus.unpublished"><![CDATA[Unveröffentlicht]]></item>
<item name="wcf.acp.article.publicationStatus.published"><![CDATA[Veröffentlicht]]></item>
<item name="wcf.acp.article.publicationStatus.delayed"><![CDATA[Zeitgesteuerte Veröffentlichung]]></item>
+ <item name="wcf.acp.article.restore.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class="confirmationObject">{$article->getTitle()}</span> wirklich wiederherstellen?]]></item>
<item name="wcf.acp.article.teaser"><![CDATA[Einleitungstext]]></item>
+ <item name="wcf.acp.article.trash.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Artikel <span class="confirmationObject">{$article->getTitle()}</span> wirklich in den Papierkorb verschieben?]]></item>
<item name="wcf.acp.article.views"><![CDATA[Zugriffe]]></item>
</category>
<item name="wcf.global.button.preview"><![CDATA[Vorschau]]></item>
<item name="wcf.global.button.refresh"><![CDATA[Aktualisieren]]></item>
<item name="wcf.global.button.reset"><![CDATA[Zurücksetzen]]></item>
+ <item name="wcf.global.button.restore"><![CDATA[Wiederherstellen]]></item>
<item name="wcf.global.button.rss"><![CDATA[RSS-Feed]]></item>
<item name="wcf.global.button.save"><![CDATA[Speichern]]></item>
<item name="wcf.global.button.saveSorting"><![CDATA[Sortierung speichern]]></item>
<item name="wcf.global.button.search"><![CDATA[Suche]]></item>
<item name="wcf.global.button.submit"><![CDATA[Absenden]]></item>
+ <item name="wcf.global.button.trash"><![CDATA[Löschen]]></item>
<item name="wcf.global.button.upload"><![CDATA[Hochladen]]></item>
<item name="wcf.global.button.readMore"><![CDATA[Weiterlesen]]></item>
<item name="wcf.global.comments"><![CDATA[Kommentare]]></item>
<item name="wcf.acp.article.publicationStatus.unpublished"><![CDATA[Unpublished]]></item>
<item name="wcf.acp.article.publicationStatus.published"><![CDATA[Published]]></item>
<item name="wcf.acp.article.publicationStatus.delayed"><![CDATA[Delayed publishing]]></item>
+ <item name="wcf.acp.article.restore.confirmMessage"><![CDATA[Do you really want to restore the article <span class="confirmationObject">{$article->getTitle()}</span>?]]></item>
<item name="wcf.acp.article.teaser"><![CDATA[Teaser]]></item>
+ <item name="wcf.acp.article.trash.confirmMessage"><![CDATA[Do you really want to move the article <span class="confirmationObject">{$article->getTitle()}</span> to the trash bin?]]></item>
<item name="wcf.acp.article.views"><![CDATA[Views]]></item>
</category>
<item name="wcf.global.button.preview"><![CDATA[Preview]]></item>
<item name="wcf.global.button.refresh"><![CDATA[Refresh]]></item>
<item name="wcf.global.button.reset"><![CDATA[Reset]]></item>
+ <item name="wcf.global.button.restore"><![CDATA[Restore]]></item>
<item name="wcf.global.button.rss"><![CDATA[RSS Feed]]></item>
<item name="wcf.global.button.save"><![CDATA[Save]]></item>
<item name="wcf.global.button.saveSorting"><![CDATA[Save Sorting]]></item>
<item name="wcf.global.button.search"><![CDATA[Search]]></item>
<item name="wcf.global.button.submit"><![CDATA[Submit]]></item>
+ <item name="wcf.global.button.trash"><![CDATA[Move to Trash]]></item>
<item name="wcf.global.button.upload"><![CDATA[Upload]]></item>
<item name="wcf.global.button.readMore"><![CDATA[Read More]]></item>
<item name="wcf.global.comments"><![CDATA[Comments]]></item>
comments SMALLINT(5) NOT NULL DEFAULT 0,
views MEDIUMINT(7) NOT NULL DEFAULT 0,
cumulativeLikes MEDIUMINT(7) NOT NULL DEFAULT 0,
+ isDeleted TINYINT(1) NOT NULL DEFAULT 0,
KEY (time)
);