diff --git a/config/Migrations/20190729142650_Saitox5x3x0.php b/config/Migrations/20190729142650_Saitox5x3x0.php index aaeb5fa08..a5a71e50f 100755 --- a/config/Migrations/20190729142650_Saitox5x3x0.php +++ b/config/Migrations/20190729142650_Saitox5x3x0.php @@ -13,13 +13,6 @@ public function up() 'length' => 191, 'null' => true, ]) - ->addIndex( - [ - 'user_id', - 'title', - ], - ['name' => 'userId_title', 'unique' => true] - ) ->update(); $this->table('useronline') @@ -76,10 +69,6 @@ public function up() public function down() { - $this->table('uploads') - ->removeIndexByName('userId_title') - ->update(); - $this->table('uploads') ->changeColumn('title', 'string', [ 'default' => null, diff --git a/config/Migrations/20190801000000_Saitox5x3x1.php b/config/Migrations/20190801000000_Saitox5x3x1.php new file mode 100755 index 000000000..03b323cef --- /dev/null +++ b/config/Migrations/20190801000000_Saitox5x3x1.php @@ -0,0 +1,55 @@ +table('uploads')->hasIndex(['user_id', 'title'])) { + // Remove existing index on title from 5.3.0 + $this->table('uploads') + ->removeIndexByName('userId_title') + ->update(); + } + + // Populate all NULL title fields from name + $this->execute('UPDATE `uploads` SET `uploads`.`title`=COALESCE(`uploads`.`title`, `uploads`.`name`)'); + + // Change title to NOT NULL + $this->table('uploads') + ->changeColumn('title', 'string', [ + 'default' => null, + 'length' => 191, + 'null' => false, + ]) + ->update(); + + // Unique index on title + $this->table('uploads') + ->addIndex( + [ + 'user_id', + 'title', + ], + ['name' => 'userId_title', 'unique' => true] + ) + ->update(); + } + + public function down() + { + $this->table('uploads') + ->removeIndexByName('userId_title') + ->update(); + + $this->table('uploads') + ->changeColumn('title', 'string', [ + 'default' => null, + 'length' => 191, + 'null' => true, + ]) + ->update(); + } +} diff --git a/docs/help/de/9-drafts.md b/docs/help/de/9-drafts.md index 741c5819d..e60f47d36 100644 --- a/docs/help/de/9-drafts.md +++ b/docs/help/de/9-drafts.md @@ -1,14 +1,14 @@ ## Entwürfe -Entwürfe sollen Datenverlust beim Verfassen eines neuen Beitrages vermeiden helfen. +Entwürfe helfen einen Datenverlust beim Verfassen eines neuen Beitrages zu vermeiden. -Der Inhalt des Beitrages wird regelmässig im Hintergrund gespeichert. Sollte es zu Problemen beim Nutzer kommen (bspw. unerwartetes Schließen des Browsers), kann der bis dahin gesicherte Inhalt später wieder hergestellt werden. Dazu muss nur ein Beitrag an der gleichen Stelle wie zuvor begonnen werden, der Inhalt eines Entwurfs wird automatisch eingefügt. +Der Inhalt des Beitrages wird regelmässig im Hintergrund gespeichert. Sollte es zu einem Problem beim Nutzer kommen (bspw. unerwartetes Beenden des Browsers), kann der bis dahin gesicherte Inhalt später wieder hergestellt werden. Dazu muss ein zuvor begonnener Beitrag an der gleichen Stelle erneut geöffnet werden, der Entwurf wird dann automatisch eingefügt. Ein Ikon zeigt den aktuellen Status an: -   Änderungen ungesichert -   Änderungen gesichert -Nach erfolgreichen Eintragen eines Beitrags wird der entsprechende Entwurf gelöscht. Entwürfe werden nach 30 Tagen als aufgegeben angesehen automatisch entfernt. +Nach erfolgreichen Eintragen eines Beitrags wird der entsprechende Entwurf gelöscht. Alle Entwürfe werden spätestens nach 30 Tagen als aufgegeben angenommen und automatisch entfernt. Beim Bearbeiten eines bestehenden Beitrages wird kein Entwurf gespeichert. diff --git a/frontend/src/app/app.js b/frontend/src/app/app.js index 462bf1f8b..17b9e9358 100644 --- a/frontend/src/app/app.js +++ b/frontend/src/app/app.js @@ -105,34 +105,36 @@ var app = { let appView, appReady; - // do this always first App.settings.set(options.SaitoApp.app.settings); - // init i18n, do this always second - const language = App.settings.get('language'); - $.i18n.setUrl(App.settings.get('webroot') + 'js/locale/' + language + '.json'); - App.currentUser.set(options.SaitoApp.currentUser); - App.request = options.SaitoApp.request; - - app.configureAjax($, App); - - Html5NotificationModule.start(); - - var callbacks = options.SaitoApp.callbacks.beforeAppInit; - _.each(callbacks, function (fct) { - fct(); + $.ajax({ + cache: true, + dataType: 'json', + mimeType: 'application/json', + success: (data) => { + $.i18n.setDictionary(data); + App.currentUser.set(options.SaitoApp.currentUser); + App.request = options.SaitoApp.request; + + app.configureAjax($, App); + + Html5NotificationModule.start(); + + var callbacks = options.SaitoApp.callbacks.beforeAppInit; + _.each(callbacks, (fct) => { fct(); }); + + appReady = function () { + app.fireOnPageCallbacks(options.SaitoApp.callbacks); + appView = new AppView({ el: 'body' }); + appView.initFromDom({ + SaitoApp: options.SaitoApp, + contentTimer: contentTimer + }); + }; + whenReady(appReady); + }, + url: options.SaitoApp.assets.lang, }); - - appReady = function () { - app.fireOnPageCallbacks(options.SaitoApp.callbacks); - appView = new AppView({ el: 'body' }); - appView.initFromDom({ - SaitoApp: options.SaitoApp, - contentTimer: contentTimer - }); - }; - - whenReady(appReady); } }; diff --git a/frontend/src/lib/jquery.i18n/jquery.i18n.extend.js b/frontend/src/lib/jquery.i18n/jquery.i18n.extend.js index ddb757e60..c4198462f 100644 --- a/frontend/src/lib/jquery.i18n/jquery.i18n.extend.js +++ b/frontend/src/lib/jquery.i18n/jquery.i18n.extend.js @@ -7,32 +7,6 @@ import i18n from 'lib/jquery.i18n/jquery.i18n'; import format from 'string-template'; $.extend($.i18n, { - - currentString: '', - - setDict: function (dict) { - this.dict = dict; - }, - - setUrl: function (dictUrl) { - this.dictUrl = dictUrl; - this._loadDict(); - }, - - _loadDict: function () { - var success = function(data) { - this.dict = data; - }; - success = $.proxy(success, this); - return $.ajax({ - url: this.dictUrl, - dataType: 'json', - mimeType: 'application/json', - async: false, - cache: true - }).done(success); - }, - /** * Localice string with tokens * diff --git a/frontend/src/modules/answering/editor/EditorView.ts b/frontend/src/modules/answering/editor/EditorView.ts index eab994552..67ee26c8a 100644 --- a/frontend/src/modules/answering/editor/EditorView.ts +++ b/frontend/src/modules/answering/editor/EditorView.ts @@ -96,7 +96,7 @@ class EditorView extends View { */ private postContentChanged() { this.handleInput(); - autosize.update(this.getUI('text')); + _.defer(() => autosize.update(this.getUI('text'))); } /** diff --git a/frontend/src/modules/answering/views/CategorySelectVw.ts b/frontend/src/modules/answering/views/CategorySelectVw.ts index 8833944d2..71c763370 100644 --- a/frontend/src/modules/answering/views/CategorySelectVw.ts +++ b/frontend/src/modules/answering/views/CategorySelectVw.ts @@ -27,12 +27,12 @@ export default class CategorySelectVw extends View { <% if (!autoselectCategory) { %> <% } %> - <% for (const [id, title] of Object.entries(categories)) { %> + <% for (category of categories) { %> <% } %> diff --git a/frontend/test/lib/TemplateHelpersSpec.js b/frontend/test/lib/TemplateHelpersSpec.js index 5524fc67a..947bfb38f 100644 --- a/frontend/test/lib/TemplateHelpersSpec.js +++ b/frontend/test/lib/TemplateHelpersSpec.js @@ -3,7 +3,7 @@ import moment from 'moment'; import TemplateHelpers from 'lib/saito/templateHelpers'; // import 'lib/jquery.i18n/jquery.i18n.extend.js'; -$.i18n.setDict({}); +$.i18n.setDictionary({}); describe('Template Helpers', function () { diff --git a/frontend/test/lib/jquery.i18n.extendSpec.js b/frontend/test/lib/jquery.i18n.extendSpec.js index 4e4fd249f..c3fcb87ad 100644 --- a/frontend/test/lib/jquery.i18n.extendSpec.js +++ b/frontend/test/lib/jquery.i18n.extendSpec.js @@ -4,7 +4,7 @@ import 'lib/jquery.i18n/jquery.i18n.extend'; describe('jquery.i18n.extend', function () { describe('replaces :token tags', function () { beforeEach(function (done) { - $.i18n.setDict({}); + $.i18n.setDictionary({}); done(); }); @@ -25,7 +25,7 @@ describe('jquery.i18n.extend', function () { var expected, result; - $.i18n.setDict({ + $.i18n.setDictionary({ 'token test': '{tokenA}; is {token_b} than fu {tokenNo} c' }); diff --git a/frontend/test/modules/answering/AnsweringVwSpec.js b/frontend/test/modules/answering/AnsweringVwSpec.js index 1fbae409d..a8a05c988 100644 --- a/frontend/test/modules/answering/AnsweringVwSpec.js +++ b/frontend/test/modules/answering/AnsweringVwSpec.js @@ -13,7 +13,7 @@ describe('answering form', () => { }, editor: { buttons: [], - categories: {1: 'Ontopic'}, + categories: [{ id: 1, title: 'Ontopic'}], smilies: [], }, meta: { diff --git a/frontend/test/modules/answering/views/CategorySelectVwSpec.js b/frontend/test/modules/answering/views/CategorySelectVwSpec.js index 284068295..d9ac4a4dc 100644 --- a/frontend/test/modules/answering/views/CategorySelectVwSpec.js +++ b/frontend/test/modules/answering/views/CategorySelectVwSpec.js @@ -4,7 +4,10 @@ import { SubjectInputView as View } from 'modules/answering/views/SubjectInputVw import _ from 'underscore'; describe('answering form', function () { - const categories = { 1: 'Ontopic', 2: 'Offtopic'}; + const categories = [ + { id: 1, title: 'Ontopic'}, + { id: 2, title: 'Offtopic'} + ]; let model; beforeEach(() => { diff --git a/frontend/test/runner.js b/frontend/test/runner.js index 9c660cee2..2fda4f52e 100644 --- a/frontend/test/runner.js +++ b/frontend/test/runner.js @@ -13,7 +13,7 @@ window.$ = $; $.ajaxSetup({cache: true}); // make empty dict available for test cases -$.i18n.setDict({}); +$.i18n.setDictionary({}); window.redirect = function (destination) { document.location.replace(destination); diff --git a/plugins/ImageUploader/src/View/Helper/ImageUploaderHelper.php b/plugins/ImageUploader/src/View/Helper/ImageUploaderHelper.php index a0a7a05de..acab8de6d 100644 --- a/plugins/ImageUploader/src/View/Helper/ImageUploaderHelper.php +++ b/plugins/ImageUploader/src/View/Helper/ImageUploaderHelper.php @@ -41,7 +41,7 @@ public function image(Upload $image): array 'created' => $image->get('created'), 'mime' => $image->get('type'), 'name' => $image->get('name'), - 'title' => $image->get('title') ?: $image->get('name'), + 'title' => $image->get('title'), 'size' => $image->get('size'), 'url' => $this->Url->assetUrl( 'useruploads/' . $image->get('name'), diff --git a/plugins/ImageUploader/tests/Fixture/UploadsFixture.php b/plugins/ImageUploader/tests/Fixture/UploadsFixture.php index 6acfb6ef3..27f14f12b 100644 --- a/plugins/ImageUploader/tests/Fixture/UploadsFixture.php +++ b/plugins/ImageUploader/tests/Fixture/UploadsFixture.php @@ -30,7 +30,7 @@ class UploadsFixture extends TestFixture public $fields = [ 'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null], 'name' => ['type' => 'string', 'length' => 200, 'null' => true, 'default' => null, 'collate' => 'utf8mb4_unicode_ci', 'comment' => '', 'precision' => null, 'fixed' => null], - 'title' => ['type' => 'string', 'length' => 191, 'null' => true, 'default' => null, 'collate' => 'utf8mb4_unicode_ci', 'comment' => '', 'precision' => null, 'fixed' => null], + 'title' => ['type' => 'string', 'length' => 191, 'null' => false, 'default' => null, 'collate' => 'utf8mb4_unicode_ci', 'comment' => '', 'precision' => null, 'fixed' => null], 'type' => ['type' => 'string', 'length' => 200, 'null' => true, 'default' => null, 'collate' => 'utf8mb4_unicode_ci', 'comment' => '', 'precision' => null, 'fixed' => null], 'size' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null, 'autoIncrement' => null], 'created' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null], @@ -58,7 +58,7 @@ public function init() [ 'id' => 1, 'name' => '1-my-upload.png', - 'title' => null, + 'title' => '1-my-upload.png', 'type' => 'image/png', 'size' => 1000000, 'user_id' => 1, @@ -68,7 +68,7 @@ public function init() [ 'id' => 2, 'name' => '3-another-upload.jpg', - 'title' => null, + 'title' => '3-another-upload.jpg', 'type' => 'image/jpeg', 'size' => 50000, 'user_id' => 3, diff --git a/src/Controller/EntriesController.php b/src/Controller/EntriesController.php index 82cda53f3..31e889094 100644 --- a/src/Controller/EntriesController.php +++ b/src/Controller/EntriesController.php @@ -534,7 +534,7 @@ protected function _setupCategoryChooser(CurrentUserInterface $User) $this->set('categoryChooserTitleId', $title); $this->set( 'categoryChooser', - $User->getCategories()->getAll('read', 'list') + $User->getCategories()->getAll('read', 'select') ); } diff --git a/src/Lib/Saito/User/Categories.php b/src/Lib/Saito/User/Categories.php index c34160e7b..58d787831 100644 --- a/src/Lib/Saito/User/Categories.php +++ b/src/Lib/Saito/User/Categories.php @@ -45,13 +45,13 @@ public function __construct(CurrentUserInterface $User) } /** - * get all available categories to the user - * + * Get all available categories to the user in order * * @param string $action action * @param string $format format - * - 'short': [id1 => 'id1', id2 => 'id2'] - * - 'select': [id1 => 'title 1'] for html select + * - 'list': [['id' => , 'title' => ], [...]] suited for JS use + * - 'select': [id1 => 'title 1'] for Cake Form Helper select + * - 'short': [id1 => 'id1', id2 => 'id2'] suited for use in queries * @return mixed */ public function getAll($action, $format = 'short') @@ -68,14 +68,30 @@ function () use ($action, $format) { foreach ($all as $category) { $categories[$category->get('id')] = $category->get('category'); } + $categories = $this->_filterAllowed($action, $categories); + switch ($format) { + case 'select': + break; case 'short': $cIds = array_keys($categories); $categories = array_combine($cIds, $cIds); break; + case 'list': + $cats = []; + foreach ($categories as $key => $category) { + $cats[] = ['id' => $key, 'title' => $category]; + } + $categories = $cats; + break; + default: + throw new \InvalidArgumentException( + sprintf('Invalid argument %s for $format.', $format), + 1567319405 + ); } - return $this->_filterAllowed($action, $categories); + return $categories; } ); Stopwatch::stop('User\Categories::getAll()'); diff --git a/src/Lib/version.php b/src/Lib/version.php index 76448850d..3ecf378ee 100644 --- a/src/Lib/version.php +++ b/src/Lib/version.php @@ -13,7 +13,7 @@ $config = [ 'Saito' => [ - 'v' => '5.3.0', + 'v' => '5.3.1', 'saitoHomepage' => 'https://saito.siezi.com/' ] ]; diff --git a/src/Locale/de/default.po b/src/Locale/de/default.po index 42bcab0b1..1565a2b9d 100644 --- a/src/Locale/de/default.po +++ b/src/Locale/de/default.po @@ -575,10 +575,6 @@ msgstr "" msgid "Category" msgstr "Kategorie" -#: src/View/Helper/PostingHelper.php:115 -msgid "error_category_empty" -msgstr "Bitte Kategorie wählen." - # msgstr "Aufrufe" #: src/View/Helper/PostingHelper.php:137 msgid "views_headline" diff --git a/src/Locale/en/default.po b/src/Locale/en/default.po index 4e7555abe..ac37d1fdf 100644 --- a/src/Locale/en/default.po +++ b/src/Locale/en/default.po @@ -561,10 +561,6 @@ msgstr "" msgid "Category" msgstr "" -#: src/View/Helper/PostingHelper.php:115 -msgid "error_category_empty" -msgstr "Category must not be empty." - #: src/View/Helper/PostingHelper.php:137 msgid "views_headline" msgstr "views" diff --git a/src/Model/Table/CategoriesTable.php b/src/Model/Table/CategoriesTable.php index 66ac3e478..63ef2dbf4 100755 --- a/src/Model/Table/CategoriesTable.php +++ b/src/Model/Table/CategoriesTable.php @@ -14,6 +14,7 @@ use App\Lib\Model\Table\AppSettingTable; use Cake\Datasource\Exception\RecordNotFoundException; +use Cake\Datasource\ResultSetInterface; use Cake\ORM\TableRegistry; use Cake\Validation\Validator; use Saito\RememberTrait; @@ -74,11 +75,11 @@ public function validationDefault(Validator $validator) } /** - * get all categories + * Get all categories in sort order * - * @return array + * @return ResultSetInterface */ - public function getAllCategories() + public function getAllCategories(): ResultSetInterface { $key = 'Saito.Cache.Categories'; diff --git a/src/View/Helper/JsDataHelper.php b/src/View/Helper/JsDataHelper.php index 1dc02859a..0c7433354 100644 --- a/src/View/Helper/JsDataHelper.php +++ b/src/View/Helper/JsDataHelper.php @@ -50,7 +50,6 @@ public function getAppJs(View $View, ForumsUserInterface $CurrentUser) $js = [ 'app' => [ - 'version' => Configure::read('Saito.v'), 'settings' => [ 'autoPageReload' => (isset($View->viewVars['autoPageReload']) ? $View->viewVars['autoPageReload'] : 0), 'editPeriod' => (int)Configure::read( @@ -69,6 +68,9 @@ public function getAppJs(View $View, ForumsUserInterface $CurrentUser) 'webroot' => $request->getAttribute('webroot') ] ], + 'assets' => [ + 'lang' => $this->Url->assetUrl('js/locale/' . Configure::read('Saito.language') . '.json'), + ], 'msg' => $this->notifications()->getAll(), 'request' => [ 'action' => $request->getParam('action'), diff --git a/src/View/Helper/PostingHelper.php b/src/View/Helper/PostingHelper.php index 8ff90e58e..3c67f8d36 100755 --- a/src/View/Helper/PostingHelper.php +++ b/src/View/Helper/PostingHelper.php @@ -12,10 +12,8 @@ namespace App\View\Helper; -use App\Model\Entity\Entry; use App\View\Helper\TimeHHelper; use Cake\Core\Configure; -use Cake\Event\Event; use Cake\View\Helper\FormHelper; use Cake\View\Helper\HtmlHelper; use Saito\Event\SaitoEventManager; @@ -84,47 +82,6 @@ public function getFastLink(BasicPostingInterface $posting, array $options = []) return $link; } - /** - * category select - * - * @param Entry $posting posting - * @param array $categories categories - * - * @return string - */ - public function categorySelect(Entry $posting, array $categories) - { - if (!$posting->isRoot()) { - // Send category for easy access in entries/preview when answering - // (not used when saved). - return $this->Form->hidden('category_id'); - } - - $html = $this->Form->control( - 'category_id', - [ - 'class' => 'form-control', - 'div' => false, - 'options' => $categories, - 'empty' => true, - 'label' => [ - 'class' => 'col-form-label mr-3', - 'text' => __('Category'), - ], - 'tabindex' => 1, - 'error' => ['notEmpty' => __('error_category_empty')], - 'templates' => [ - 'inputContainer' => '{{content}}', - ], - 'required' => 'required', - ] - ); - $html = $this->Html->div('form-group d-flex', $html); - // $html = $this->Html->div('form-inline', $html); - - return $html; - } - /** * Render view counter * diff --git a/tests/TestCase/Lib/Saito/User/CategoriesTest.php b/tests/TestCase/Lib/Saito/User/CategoriesTest.php index eddc8382d..7bfbb5bfe 100644 --- a/tests/TestCase/Lib/Saito/User/CategoriesTest.php +++ b/tests/TestCase/Lib/Saito/User/CategoriesTest.php @@ -41,12 +41,19 @@ public function testGetAllForAnon() */ $result = $Lib->getAll('thread'); $this->assertEquals([], $result); + } - /** - * test answer - */ - $result = $Lib->getAll('answer'); - $this->assertEquals([], $result); + public function testGetAllFormatList() + { + $User = CurrentUserFactory::createDummy(['id' => 1, 'user_type' => 'anon']); + $Lib = $User->getCategories(); + + $result = $Lib->getAll('read', 'list'); + $expected = [ + ['id' => 3, 'title' => 'Another Ontopic'], + ['id' => 2, 'title' => 'Ontopic'], + ]; + $this->assertEquals($result, $expected); } public function testGetAllForUser() @@ -109,7 +116,7 @@ public function testGetAllForMod() $this->assertEquals($expected, $result); /** - * test answer + * test answer as */ $result = $Lib->getAll('answer', 'select'); $this->assertEquals($expected, $result);