Sugerencia de temas al escribir

Hace tiempo preparé un plugin para el Foro de Soporte oficial de Foroactivo y lo compartí con la comunidad inglesa en uno de los foros más conocidos de la misma. Como este foro ha cerrado me gustaría que este código no se perdiese, por lo que lo comparto en el blog, aunque se separe un poco de la temática original.

¿Qué hace este plugin? Con este plugin harás que a la hora de abrir un nuevo tema, tus usuarios reciban sugerencias de posibles temas ya crados relacionados con el título del que está escribiendo. Este plugin está pensado principalmente para foros de soporte, evitando que los usuarios repitan preguntas ya hechas.

Una demo de como funciona el plugin

Instalación

Primero crea un nuevo JavaScript desde la Gestión de JavaScripts del PA y muéstralo en todas las páginas. Añadele el siguiente:
/* Similar Topics v.1.1.2 | Flerex | flerex.dev */
eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('2m 10=10||{};10.A=(b(){\'2n 2o\';j 8={11:[],1f:G,J:5,1G:4,1g:1h,1H:G,1i:G,1I:2p 2q(/[.,\\/#!$%\\^&\\*¿?!¡;:{}\\\\=\\-1j`~"«“‘’”»()\\[\\]]/,\'g\'),9:{1J:\'.12\',c:\'2r.2s\',1K:\'.13\',K:\'a[B^="/u"]\',C:\'a[B^="/f"]\',p:{1k:G,1l:\'1L.14\'},t:\'t\',1M:\'#2t 2u[L="2v"]\',1N:\'1L\',H:$(\'\',{1m:\'A\'}),1O:$(\'\',{k:\'c-2w\'}),1P:$(\'\',{k:\'15\'}).v($(\'\',{k:\'1Q-2x\'})).v($(\'\',{k:\'1Q-2y\'})),1R:$(\'\',{k:\'c\'}),1S:$(\'\',{k:\'c-i\'}),1T:$(\'\',{k:\'c-1n\'}),M:$(\'\',{k:\'c-2z\'}),1U:$(\'\',{k:\'c-14\'}),N:$(\'\',{k:\'c-D\'}),1V:$(\'\',{k:\'c-2A\'}),O:$(\'<1W />\',{k:\'c-2B\',x:\'2C \'}),P:$(\'<1W />\',{k:\'c-16\',x:\' 2D \'}),1X:$(\'<2E />\',{k:\'A-i\',x:\'2Fás 2G 2H...\'}),},},m={},2I,1Y=b(E,1Z){j Q;R b(...a){2J(Q);Q=2K(1j=>{Q=2L;E.17(r,...a)},1Z)}},1o=b(20){R 20.h(/%l%2M/g,\'%2N\').h(/%l%2O/g,\'%2P\').h(/%l%2Q/g,\'%2R\').h(/%l%2S/g,\'%2T\').h(/%l%2U/g,\'%2V\').h(/%l%2W/g,\'%2X\').h(/%l%2Y/g,\'%2Z\').h(/%l%30/g,\'%31\').h(/%l%32/g,\'%33\').h(/%l%34/g,\'%35\').h(/%l%36/g,\'%37\').h(/%l%38/g,\'%39\').h(/%l%3a/g,\'%3b\').h(/%l%3c/g,\'%3d\')},1p=b(n,E){$.3e({n:n,}).3f(b(1n){j w=[],$12=$(8.9.1J,1n);y($12.F){$12.I(8.9.c).18(0,8.J).21(b(){j $r=$(r),$13=$r.I(8.9.1K),$C=$r.I(8.9.C),$K=$r.I(8.9.K),$p=8.9.p.1k?$r.I(8.9.p.1l):$r.22(8.9.p.1l);j D;y(8.9.p.1k){D=$p.S(\'3g\')}1q{D=$p.T(\'U-V\').F?$p.T(\'U-V\').18(4,-1).h(/"/g,\'\'):G}w.3h({i:$13.x().23(),n:$13.S(\'B\'),14:$r.T(\'U-V\').18(4,-1).h(/"/g,\'\'),D,16:{L:$C.x(),n:$C.S(\'B\'),},1r:{L:$K.x(),n:$K.S(\'B\'),},})})}E.17(r,w)}).3i(1j=>{j 24;3j 24||G})},25=b(26){R 26.23().h(8.1I,\'\').3k(\' \').27(19=>19.F>=8.1G)},28=b(z){m.W.3l();y(z.F){j 1s=3m.3n();$.21(z,b(3o,c){j $1t=8.9.1S.o(),$1u=8.9.1R.o(),$1v=8.9.1V.o(),$O=8.9.O.o(),$N=8.9.N.o(),$P=8.9.P.o(),$M=8.9.M.o(),$1w=8.9.1T.o(),$p=8.9.1U.o(),$29=$(\'\',{1x:\'1y\',B:c.n,x:c.i}),$C=$(\'\',{1x:\'1y\',B:c.16.n,x:c.16.L}),$2a=$(\'\',{1x:\'1y\',B:c.1r.n,x:c.1r.L});$p.T(\'U-V\',`n(\'${ c.14 }\')`);c.D&&$N.T(\'U-V\',`n(\'${ c.D }\')`);$O.v($2a);$P.v($C);$1t.v($29);$1v.v($O,$P);$1w.v($1t,$1v);$M.v($N,$p);$1u.v($M,$1w);1s.v($1u[0])});m.W[0].3p(1s)}1q m.H.X(8.9.t)},2b=b(){m.1z.1a(8.9.t);m.1A.X(8.9.t);m.W.X(8.9.t)},2c=b(){m.1z.X(8.9.t);m.1A.1a(8.9.t);m.W.1a(8.9.t)},2d=b(Y,E){j 1b={3q:8.1f||`f${/\\?f=(\\d+)/.3r(1c.Z)[1]}`,3s:\'3t\',3u:0,3v:\'3w\',2e:\'3x\',3y:Y.3z(\' \'),};1p(`/Z?${1o($.2f(1b))}`,b(z){j w=z;y(w.F<8.J){1b.2e=\'3A\';1p(`/Z?${1o($.2f(1b))}`,b(z){j 1d=z,2g=8.J-w.F;1d=1d.27(19=>w.I(e=>e.n==19.n)===3B);w=[...w,...1d.18(0,2g)];E.17(r,w)})}E.17(r,w)})},1B=b(){m.H.X(8.9.t)},2h=b(){m.H.1a(8.9.t)},1C=b($i){j Y=25($i.3C());y(Y.F==0){1B();R}2h();2b();2d(Y,b(z){28(z);2c()})},2i=b($i){j $A=8.9.H.o(),$15=8.9.1P.o(),$1D=8.9.1O.o(),$1E=8.9.1X.o();m={H:$A,1z:$15,W:$1D,1A:$1E,};$A.v($15,$1E,$1D);$i.22(8.9.1N).3D($A)},1e=b(2j){$.3E(1h,8,2j);j Q,$i=$(8.9.1M);y(!8.1i)$i.S(\'1i\',\'3F\');2i($i);y(8.1g)$i.1F(\'3G\',1Y(b(e){y(e.3H!==0)1C($i)},3I));1q $i.1F(\'2k\',b(){1C($i)});y(8.1g&&8.1H)$i.1F(\'2k\',b(){1B()})};R{1e:1e,}})();!b(){3J 8={11:[1,2,3,4,5,6,7],1f:\'-1\',J:5,};1c.3K==\'/3L\'&&1c.Z.2l(\'&3M=3N\')>-1&&(8.11===1h||8.11.3O(1m=>1c.Z.2l(`?f=${1m}`)>-1))&&$(b(){10.A.1e(8)})}();',62,237,'||||||||settings|dom||function|topic|||||replace|title|let|class|C3|structure|url|clone|topicicon|div|this||visible||append|relatedTopics|text|if|arr|similarTopics|href|forumlink|status|cb|length|false|maincontainer|find|maxTopics|userlink|name|topicflags|topicstatus|topicauthor|topicforum|timeout|return|attr|css|background|image|topiccontainer|removeClass|words|search|FLRX|forums|forabg|topictitle|icon|spinner|forum|call|slice|elm|addClass|params|location|searchAnyWord|init|searchIn|triggerWhenWriting|true|autocomplete|_|img|selector|id|data|sanitizeURI|searchTopics|else|user|docfrag|topicTitle|topicContainer|topicInfo|topicdata|target|_blank|loadingcontainer|recentstitle|hideSimilarTopics|searchSimilarTopics|topicsContainer|recentsTitle|on|wordMinLength|hideAtFocusout|excludedCharacters|topicscontainer|titlelink|dl|titleinput|inputcontainer|similartopiccont|loadingelm|double|topicelmcont|topicelmtitle|topicdatacont|topiciconcont|topicelminfo|span|similarstitle|debounce|delay|uri|each|closest|trim|up|getWords|str|filter|updateDOM|topicLink|authorlink|setLoadingStatus|topicsRetrieved|searchAlgorithm|search_terms|param|neededElms|showSimilarTopics|generateStructure|options|focusout|indexOf|var|use|strict|new|RegExp|dd|dterm|postingbox|input|subject|container|bounce1|bounce2|flags|info|author|por|en|h4|Quiz|te|interese|request|clearTimeout|setTimeout|null|91|D1|B1|F1|81|C1|89|C9|8D|CD|93|D3|9A|DA|9C|DC|A1|E1|A9|E9|AD|ED|B3|F3|BA|FA|BC|FC|ajax|done|src|push|fail|throw|split|empty|document|createDocumentFragment|index|appendChild|search_where|exec|show_results|topics|sort_by|sort_dir|DESC|all|search_keywords|join|any|undefined|val|after|extend|off|keypress|which|500|const|pathname|post|mode|newtopic|some'.split('|'),0,{}))

Ahora añade el siguiente CSS a tu foro para obtener el diseño por defecto del plugin (igual que el del gif anterior):

#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 white;
	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;
}

Finalmente añade el siguiente JS desde la Gestión de JavaScript del PA y asegúrate de marcarlo para que se muestre en todas las páginas:

!function() {
 
	const settings = {
		forums : [1,2,3,4,5,6,7],
		searchIn : '-1',
		maxTopics : 5,
	};
 
 
	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);
	});
 
}();

Si te has dado cuenta hay una constante settings que permite pasar distintos parámetros de configuración al plugin. Esos parámetros no son los únicos disponibles. Todas las posibles configuraciones están en la documentación.

Documentación

A continuación encontrarás todos los parámetros de configuración disponibles. Recuerda que los parámetros de configuración tienen la siguiente sintaxis:

setting: value,

Por lo que si tienes más de un parámetro de configuración, la constante del código debería verse tal que así:

const settings = {
	setting1: value,
	setting2: value,
	setting3: value,
};

Antes de empezar, ten en cuenta que poner un parámetro de configuración con el valor por defecto es lo mismo que no poner nada.

Opciones básicas

  • forums: Todos los foros donde se sugerirán temas relacionados al abrir uno nuevo. Un foro es representado por su ID (la que aparece en la URL al mismo). Las IDs serán puestas entre corchetes, separadas por comas. Por ejemplo:
    forums: [1, 2, 3, 4, 5],

    Casos especiales:

    • []: Deshabilita el plugin en todos los foros (contraproducente).

    Valor por defecto:

    forums: [],
  • searchIn: El foro o categoría donde la búsqueda de temas se realizará. Para identificar el foro o categoría se utilizará su ID, precedido por la letra f en el caso de los foros o c en el caso de las categorías, todo entre comillas. Por ejemplo, para buscar en la categoría con ID 2 se usaría:
    searchIn: 'c2',

    Casos especiales:

    • false: Buscar en el mismo foro donde el tema se está creando.
    • -1: Buscar en todo el foro.

    Valor por defecto:

    searchIn: false,
  • maxTopics: Cantidad máxima de temas a mostrar. Debido a problemas de implementación, el número máximo de temas a mostrar será el número máximo de temas por página que Foroactivo muestra.

    Ejemplo:

    maxTopics : 10,

    Valor por defecto:

    maxTopics : 5,
  • triggerWhenWriting: Activa o desactiva la funcionalidad mientras se escriba. Desactivar esta opción significará que la lista será mostrada solamente cuando el foco salga del cuadro de escritura del título. Ejemplo:
    triggerWhenWriting : false,

    Valor por defecto:

    triggerWhenWriting : true,
  • hideAtFocusout: Oculta la lista de temas cuando el cuadro de escritura pierde el foco.Precondiciones:
    • La opción triggerWhenWriting debe tener el valor true.

    Ejemplo:

    hideAtFocusout : true,

    Valor por defecto:

    hideAtFocusout : false,
  • autocomplete: Desactiva o activa autofill (sugerencias del navegador) en el campo de texto del título, es decir, pone el atributo autocomplete a on u off.

    Ejemplo:

    autocomplete : true,

    Valor por defecto:

    autocomplete : false,
  • wordMinLength: El número mínimo de caracteres de una palabra para que sea tomada en cuenta en la búsqueda. Ten en cuenta que debido a limitaciones de Foroactivo, usar un valor menor que 4 no tendrá efecto.

    El valor deberá ser un entero entre el número 4 (incluído) e infinito (es decir, el número máximo de caracteres permitidos por Foroactivo en las búsquedas).

    Ejemplo:

    wordMinLength : 5,

    Valor por defecto:

    wordMinLength : 4,

Opciones avanzadas

Estas opciones están pensadas tan solo para usuarios avanzados.

  • excludedCharacters: Objeto RexExp que hace match con los caracteres ignorados del campo de texto: Valor por defecto:
    excludedCharacters : new RegExp(/[.,\/#!$%\^&\*¿?!¡;:{}\\=\-_`~"«“‘’”»()\[\]]/, 'g'),
  • dom: Objeto personalizable utilizado para modificar la forma en la que el DOM se lee. Muy útil encaso de que hayas modificado tus plantillas y estas teniendo problemas de incompatibilidad.

    Puedes ver todas las modificaciones posibles en la sección «Valor por defecto», que corresponde a la estructura por defecto de un foro con versión phpBB3. Adaptando estas opciones a cualquier otra versión debería ser sencillo. Para poder hacer la adaptación simple, todas las relaciones entre elemetnos son estrictamente parentales (es decir, no hay posibilidad de que ninguno de los identificadores del DOM de este objeto representen al mismo objeto, solo un padre, abuelo cercano o lejano, hijo o nieto cercano o lejano). Si hay cualquier problema entiendiendo esto no tendré problema en ayudar.

    Valor por defecto:

    dom : {
    
    	/* Search page */
    	topicscontainer		: '.forabg',
    	topic				: 'dd.dterm',
    	titlelink			: '.topictitle',
    	userlink			: 'a[href^="/u"]',
    	forumlink			: 'a[href^="/f"]',
    	topicicon			: { img: false, selector: '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...' }),
    
    },

A tener en cuenta…

  • El soporte de navegadores de este plugin sigue mis reglas de soporte general: solo los principales navegadores (Chrome, Firefox, Safari, Edge y derivados) en su versión actual y versión actual – 1. No soportaré ninguna versión de Internet Explorer puesto que el navegador ya ha llegado al fin de su ciclo de vida. Esto no significa que el plugin no funcionará en los navegadores no soportados, podría funcionar. Esto tan solo significa que en caso de que no funcione, no corrigiré esta falla. También significa que si usas uno de estos navegadores deberías cambiarte.
  • Si encuentras algún error en los navegadores soportados anteriormente mencionados, agradecería que se me lo hiciese saber.
  • Además, si tienes algún tipo de sugerencia sobre características a añadir, tan solo dilo, no muerdo… mucho.