diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index fc6d8911b1..657cfb3231 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -620,7 +620,7 @@ define('admin/manage/users', [ params.query = query.query; params.page = query.page; params.sortBy = params.sortBy || 'lastonline'; - const qs = decodeURIComponent($.param(params)); + const qs = $.param(params); $.get(config.relative_path + '/api/admin/manage/users?' + qs, function (data) { renderSearchResults(data); const url = config.relative_path + '/admin/manage/users?' + qs; @@ -673,7 +673,7 @@ define('admin/manage/users', [ delete params.searchBy; } - return decodeURIComponent($.param(params)); + return $.param(params); } function handleSort() { diff --git a/public/src/client/login.js b/public/src/client/login.js index ff02e7cb64..f7508b8ec2 100644 --- a/public/src/client/login.js +++ b/public/src/client/login.js @@ -59,7 +59,7 @@ define('forum/login', ['hooks', 'translator', 'jquery-form'], function (hooks, t const params = utils.params({ url: data.next }); params.loggedin = true; delete params.register; // clear register message incase it exists - const qs = decodeURIComponent($.param(params)); + const qs = $.param(params); window.location.href = pathname + '?' + qs; }, diff --git a/public/src/client/register.js b/public/src/client/register.js index d8144d26d5..f989901e7b 100644 --- a/public/src/client/register.js +++ b/public/src/client/register.js @@ -85,7 +85,7 @@ define('forum/register', [ const params = utils.params({ url: data.next }); params.registered = true; - const qs = decodeURIComponent($.param(params)); + const qs = $.param(params); window.location.href = pathname + '?' + qs; } else if (data.message) { diff --git a/public/src/client/search.js b/public/src/client/search.js index 5bf52a1ef9..d2c1bb1c03 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -72,12 +72,12 @@ define('forum/search', [ let labelText = '[[search:tags]]'; if (selectedTags.length) { labelText = translator.compile( - 'search:tags-x', selectedTags.map(u => u.valueEscaped).join(', ') + 'search:tags-x', selectedTags.map(u => u.value).join(', ') ); } $('[component="tag/filter/button"]').toggleClass( 'active-filter', isActive - ).find('.filter-label').translateText(labelText); + ).find('.filter-label').translateHtml(labelText); } function updateTimeFilter() { @@ -326,7 +326,7 @@ define('forum/search', [ return { value: value, valueEscaped: escapedTag, - valueEncoded: encodeURIComponent(escapedTag), + valueEncoded: encodeURIComponent(value), class: escapedTag.replace(/\s/g, '-'), }; } @@ -353,7 +353,7 @@ define('forum/search', [ result.tags = result.tags.slice(0, 20); const tagMap = {}; result.tags.forEach((tag) => { - tagMap[tag.valueEscaped] = tag; + tagMap[tag.value] = tag; }); const html = await app.parseAndTranslate('partials/search-filters', 'tagFilterResults', { @@ -374,7 +374,7 @@ define('forum/search', [ el.on('click', '[component="tag/filter/delete"]', function () { const deleteTag = $(this).attr('data-tag'); - selectedTags = selectedTags.filter(tag => tag.valueEscaped !== deleteTag); + selectedTags = selectedTags.filter(tag => tag.value !== deleteTag); renderSelectedTags(); }); diff --git a/public/src/modules/categoryFilter.js b/public/src/modules/categoryFilter.js index 7c76182dd3..4f330be091 100644 --- a/public/src/modules/categoryFilter.js +++ b/public/src/modules/categoryFilter.js @@ -55,7 +55,7 @@ define('categoryFilter', ['categorySearch', 'api', 'hooks'], function (categoryS delete currentParams.page; if (Object.keys(currentParams).length) { - url += '?' + decodeURIComponent($.param(currentParams)); + url += '?' + $.param(currentParams); } ajaxify.go(url); } diff --git a/public/src/modules/search.js b/public/src/modules/search.js index aedaa8380f..abf3343ac0 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -286,7 +286,7 @@ define('search', [ data: data, }); - return decodeURIComponent($.param(query)); + return $.param(query); } Search.getSearchPreferences = function () { diff --git a/public/src/modules/sort.js b/public/src/modules/sort.js index 2fcbb67897..0482cff837 100644 --- a/public/src/modules/sort.js +++ b/public/src/modules/sort.js @@ -17,7 +17,7 @@ define('sort', ['components'], function (components) { const newSetting = $(this).attr('data-sort'); const urlParams = utils.params(); urlParams.sort = newSetting; - const qs = decodeURIComponent($.param(urlParams)); + const qs = $.param(urlParams); ajaxify.go(gotoOnSave + (qs ? '?' + qs : '')); }); }; diff --git a/public/src/modules/tagFilter.js b/public/src/modules/tagFilter.js index ea8f8f887f..25df586d23 100644 --- a/public/src/modules/tagFilter.js +++ b/public/src/modules/tagFilter.js @@ -98,7 +98,7 @@ define('tagFilter', ['hooks', 'alerts', 'bootstrap'], function (hooks, alerts, b } delete currentParams.page; if (Object.keys(currentParams).length) { - url += '?' + decodeURIComponent($.param(currentParams)); + url += '?' + $.param(currentParams); } ajaxify.go(url); } @@ -159,7 +159,7 @@ define('tagFilter', ['hooks', 'alerts', 'bootstrap'], function (hooks, alerts, b function renderList(tags) { const selectedTags = options.selectedTags; tags.forEach(function (tag) { - tag.selected = selectedTags.includes(tag.valueEscaped); + tag.selected = selectedTags.includes(tag.value); }); app.parseAndTranslate(options.template, { diff --git a/public/src/overrides.js b/public/src/overrides.js index f933f5f7ec..73c497d108 100644 --- a/public/src/overrides.js +++ b/public/src/overrides.js @@ -6,7 +6,7 @@ window.overrides = window.overrides || {}; function translate(elements, type, str) { return elements.each(function () { - var el = $(this); + const el = $(this); translator.translate(str, function (translated) { el[type](translated); }); diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js index 494bae0e34..a6ade8c73b 100644 --- a/src/controllers/helpers.js +++ b/src/controllers/helpers.js @@ -362,11 +362,11 @@ helpers.getSelectedTag = function (tags) { tags = [tags]; } tags = tags || []; - const tagData = tags.map(t => validator.escape(String(t))); + const tagData = tags.map(t => String(t)); let selectedTag = null; if (tagData.length) { selectedTag = { - label: tagData.join(', '), + label: validator.escape(tagData.join(', ')), }; } return { diff --git a/src/controllers/tags.js b/src/controllers/tags.js index c411aad90a..e5806c87d2 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -41,8 +41,8 @@ tagsController.getTag = async function (req, res) { const stop = start + settings.topicsPerPage - 1; const [topicCount, tids] = await Promise.all([ - topics.getTagTopicCount(tag, cids), - topics.getTagTidsByCids(tag, cids, start, stop), + topics.getTagTopicCount(req.params.tag, cids), + topics.getTagTidsByCids(req.params.tag, cids, start, stop), ]); templateData.topics = await topics.getTopics(tids, req.uid); diff --git a/src/topics/data.js b/src/topics/data.js index e9a281ecaa..d411cbf329 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -134,7 +134,7 @@ function modifyTopic(topic, fields) { return { value: tag, valueEscaped: escaped, - valueEncoded: encodeURIComponent(escaped), + valueEncoded: encodeURIComponent(tag), class: escaped.replace(/\s/g, '-'), }; }); diff --git a/src/topics/tags.js b/src/topics/tags.js index 80fea5e6c6..0df806c34d 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -323,7 +323,7 @@ module.exports = function (Topics) { } tags.forEach((tag) => { tag.valueEscaped = validator.escape(String(tag.value)); - tag.valueEncoded = encodeURIComponent(tag.valueEscaped); + tag.valueEncoded = encodeURIComponent(tag.value); tag.class = tag.valueEscaped.replace(/\s/g, '-'); }); return tags; diff --git a/test/topics.js b/test/topics.js index 366a7f85bc..dcea6ccd09 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1432,6 +1432,7 @@ describe('Topic\'s', () => { before(async () => { await topics.post({ uid: adminUid, tags: ['php', 'nosql', 'psql', 'nodebb', 'node icon'], title: 'topic title 1', content: 'topic 1 content', cid: topic.categoryId }); await topics.post({ uid: adminUid, tags: ['javascript', 'mysql', 'python', 'nodejs'], title: 'topic title 2', content: 'topic 2 content', cid: topic.categoryId }); + await topics.post({ uid: adminUid, tags: ['signal & slot', 'node & c++'], title: 'topic title 3', content: 'topic 3 content', cid: topic.categoryId }); }); it('should return empty array if query is falsy', (done) => { @@ -1483,10 +1484,11 @@ describe('Topic\'s', () => { it('should search and load tags', (done) => { socketTopics.searchAndLoadTags({ uid: adminUid }, { query: 'no' }, (err, data) => { assert.ifError(err); - assert.equal(data.matchCount, 4); + assert.equal(data.matchCount, 5); assert.equal(data.pageCount, 1); const tagData = [ { value: 'nodebb', valueEscaped: 'nodebb', valueEncoded: 'nodebb', score: 3, class: 'nodebb' }, + { value: 'node & c++', valueEscaped: 'node & c++', valueEncoded: 'node%20%26%20c%2B%2B', score: 1, class: 'node-&-c++' }, { value: 'node icon', valueEscaped: 'node icon', valueEncoded: 'node%20icon', score: 1, class: 'node-icon' }, { value: 'nodejs', valueEscaped: 'nodejs', valueEncoded: 'nodejs', score: 1, class: 'nodejs' }, { value: 'nosql', valueEscaped: 'nosql', valueEncoded: 'nosql', score: 1, class: 'nosql' },