This code allows the users to see a list of similar topics while they are writing the title. It is ideal for support forums in which almost all answers are already solved. Here's an example of how it looks like:
Everyone can adapt the code according to his/her forum. It can also be possible to adapt the code to any other version, although this would be up to the user, and this adaptation is not supported. By default the code works for phpBB3
Installation ACP(Admin Control Panel) > Modules > HTML & JAVASCRIPT > Javascript codes management > Javascript codes management Position: All pages
- Code:
/* The following code is DOM-dependent. It may not work if you modify the posting structure in the forum templates */ var FLRX = FLRX || {}; FLRX.similarTopics = (function () { 'use strict'; let settings = { // default settings forums : [], searchIn : false, maxTopics : 5, wordMinLength: 4, autocomplete: false, // disable browser autocomplete from subject input /* Advanced settings */ excludedCharacters : new RegExp(/[.,\/#!$%\^&\*¿?!¡;:{}\\=\-_`~"«“‘’”»()\[\]]/, 'g'), // The ignored characters from the topic title. dom : { // default settings (should work if left like this with unmodified templates) /* Search page */ topicscontainer : '.forabg', topic : 'dd.dterm', infocontainer : '.span-tab', titlelink : '.topictitle', userlink : 'a[href^="/u"]', forumlink : 'a[href^="/f"]', topicicon : 'dl.icon', /* structure */ visible : 'visible', /* posting page */ titleinput : '#postingbox input[name="subject"]', inputcontainer : 'dl', /* created elements */ maincontainer : $('<div />', { id : 'similarTopics' }), similartopiccont : $('<div />', { class : 'topic-container' }), loadingelm : $('<div />', { class : 'spinner' }) .append($('<div/>', { class:'double-bounce1' })) .append($('<div/>', { class:'double-bounce2' })), topicelmcont : $('<div />', { class: 'topic' }), topicelmtitle : $('<div />', { class: 'topic-title' }), topicdatacont : $('<div />', { class: 'topic-data' }), topicflags : $('<div />', { class: 'topic-flags' }), topiciconcont : $('<div />', { class: 'topic-icon' }), topicstatus : $('<div />', { class: 'topic-status' }), topicelminfo : $('<div />', { class: 'topic-info' }), topicauthor : $('<span />', { class: 'topic-author', text: 'by ' }), topicforum : $('<span />', { class: 'topic-forum', text: ' in ' }), similarstitle : $('<h4 />', { class: 'similarTopics-title', text: 'You may be interested in checking this topics before opening a new one' }), }, }, structure = {}, request, debounce = function(cb, delay) { let timeout; return function(...a) { clearTimeout(timeout); timeout = setTimeout( _ => { timeout = null; cb.call(this, ...a); }, delay); }; }, /* transforms a UTF8-encoded URI into Windows-1252 */ sanitizeURI = function(uri) { /* For some reason Forumotion uses Windows-1252 encoding in search URIs. This workaround will only fix issues with Spanish characters */ return uri.replace(/%C3%91/g, '%D1') // Ñ .replace(/%C3%B1/g, '%F1') // ñ .replace(/%C3%81/g, '%C1') // Á .replace(/%C3%89/g, '%C9') // É .replace(/%C3%8D/g, '%CD') // Í .replace(/%C3%93/g, '%D3') // Ó .replace(/%C3%9A/g, '%DA') // Ú .replace(/%C3%9C/g, '%DC') // Ü .replace(/%C3%A1/g, '%E1') // á .replace(/%C3%A9/g, '%E9') // é .replace(/%C3%AD/g, '%ED') // í .replace(/%C3%B3/g, '%F3') // ó .replace(/%C3%BA/g, '%FA') // ú .replace(/%C3%BC/g, '%FC'); // ü }, /* returns an object array (representation of topics) from a search URL synchronously */ searchTopics = function(url, cb) { $.ajax({ url : url, }).done(function(data) { let relatedTopics = [], $forabg = $(settings.dom.topicscontainer, data); if($forabg.length) { $forabg.find(settings.dom.topic).slice(0, settings.maxTopics).each(function() { let $this = $(this), $topictitle = $this.find(settings.dom.titlelink), $spantab = $this.find(settings.dom.infocontainer), $forumlink = $spantab.find(settings.dom.forumlink), $userlink = $spantab.find(settings.dom.userlink), $topicicon = $this.closest(settings.dom.topicicon); relatedTopics.push({ title : $topictitle.text().trim(), url : $topictitle.attr('href'), icon : $this.css('background-image').slice(4, -1), status : $topicicon.css('background-image').length ? $topicicon.css('background-image').slice(4, -1) : false, forum : { name : $forumlink.text(), url : $forumlink.attr('href'), }, user : { name : $userlink.text(), url : $userlink.attr('href'), }, }); }); } cb.call(this, relatedTopics); }).fail(_ => {let up; throw up || false}); }, /* returns an array with the words of a string that fulfil conditions of settings.excludedCharacters */ getWords = function(str) { return str.trim().replace(settings.excludedCharacters, '').split(' ').filter(elm => elm.length >= settings.wordMinLength); }, /* updates the similar topics DOM structure with the ones in the input array */ updateDOM = function(arr) { structure.topiccontainer.empty(); if(arr.length) { let docfrag = document.createDocumentFragment(); $.each(arr, function(index, topic) { let $topicTitle = settings.dom.topicelmtitle.clone(), $topicContainer = settings.dom.topicelmcont.clone(), $topicInfo = settings.dom.topicelminfo.clone(), $topicauthor = settings.dom.topicauthor.clone(), $topicstatus = settings.dom.topicstatus.clone(), $topicforum = settings.dom.topicforum.clone(), $topicflags = settings.dom.topicflags.clone(), $topicdata = settings.dom.topicdatacont.clone(), $topicicon = settings.dom.topiciconcont.clone(), /* link creation */ $topicLink = $('<a />', { href: topic.url, text: topic.title }), $forumlink = $('<a />', { href: topic.forum.url, text: topic.forum.name }), $authorlink = $('<a />', { href: topic.user.url, text: topic.user.name }); $topicicon.css('background-image', `url('${ topic.icon }')`); topic.status && $topicstatus.css('background-image', `url('${ topic.status }')`); $topicauthor.append($authorlink); $topicforum.append($forumlink); $topicTitle.append($topicLink); $topicInfo.append($topicauthor, $topicforum); $topicdata.append($topicTitle, $topicInfo); $topicflags.append($topicstatus, $topicicon); $topicContainer.append($topicflags, $topicdata); docfrag.append($topicContainer[0]); }); structure.topiccontainer[0].appendChild(docfrag); } else structure.maincontainer.removeClass(settings.dom.visible); }, setLoadingStatus = function() { structure.loadingcontainer.addClass(settings.dom.visible); structure.recentstitle.removeClass(settings.dom.visible); structure.topiccontainer.removeClass(settings.dom.visible); }, topicsRetrieved = function (){ structure.loadingcontainer.removeClass(settings.dom.visible); structure.recentstitle.addClass(settings.dom.visible); structure.topiccontainer.addClass(settings.dom.visible); }, searchAlgorithm = function(words, cb) { let params = { search_where : settings.searchIn || `f${/\?f=(\d+)/.exec(location.search)[1]}`, show_results : 'topics', sort_by : 0, sort_dir : 'DESC', search_terms : 'all', search_keywords : words.join(' '), }; searchTopics(`/search?${sanitizeURI($.param(params))}`, function(arr) { let relatedTopics = arr; if(relatedTopics.length < settings.maxTopics) { params.search_terms = 'any'; searchTopics(`/search?${sanitizeURI($.param(params))}`, function(arr) { let searchAnyWord = arr, neededElms = settings.maxTopics - relatedTopics.length; searchAnyWord = searchAnyWord.filter(elm => relatedTopics.find(e => e.url == elm.url) === undefined); // Ignore duplicates relatedTopics = [...relatedTopics, ...searchAnyWord.slice(0, neededElms)]; cb.call(this, relatedTopics); }); } cb.call(this, relatedTopics); }); }, /* main function */ searchSimilarTopics = function($title) { let words = getWords($title.val()); if(words.length == 0) return; // for the first time, if it was hidden structure.maincontainer.addClass(settings.dom.visible); setLoadingStatus(); searchAlgorithm(words, function(arr) { updateDOM(arr); topicsRetrieved(); }); }, generateStructure = function($title) { let $similarTopics = settings.dom.maincontainer.clone(), $spinner = settings.dom.loadingelm.clone(), $topicsContainer = settings.dom.similartopiccont.clone(), $recentsTitle = settings.dom.similarstitle.clone(); structure = { maincontainer : $similarTopics, loadingcontainer : $spinner, topiccontainer : $topicsContainer, recentstitle : $recentsTitle, }; $similarTopics.append($spinner, $recentsTitle, $topicsContainer); $title.closest(settings.dom.inputcontainer).after($similarTopics); }, init = function(options) { $.extend(true, settings, options); let timeout, $title = $(settings.dom.titleinput); if(!settings.autocomplete) $title.attr('autocomplete', 'off'); // append the basic dom structure (should be hidden by default with CSS) generateStructure($title); $title.on('keypress', debounce(function(e) { if(e.which !== 0) searchSimilarTopics($title); }, 500)); }; /* API :-) */ return { init : init, }; })(); !function() { const settings = { forums : [1,2,3,4,5,6,7], // Forum IDs (separated by comma) where the "Similar Topics" feature will be enabled. Set to true to enable the feature everywhere (not recommended). searchIn : '-1', // Where the searches will take place. Use -1 to search everywhere. If not set, it will search the forum where the topic is being created maxTopics : 5, // Maximum amount of topics shown }; location.pathname == '/post' && location.search.indexOf('&mode=newtopic') > -1 && (settings.forums === true || settings.forums.some(id => location.search.indexOf(`?f=${id}`) > -1)) && $(function() { FLRX.similarTopics.init(settings); }); }();
Now add this CSS code to your forum - Code:
#similarTopics{width:500px;background:#E1EBF2;padding:5px 10px;border-radius:5px;margin:5px 0 0 10em} #similarTopics,#similarTopics .spinner,#similarTopics .topic-container,#similarTopics .similarTopics-title{display:none} #similarTopics.visible,#similarTopics .spinner.visible,#similarTopics .similarTopics-title.visible,#similarTopics .topic-container.visible{display:block} #similarTopics .topic{display:flex;border-bottom:1px solid #fff;padding:5px 0;margin:5px 0} #similarTopics .topic:last-child{border-bottom:none} #similarTopics .topic-data{flex:1} #similarTopics .topic-flags{align-items:center;margin-right:10px;position:relative} #similarTopics .topic-icon{position:absolute;top:0;left:0;bottom:0;right:0;background:transparent 50% 50% no-repeat} #similarTopics .similarTopics-title{border-bottom:1px solid #0076b1;color:#0076b1;font-size:.9em;margin:.5em 0;text-transform:uppercase} #similarTopics .topic-status{width:27px;height:27px;background:transparent 0 0 no-repeat} At the beginning of the code there are several lines between brackets in the const settings definition. This is where you will be able to add several configuration lines (the lines that are already in the code are ones by default I have added). Next I will add a bit of a documentation to know what settings can you add.
Documentation Here are all the settings you can add to the script. Please note all the options have the same format: - Code:
name: value, That means if you wanna add several options, the beginning of the script should look (more or less) like this: - Code:
const settings = { nameOption1: value, nameOption2: value, nameOption3: value, };
WARNING: To add an option with its default value it's the same as not to add it. The options are the following:
-
forums When creating a topic in the forums the functionality will appear. A forum will be represented by its ID (the URL of the forum). The IDs will be between brackets separated by commas. For example: - Code:
forums: [1, 2, 3, 4, 5], Special cases:
- []: Not turning on the feature in any forum (useless and self-defeating).
- Code:
forums: [],
Default value: - Code:
forums: [], searchIn The forum or category where the search will be done. To identify which forum or category will be done, an ID will be used which is added after a "c" for categories or a "f" for forums (between quote marks). For example, to search in the category with id 3, it would be: - Code:
searchIn: 'c3', There are special values: false: Search in the same forum where the topic is being created. '-1': Seach in all forum
Value by default: - Code:
searchIn: false, maxTopics Number of topics what will show. The minimum value is 3: Example: - Code:
maxTopics : 10, Value by default: - Code:
maxTopics : 5, autocomplete Default value: Turn on or off the autocomplete of the field of the title (the suggestions of the browser). That is, to put the attribute autocomplete of the element input to on or off You can use the value "true" to be on, or "false" to not be. Example: - Code:
autocomplete : true, Default value: - Code:
autocomplete : false, wordMinLength A minimum number of characters of a word to be taken into account in the search. Please note that because of the Forumotions limitations, it isn't possible to put less than 3. The value is a full number that can be between 4 to the maximum numbers of characters allowed by Forumotion. Example: - Code:
wordMinLength : 5, Default value: - Code:
wordMinLength : 4,
There also exists options for advanced users. To use them it is neccesary to have JS and HTML knowledge.
- Advanced options:
-
excludedCharacters Regular expression object that matches the characters to be ignored in titles. Default value: - Code:
excludedCharacters : new RegExp(/[.,\/#!$%\^&\*¿?!¡;:{}\\=\-_`~"«“‘’”»()\[\]]/, 'g'),
-
dom Custom object that allows you to modify the way in which the DOM of the forum is interpreted. Useful if you want to modify the HTML structure of the plugin or if you have modified parts of the templates which the plugin depends on. You can see all the options in the default value, which would correspond on how a phpBB3 forum structure is interpreted. Adapting it to any other version should be easy. In order to have an easier adaption to any type of template modifications, the relationships between the elements are all strictly parental (i.e.., there is no possibility that any of the DOM identifiers of this object are the same) and the way they are related parentally is of the deep type (i.e. they don't need to be children or direct parents between them - the plugin implementation uses the find and closest methods to select the elements). It is not necessary to put all the object properties that appear in the default value, only those that are different from those of the object in the default value. Default value: - Code:
dom : { /* Search page */ topicscontainer : '.forabg', topic : 'dd.dterm', infocontainer : '.span-tab', titlelink : '.topictitle', userlink : 'a[href^="/u"]', forumlink : 'a[href^="/f"]', topicicon : 'dl.icon', /* structure */ visible : 'visible', /* posting page */ titleinput : '#postingbox input[name="subject"]', inputcontainer : 'dl', /* created elements */ maincontainer : $('<div />', { id : 'similarTopics' }), similartopiccont : $('<div />', { class : 'topic-container' }), loadingelm : $('<div />', { class : 'spinner' }) .append($('<div/>', { class:'double-bounce1' })) .append($('<div/>', { class:'double-bounce2' })), topicelmcont : $('<div />', { class: 'topic' }), topicelmtitle : $('<div />', { class: 'topic-title' }), topicdatacont : $('<div />', { class: 'topic-data' }), topicflags : $('<div />', { class: 'topic-flags' }), topiciconcont : $('<div />', { class: 'topic-icon' }), topicstatus : $('<div />', { class: 'topic-status' }), topicelminfo : $('<div />', { class: 'topic-info' }), topicauthor : $('<span />', { class: 'topic-author', text: 'por ' }), topicforum : $('<span />', { class: 'topic-forum', text: ' en ' }), similarstitle : $('<h4 />', { class: 'similarTopics-title', text: 'Quizás te interese...' }), },
|