From e5c625ac77320907d6b3a92823b9e27e7455b905 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Fri, 8 Jul 2016 13:29:54 +0200 Subject: [PATCH] Added support for page bbcode / page lookup --- com.woltlab.wcf/bbcode.xml | 12 +- com.woltlab.wcf/templates/wysiwyg.tpl | 8 +- .../files/acp/templates/__pageAddContent.tpl | 18 +++ .../install/files/acp/templates/wysiwyg.tpl | 8 +- .../3rdParty/redactor2/plugins/WoltLabPage.js | 13 ++ .../files/js/WoltLab/WCF/Ui/Page/Search.js | 130 ++++++++++++++++++ .../files/js/WoltLab/WCF/Ui/Redactor/Page.js | 34 +++++ .../files/lib/data/page/PageAction.class.php | 39 +++++- .../data/page/content/PageContent.class.php | 1 + .../bbcode/WoltLabSuitePageBBCode.class.php | 34 +++++ ...PageMessageEmbeddedObjectHandler.class.php | 15 ++ 11 files changed, 301 insertions(+), 11 deletions(-) create mode 100644 wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabPage.js create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search.js create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Page.js create mode 100644 wcfsetup/install/files/lib/system/bbcode/WoltLabSuitePageBBCode.class.php diff --git a/com.woltlab.wcf/bbcode.xml b/com.woltlab.wcf/bbcode.xml index a24543e7c9..90931ec09f 100644 --- a/com.woltlab.wcf/bbcode.xml +++ b/com.woltlab.wcf/bbcode.xml @@ -188,7 +188,6 @@ ^\d+$ 1 - 1 ^(small|medium|large|original)$ @@ -205,7 +204,16 @@ ^\d+(,\d+)*$ 1 - 1 + + + + + + + + + ^\d+$ + 1 diff --git a/com.woltlab.wcf/templates/wysiwyg.tpl b/com.woltlab.wcf/templates/wysiwyg.tpl index 3183d0b94c..fd668b69d8 100644 --- a/com.woltlab.wcf/templates/wysiwyg.tpl +++ b/com.woltlab.wcf/templates/wysiwyg.tpl @@ -172,15 +172,15 @@ config.plugins.push('WoltLabMedia'); {/if} - // load the button plugin last to ensure all buttons have been initialized - // already and we can safely add all icons - config.plugins.push('WoltLabButton'); - {if $__redactorConfig|isset}{@$__redactorConfig}{/if} {assign var=$__redactorConfig value=''} {event name='redactorConfig'} + // load the button plugin last to ensure all buttons have been initialized + // already and we can safely add all icons + config.plugins.push('WoltLabButton'); + $(element).redactor(config); }); }); diff --git a/wcfsetup/install/files/acp/templates/__pageAddContent.tpl b/wcfsetup/install/files/acp/templates/__pageAddContent.tpl index 8cd7aa98f1..3f4ae3d8c8 100644 --- a/wcfsetup/install/files/acp/templates/__pageAddContent.tpl +++ b/wcfsetup/install/files/acp/templates/__pageAddContent.tpl @@ -1,5 +1,23 @@ {if $pageType == 'text'} + {capture append='__redactorJavaScript'}, '{@$__wcf->getPath()}js/3rdParty/redactor2/plugins/WoltLabPage.js?v={@LAST_UPDATE_TIME}'{/capture} + {capture append='__redactorConfig'} + Language.addObject({ + 'wcf.page.search': '{lang}wcf.page.search{/lang}', + 'wcf.page.search.error.tooShort': '{lang}wcf.page.search.error.tooShort{/lang}', + 'wcf.page.search.error.noResults': '{lang}wcf.page.search.error.noResults{/lang}', + 'wcf.page.search.name': '{lang}wcf.page.search.name{/lang}', + 'wcf.page.search.results': '{lang}wcf.page.search.results{/lang}', + 'wcf.page.search.results.description': '{lang}wcf.page.search.results.description{/lang}' + }); + + buttonOptions.woltlabPage = { icon: 'fa-file-text-o', title: '{lang}wcf.editor.button.page{/lang}' }; + + buttons.push('woltlabPage'); + + config.plugins.push('WoltLabPage'); + {/capture} + {include file='wysiwyg' wysiwygSelector='content'|concat:$languageID} {elseif $pageType == 'html'} {include file='codemirror' codemirrorMode='htmlmixed' codemirrorSelector='#content'|concat:$languageID} diff --git a/wcfsetup/install/files/acp/templates/wysiwyg.tpl b/wcfsetup/install/files/acp/templates/wysiwyg.tpl index 3183d0b94c..fd668b69d8 100644 --- a/wcfsetup/install/files/acp/templates/wysiwyg.tpl +++ b/wcfsetup/install/files/acp/templates/wysiwyg.tpl @@ -172,15 +172,15 @@ config.plugins.push('WoltLabMedia'); {/if} - // load the button plugin last to ensure all buttons have been initialized - // already and we can safely add all icons - config.plugins.push('WoltLabButton'); - {if $__redactorConfig|isset}{@$__redactorConfig}{/if} {assign var=$__redactorConfig value=''} {event name='redactorConfig'} + // load the button plugin last to ensure all buttons have been initialized + // already and we can safely add all icons + config.plugins.push('WoltLabButton'); + $(element).redactor(config); }); }); diff --git a/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabPage.js b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabPage.js new file mode 100644 index 0000000000..52bbf917f8 --- /dev/null +++ b/wcfsetup/install/files/js/3rdParty/redactor2/plugins/WoltLabPage.js @@ -0,0 +1,13 @@ +$.Redactor.prototype.WoltLabPage = function() { + "use strict"; + + return { + init: function() { + var button = this.button.add('woltlabPage', ''); + + require(['WoltLab/WCF/Ui/Redactor/Page'], (function (UiRedactorPage) { + new UiRedactorPage(this, button[0]); + }).bind(this)); + } + }; +}; diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search.js new file mode 100644 index 0000000000..917a2273cd --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search.js @@ -0,0 +1,130 @@ +define(['Ajax', 'EventKey', 'Language', 'StringUtil', 'Dom/Util', 'Ui/Dialog'], function(Ajax, EventKey, Language, StringUtil, DomUtil, UiDialog) { + "use strict"; + + var _callbackSelect, _resultContainer, _resultList, _searchInput = null; + + return { + open: function(callbackSelect) { + _callbackSelect = callbackSelect; + + UiDialog.open(this); + }, + + _search: function (event) { + event.preventDefault(); + + var inputContainer = _searchInput.parentNode; + var innerError = inputContainer.nextSibling; + if (innerError && innerError.nodeName === 'SMALL') elRemove(innerError); + + var value = _searchInput.value.trim(); + if (value.length < 3) { + innerError = elCreate('small'); + innerError.className = 'innerError'; + innerError.textContent = Language.get('wcf.page.search.error.tooShort'); + DomUtil.insertAfter(innerError, inputContainer); + return; + } + + Ajax.api(this, { + parameters: { + searchString: value + } + }); + }, + + _click: function (event) { + event.preventDefault(); + + _callbackSelect(elData(event.currentTarget, 'page-id')); + + UiDialog.close(this); + }, + + _ajaxSuccess: function(data) { + var html = '', page; + //noinspection JSUnresolvedVariable + for (var i = 0, length = data.returnValues.length; i < length; i++) { + //noinspection JSUnresolvedVariable + page = data.returnValues[i]; + + html += '
  • ' + + '
    ' + + '

    ' + StringUtil.escapeHTML(page.name) + '

    ' + + '' + StringUtil.escapeHTML(page.displayLink) + '' + + '
    ' + + '
  • '; + } + + _resultList.innerHTML = html; + + window[html ? 'elShow' : 'elHide'](_resultContainer); + + if (html) { + elBySelAll('.containerHeadline', _resultList, (function(item) { + item.addEventListener(WCF_CLICK_EVENT, this._click.bind(this)); + }).bind(this)); + } + else { + var innerError = elCreate('small'); + innerError.className = 'innerError'; + innerError.textContent = Language.get('wcf.page.search.error.noResults'); + DomUtil.insertAfter(innerError, _searchInput.parentNode); + } + }, + + _ajaxSetup: function () { + return { + data: { + actionName: 'search', + className: 'wcf\\data\\page\\PageAction' + } + }; + }, + + _dialogSetup: function() { + return { + id: 'wcfUiPageSearch', + options: { + onSetup: (function() { + var callbackSearch = this._search.bind(this); + + _searchInput = elById('wcfUiPageSearchInput'); + _searchInput.addEventListener('keydown', function(event) { + if (EventKey.Enter(event)) { + callbackSearch(event); + } + }); + + _searchInput.nextElementSibling.addEventListener(WCF_CLICK_EVENT, callbackSearch); + + _resultContainer = elById('wcfUiPageSearchResultContainer'); + _resultList = elById('wcfUiPageSearchResultList'); + }).bind(this), + onShow: function() { + _searchInput.focus(); + }, + title: Language.get('wcf.page.search') + }, + source: '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + + '' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '' + }; + } + }; +}); diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Page.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Page.js new file mode 100644 index 0000000000..b5b6c86405 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Redactor/Page.js @@ -0,0 +1,34 @@ +/** + * Converts `` into the bbcode representation. + * + * @author Alexander Ebert + * @copyright 2001-2016 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLab/WCF/Ui/Redactor/Metacode + */ +define(['WoltLab/WCF/Ui/Page/Search'], function(UiPageSearch) { + "use strict"; + + function UiRedactorPage(editor, button) { this.init(editor, button); } + UiRedactorPage.prototype = { + init: function (editor, button) { + this._editor = editor; + + button.addEventListener(WCF_CLICK_EVENT, this._click.bind(this)) + }, + + _click: function (event) { + event.preventDefault(); + + UiPageSearch.open(this._insert.bind(this)); + }, + + _insert: function (pageID) { + this._editor.buffer.set(); + + this._editor.insert.text("[wsp='" + pageID + "'][/wsp]"); + } + }; + + return UiRedactorPage; +}); diff --git a/wcfsetup/install/files/lib/data/page/PageAction.class.php b/wcfsetup/install/files/lib/data/page/PageAction.class.php index 689ed24754..9e65fa65cf 100644 --- a/wcfsetup/install/files/lib/data/page/PageAction.class.php +++ b/wcfsetup/install/files/lib/data/page/PageAction.class.php @@ -53,7 +53,7 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction, /** * @inheritDoc */ - protected $requireACP = ['create', 'delete', 'toggle', 'update']; + protected $requireACP = ['create', 'delete', 'getSearchResultList', 'search', 'toggle', 'update']; /** * @inheritDoc @@ -269,6 +269,43 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction, return $this->pageEditor->getHandler()->lookup($this->parameters['data']['searchString']); } + /** + * Validates parameters to search for a page by its internal name. + */ + public function validateSearch() { + $this->readString('searchString'); + } + + /** + * Searches for a page by its internal name. + * + * @return array list of matching pages + */ + public function search() { + $sql = "SELECT pageID + FROM wcf".WCF_N."_page + WHERE name LIKE ? + AND requireObjectID = ? + ORDER BY name"; + $statement = WCF::getDB()->prepareStatement($sql, 5); + $statement->execute([ + '%' . $this->parameters['searchString'] . '%', + 0 + ]); + + $pages = []; + while ($pageID = $statement->fetchColumn()) { + $page = PageCache::getInstance()->getPage($pageID); + $pages[] = [ + 'displayLink' => $page->getDisplayLink(), + 'name' => $page->name, + 'pageID' => $pageID + ]; + } + + return $pages; + } + /** * @inheritDoc */ diff --git a/wcfsetup/install/files/lib/data/page/content/PageContent.class.php b/wcfsetup/install/files/lib/data/page/content/PageContent.class.php index ef773d54da..e106ef238a 100644 --- a/wcfsetup/install/files/lib/data/page/content/PageContent.class.php +++ b/wcfsetup/install/files/lib/data/page/content/PageContent.class.php @@ -45,6 +45,7 @@ class PageContent extends DatabaseObject { public function getFormattedContent() { // assign embedded objects MessageEmbeddedObjectManager::getInstance()->setActiveMessage('com.woltlab.wcf.page.content', $this->pageContentID); + MessageEmbeddedObjectManager::getInstance()->loadObjects('com.woltlab.wcf.page.content', [$this->pageContentID]); $processor = new HtmlOutputProcessor(); $processor->process($this->content, 'com.woltlab.wcf.page.content', $this->pageContentID); diff --git a/wcfsetup/install/files/lib/system/bbcode/WoltLabSuitePageBBCode.class.php b/wcfsetup/install/files/lib/system/bbcode/WoltLabSuitePageBBCode.class.php new file mode 100644 index 0000000000..71a5513c2d --- /dev/null +++ b/wcfsetup/install/files/lib/system/bbcode/WoltLabSuitePageBBCode.class.php @@ -0,0 +1,34 @@ + + * @package WoltLabSuite\Core\System\Bbcode + * @since 3.0 + */ +class WoltLabSuitePageBBCode extends AbstractBBCode { + /** + * @inheritDoc + */ + public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) { + $pageID = (!empty($openingTag['attributes'][0])) ? intval($openingTag['attributes'][0]) : 0; + if (!$pageID) { + return ''; + } + + /** @var Page $page */ + $page = MessageEmbeddedObjectManager::getInstance()->getObject('com.woltlab.wcf.page', $pageID); + if ($page !== null) { + return StringUtil::getAnchorTag($page->getLink(), $page->getTitle()); + } + + return ''; + } +} diff --git a/wcfsetup/install/files/lib/system/message/embedded/object/PageMessageEmbeddedObjectHandler.class.php b/wcfsetup/install/files/lib/system/message/embedded/object/PageMessageEmbeddedObjectHandler.class.php index 83b33384a8..38c99b9bd0 100644 --- a/wcfsetup/install/files/lib/system/message/embedded/object/PageMessageEmbeddedObjectHandler.class.php +++ b/wcfsetup/install/files/lib/system/message/embedded/object/PageMessageEmbeddedObjectHandler.class.php @@ -2,6 +2,7 @@ namespace wcf\system\message\embedded\object; use wcf\data\page\Page; use wcf\data\page\PageCache; +use wcf\system\html\input\HtmlInputProcessor; /** * Parses embedded pages and outputs their link or title. @@ -12,6 +13,20 @@ use wcf\data\page\PageCache; * @package WoltLabSuite\Core\System\Message\Embedded\Object */ class PageMessageEmbeddedObjectHandler extends AbstractSimpleMessageEmbeddedObjectHandler { + /** + * @inheritDoc + */ + public function parse(HtmlInputProcessor $htmlInputProcessor, array $embeddedData) { + $pageIDs = []; + if (!empty($embeddedData['wsp'])) { + for ($i = 0, $length = count($embeddedData['wsp']); $i < $length; $i++) { + $pageIDs[] = intval($embeddedData['wsp'][$i][0]); + } + } + + return array_unique($pageIDs); + } + /** * @inheritDoc */ -- 2.20.1