Comentarios sobre "on-discourse" javascript para configurar JS personalizado para cada página?

He estado escribiendo una aplicación Svelte que necesita instalarse en cada página. Descubrí cómo agregar mi archivo JS a la sección head y hacer que se cargue en la primera carga de página. Sin embargo, al experimentar, me di cuenta de que Discourse carga nuevo contenido a través de XHR y reemplaza ciertas secciones, por lo que mi aplicación no se reinicializaba cuando se cargaba una nueva página.

Intenté varias formas de ser notificado cuando la página cambia, pero parece que Ember no proporciona los ganchos y no pude encontrar ningún evento personalizado al que pudiera escuchar.

Parece que una forma es agregar un observador de mutaciones al DOM y observar los cambios. Descubrí que el #topic div parece ser el que se recarga (y los atributos cambian, por lo que puedes observar solo los cambios de atributos). Configuré un observador de mutaciones allí (los observadores de mutaciones son la forma nueva y eficiente de observar cambios en el DOM). La forma en que funciona es observar los cambios y, cuando ocurren, ejecutar una función de devolución de llamada para recargar mi aplicación Svelte para esa página.

Me gusta este enfoque y me gustaría recibir comentarios.

Una pregunta: ¿debería observar los cambios en la URL en su lugar? ¿Es una mejor idea registrar un oyente para popstate?

Para usarlo, haz algo como esto en tu tema/head:

<script src="https://files.extrastatic.dev/community/on-discourse.js"></script>
<script src="https://files.extrastatic.dev/community/index.js"></script>
<link rel="stylesheet" type="text/css" href="https://files.extrastatic.dev/community/index.css">

Luego, dentro de tu biblioteca, puedes llamar a on-discourse de esta manera:

function log(...msg) {
  console.log('svelte', ...msg);
}

// Este es el código de instalación de Svelte
function setup() {
  try {
    const ID = 'my-special-target-id';
    log('Inside setup()');
    const el = document.getElementById(ID);
    if (el) {
      log('Removed existing element', ID);
      el.remove();
    }
    const target = document.createElement("div");
    target.setAttribute("id", ID);
    log('Created target');
    document.body.appendChild(target);
    log('Appended child to body');
    const app = new App({
      // eslint-disable-next-line no-undef
      target
    });
    log('Created app and installed');
  } catch(err) {
    console.error('Unable to complete setup()', err.toString() );
  }
}

(function start() {
  log('Starting custom Svelte app');
  // Reinstalar en caso de cambios
  window.onDiscourse && window.onDiscourse( setup );
  // Cargar la aplicación en la primera carga de página
  window.addEventListener('load', () => {
    setup();
  });
  log('Finished custom Svelte app');
})();

Básicamente, solo llamas a window.onDiscourse(callback) con tu función de devolución de llamada (y puedes ejecutarla varias veces para instalar varias funciones de devolución de llamada), y luego, cuando ocurre una mutación, se ejecuta esa función de devolución de llamada para inicializar tu aplicación.

Aquí está el código completo para on-discourse.js. (edición: actualicé esto para usar #topic, que parece ser algo bueno a observar, porque los atributos cambian cuando la página se carga, y luego la mutación solo necesita observar los cambios de atributos, en lugar de buscar en todo el árbol DOM para #main-outlet)

let mutationObservers = [];

function log(...msg) {
  console.log("on-discourse", ...msg);
}

function observeMainOutlet() {
  log('Observing main outlet');
  // Selecciona el nodo que se observará para mutaciones
  const targetNode = document.getElementById("topic");
 
  if (targetNode) {
    // Opciones para el observador (qué mutaciones observar)
    const config = { attributes: true };
    
   // crea una instancia de observador para restablecer cuando cambie childList
    const observer = new MutationObserver(function(mutations) {
      let reset = false;
      mutations.forEach(function(mutation) {
        if (mutation.type === 'attributes') {
          log('Found main-outlet mutation, running callbacks');
          mutationObservers.forEach( (s,i) => {
            try {
              log(`Running div callback ${i+1}`);      
              s();
              log(`Finished div callback ${i+1}`);      
            } catch( err ) {
              log(`Div callback error (${i+1})`, err );      
            }
         });
       }
      });
    });
    
    // Comienza a observar el nodo objetivo para las mutaciones configuradas
    observer.observe(targetNode, config);
    
    // Más tarde, puedes dejar de observar
    // observer.disconnect();
    log('Done with outlet observer');
  } else {
    console.error('on-discourse FATAL ERROR: Unable to find main-outlet');
  }
}

window.addDiscourseDivMutationObserver = (cb) => {
    log('Adding on-discourse div mutation callback');  
    mutationObservers.push(cb);
    log('Added on-discourse div mutation callback');    
}

window.addEventListener("load", () => {
    log('Setting up topic observer');
    if (mutationObservers.length > 0) {
        observeMainOutlet();
    }
    log('Created topic observer');  
});

log('Completed setup of on-discourse.js');

Creo que quieres poner tu código en un inicializador en lugar de en un encabezado. Consulta la guía del desarrollador y mira cómo puedes poner tu JS en archivos separados.

1 me gusta

¡Muchas gracias por la respuesta!

Creo que esto es en parte lo que me confunde, me cuesta encontrar referencias para extender Discourse añadiendo mi propio JS.

Cuando busco en DuckDuckGo “Discourse developer guide”, el primer enlace que obtengo es un enlace al repositorio de GitHub.

El siguiente enlace es a la “Discourse Advanced Developer Install Guide”. Esta guía es para decirte cómo configurar Rails para el desarrollo, pero no tiene enlaces sobre cómo instalar JS personalizado AFAIK. Estoy tratando de evitar un proceso de compilación complicado, que es lo que recuerdo de mis días de Rails. Realmente me gustaría desarrollar este código de extensión JS de forma aislada, y luego poner una etiqueta de script en mi sitio. Por lo tanto, realmente no quiero tener que configurar un entorno Rails localmente para poder compilarlo; ¿quizás me estoy perdiendo la utilidad de eso? Pero, realmente me gusta poder simplemente actualizar un contenedor Docker que usa un tema con unas pocas etiquetas <script>.

El siguiente enlace es una “Guía para principiantes para desarrollar temas de Discourse”, que trata sobre el desarrollo de temas, no es lo que necesito, ¿verdad?

Veo enlaces a la API de Discourse, que obviamente no es lo que quiero.

Si busco “discourse javascript initializer”, veo este enlace de hace 5 años: Execute JavaScript code from a plugin once after load Pero, eso parece que estoy conectando a Rails, y siento que debería haber una manera más simple, ¿y este hilo también parece no resuelto?

Otro enlace a “discourse javascript initializer” sugiere hacer lo que estoy haciendo para instalar el JS, pero no tiene sugerencias sobre cómo asegurarse de que cada vez que el contenido de la página cambie (ya sea a través de una actualización completa de la página o una solicitud XHR “tipo turbolinks”): https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

¿Es esta la discusión que debería revisar? A versioned API for client side plugins

¿O, quizás esto? A primera vista no entiendo la sintaxis (esas anotaciones no parecen JS, ¿son convenciones de Rails?) así que no estoy seguro de si esto es lo que necesito: Using Plugin Outlet Connectors from a Theme or Plugin

Sí, no todo es trivial.

Discourse es una aplicación EmberJS.

Idealmente, las extensiones de JavaScript deberían escribirse en el framework EmberJS utilizando un Componente Temático (o Plugin), la API de JavaScript de Discourse cuando corresponda y los enchufes de plugins.

El principal problema que encontrarás al usar scripts externos ad hoc es hacer que se activen en el momento adecuado.

Para asegurarte de que necesitas vincularlos a acciones en un Componente (que puede activarse en la inserción o actualización).

1 me gusta

Eso es una buena confirmación. Pero, debo decir, estoy muy contento con mi código de observador de mutaciones. Detecta cuándo cambia el contenido de la página correctamente y luego me permite ejecutar mi código JS personalizado. Está funcionando de maravilla, y no tuve que aprender Ember, ni modificar la aplicación RoR de ninguna manera. Estoy muy contento con esta solución por ahora. Gracias por todos los comentarios.

1 me gusta

Acabo de intentar usar un observador de eventos popstate. Si hubiera funcionado, el código tendría 5 líneas en lugar de 20. Sin embargo, hacer clic en los enlaces no parece activar ese evento. Si uso los botones de adelante o atrás, sí veo el evento. No entiendo popstate lo suficientemente bien, pero por ahora me quedo con un observador de mutaciones de div.

Me di cuenta de que estaba haciendo una cosa tonta aquí. Estoy modificando el tema y agregando código al encabezado. Si cambio a un tema diferente, esos cambios se pierden. La forma correcta es agregar el código usando un plugin. Usé la plantilla aquí: GitHub - discourse/discourse-plugin-skeleton: Template for Discourse plugins. Luego, agregué código JavaScript de esta manera:

export default {
  name: 'alert',
    initialize() {
        const scripts = [
            'https://files.extrastatic.dev/community/on-discourse.js',
            'https://files.extrastatic.dev/community/index.js'
        ];
        scripts.forEach( s => {
            const script = document.createElement('script');
            script.setAttribute('src', s);
            document.head.appendChild(script);
        });

        const link = document.createElement('link');
        link.setAttribute('rel', "stylesheet");
        link.setAttribute('type', "text/css");
        link.setAttribute('href', "https://files.extrastatic.dev/community/index.css");
        document.head.appendChild(link);
    }
};

Luego, ejecuté ./launcher rebuild app y luego habilité el plugin.
Por último, necesité agregar una política CSP en la configuración para permitir la carga de esos scripts. Fui a admin->settings->security y agregué files.extrastatic.dev a “content security policy script src” y hice clic en aplicar.

No es cierto.

Si no estás modificando la API (por ejemplo, usando 100% javascript) no hay necesidad de usar un Plugin, que son más engorrosos de desplegar y cambiar.

Si lo único que haces es modificar o añadir algo de JavaScript, un Componente de Tema debería ser suficiente.

2 Me gusta

OK. Pero, cuando cambio de temas (o si permito que un usuario elija su propio tema), ¿no me enfrento al problema de que necesito asegurarme de que cada tema sea 1) editable para poder agregar las nuevas etiquetas de encabezado e incluso peor 2) necesito mantener mi código en todos los temas?

Cuando comencé a usar diferentes temas, algunos indican que para editar el tema necesitas modificar el repositorio de github. Esto parece muy engorroso e inflexible. Pero, mantener mis etiquetas de script en cada tema parece muy propenso a errores y un problema aún mayor.

¿Qué me estoy perdiendo? ¿Hay alguna forma de resolver esos problemas solo usando temas?

1 me gusta

En este caso, lo convertirías en un Componente de Tema y añadirías ese Componente a todos los Temas. (Admito que esto es una complejidad adicional).

Si quieres ejecutar el sitio profesionalmente, asegurarte de que todo tu código esté en Github es una muy buena idea.

Sin embargo, inicialmente, cuando solo estás probando algunas ideas, seguro que puedes jugar con él “localmente” si así lo deseas.

Cuando los cambios de código se hayan asentado, probablemente deberías incluirlo en el control de versiones, pero argumentaría que cuanto antes, mejor.

Una forma de combinar la rápida evolución con GitHub es combinarla con esto:

Y desplegar en un Componente de Tema de prueba en un Tema de prueba… pero ahora nos estamos poniendo realmente creativos…

Lo que te permite desplegar sobre la marcha, luego puedes asegurar tus cambios en un repositorio git una vez que estés satisfecho.

1 me gusta

Echa un vistazo a GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes

Esto es lo que necesitas How do you force a script to refire on every page load in Discourse? - #5 by simon

<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() =>{
        // código
    });
</script>
3 Me gusta

¡Wow @RGJ, eso se ve perfecto! ¡Gracias!

2 Me gusta

Hola @RGJ, he estado intentando averiguar cómo hacer esto y debo admitir que me estoy confundiendo. Parece que la API de plugins ha cambiado un poco a lo largo de los años y no estoy seguro de cómo obtener lo que está actual, o ver un ejemplo.

Tu código parece que va en una página HTML en algún lugar, ya que está envuelto por las etiquetas de script. Yo estaba usando código como este, que es el propio archivo JS.

https://extrastatic.dev/publicdo/publicdo-discourse-plugin/-/blob/main/assets/javascripts/discourse/initializers/kanji.js?ref_type=heads

¿Cómo modifico mi plugin para usar el código que proporcionaste? ¿Es este código que sugeriste algo que va en un plugin, o lo pongo en un tema, o de lo contrario lo añado a las páginas?

Intenté usar código como este:

export default {
  name: 'publicdo',
    initialize() {
     withPluginApi('0.1', api => {
                api.onPageChange(() => {
                   console.log('Ejecuta mi código aquí.');
                });
      });
    }
}

Pero esto falla con Uncaught (in promise) ReferenceError: withPluginApi is not defined, así que claramente no es algo que el JS cargado en general reciba.

Simplemente debes

import { withPluginApi } from "discourse/lib/plugin-api";

3 Me gusta

Deberías navegar por las fuentes enlazadas en Theme component (¡el ecosistema es casi todo de código abierto, aprovéchalo!).

Notarás que las importaciones son a menudo necesarias y comunes (al igual que el uso de api.onPageChange y otras funciones útiles de la api).

4 Me gusta

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.