Commentaires sur le javascript "on-discourse" pour la configuration de JS personnalisé pour chaque page ?

Je développe une application Svelte qui doit s’installer sur chaque page. J’ai trouvé comment ajouter mon fichier JS dans la section head et le faire charger au premier chargement de la page. Cependant, en expérimentant, j’ai réalisé que Discourse chargeait du nouveau contenu via XHR et remplaçait certaines sections, et donc mon application n’était pas réinitialisée lorsqu’une nouvelle page se chargeait.

J’ai essayé diverses tentatives pour être notifié lorsque la page change, mais il semble qu’Ember ne fournisse pas les hooks, et je n’ai pas réussi à trouver d’événements personnalisés auxquels je pourrais m’abonner.

Il semble qu’une façon soit d’ajouter un observateur de mutations au DOM et de surveiller les changements. J’ai trouvé que le `#topic div semble être celui qui est rechargé (et les attributs changent, vous pouvez donc simplement surveiller les changements d’attributs). J’ai configuré un observateur de mutations là-bas (les observateurs de mutations sont la nouvelle méthode performante pour surveiller les changements DOM). La façon dont cela fonctionne est de surveiller les changements, et lorsqu’ils se produisent, d’exécuter une fonction de rappel pour recharger mon application Svelte pour cette page.

J’aime cette approche et j’aimerais avoir votre avis.

Une question : devrais-je plutôt surveiller les changements d’URL ? Est-ce une meilleure idée d’enregistrer un écouteur pour popstate ?

Pour l’utiliser, faites quelque chose comme ceci dans votre thème/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">

Ensuite, à l’intérieur de votre bibliothèque, vous pouvez appeler on-discourse comme ceci :

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

// Ceci est le code d'installation 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');
  // Réinstaller en cas de changements
  window.onDiscourse && window.onDiscourse( setup );
  // Charger l'application au premier chargement de la page
  window.addEventListener('load', () => {
    setup();
  });
  log('Finished custom Svelte app);  
})();

En gros, vous appelez simplement window.onDiscourse(callback) avec votre fonction de rappel (et vous pouvez l’exécuter plusieurs fois pour installer plusieurs fonctions de rappel), et lorsqu’une mutation se produit, cette fonction de rappel est exécutée pour initialiser votre application.

Voici le code complet pour on-discourse.js. (edit : j’ai mis à jour ceci pour utiliser #topic, ce qui semble être une bonne chose à surveiller, car les attributs changent lorsque la page se charge, et la mutation n’a alors qu’à surveiller les changements d’attributs, plutôt que de parcourir l’intégralité de l’arborescence DOM pour #main-outlet)

let mutationObservers = [];

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

function observeMainOutlet() {
  log('Observing main outlet');
  // Sélectionner le nœud qui sera observé pour les mutations
  const targetNode = document.getElementById("topic");
 
  if (targetNode) {
    // Options pour l'observateur (quelles mutations observer)
    const config = { attributes: true };
    
   // créer une instance d'observateur pour réinitialiser lorsque childList change
    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 );	    	      
		}
	 });
       }
      });
    });
    
    // Commencer à observer le nœud cible pour les mutations configurées
    observer.observe(targetNode, config);
    
    // Plus tard, vous pouvez arrêter d'observer
    // 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');

Je pense que vous voulez mettre votre code dans un initialiseur plutôt que dans un head. Regardez le guide du développeur et voyez comment vous pouvez mettre votre JS dans des fichiers séparés.

1 « J'aime »

Merci beaucoup pour votre réponse !

Je pense que c’est en partie ce qui me rend confus, j’ai du mal à trouver des références pour étendre Discourse en ajoutant mon propre JS.

Lorsque je fais une recherche sur DuckDuckGo pour « Discourse developer guide », le premier lien que j’obtiens est un lien vers le dépôt GitHub.

Le lien suivant mène au « Discourse Advanced Developer Install Guide ». Ce guide explique comment configurer Rails pour le développement, mais ne contient aucun lien sur la façon d’installer du JS personnalisé, d’après ce que j’ai pu voir. J’essaie d’éviter un processus de build compliqué, ce dont je me souviens de mon époque Rails. J’aimerais vraiment développer ce code d’extension JS en isolation, puis placer une balise script sur mon site. Donc, je ne veux vraiment pas avoir à configurer un environnement Rails localement pour pouvoir le construire ; peut-être que je manque l’utilité de cela ? Mais j’aime beaucoup pouvoir simplement mettre à jour un conteneur Docker qui utilise un thème avec quelques balises <script>.

Le lien suivant est un « Beginner’s guide to developing Discourse Themes » qui concerne le développement de thèmes, pas ce dont j’ai besoin, n’est-ce pas ?

Je vois des liens vers l’API Discourse, ce qui n’est évidemment pas ce que je veux.

Si je recherche « discourse javascript initializer », je vois ce lien datant de 5 ans : Execute JavaScript code from a plugin once after load Mais cela semble être une connexion à Rails, et j’ai l’impression qu’il devrait y avoir un moyen plus simple, et ce fil de discussion semble également non résolu ?

Un autre lien vers « discourse javascript initializer » suggère de faire ce que je fais pour installer le JS, mais ne donne pas de suggestions sur la façon de s’assurer que chaque fois que le contenu de la page change (soit par un rafraîchissement complet de la page, soit par une requête XHR de type « turbolinks ») : https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

Est-ce que cette discussion est celle que je devrais examiner ? A versioned API for client side plugins

Ou, peut-être celle-ci ? À première vue, je ne comprends pas la syntaxe (ces annotations ne ressemblent pas à du JS, ce sont des conventions Rails ?) donc je ne suis pas sûr si c’est ce dont j’ai besoin : Using Plugin Outlet Connectors from a Theme or Plugin

Ce n’est pas tout à fait trivial.

Discourse est une application EmberJS.

Idéalement, les extensions JavaScript devraient être écrites dans le framework EmberJS en utilisant un composant de thème (ou un plugin), l’API JavaScript de Discourse le cas échéant et les « plugin outlets ».

Le principal problème que vous rencontrerez en utilisant des scripts externes ad hoc est de les faire s’exécuter au bon moment.

Pour vous assurer de cela, vous devez les lier à des actions dans un composant (qui peut être configuré pour s’exécuter lors de l’insertion ou de la mise à jour).

1 « J'aime »

C’est une bonne confirmation. Mais, je dois dire, je suis vraiment content de mon code de mutation observer. Il détecte correctement quand le contenu de la page change, et me permet ensuite d’exécuter mon code JS personnalisé. Ça fonctionne à merveille, et je n’ai pas eu à apprendre Ember, ni à modifier l’application RoR d’aucune manière. Je suis vraiment content de cette solution pour l’instant. Merci pour tous les commentaires.

1 « J'aime »

J’ai juste essayé d’utiliser un observateur d’événements popstate. Si cela avait fonctionné, le code ferait 5 lignes au lieu de 20. Cependant, cliquer autour ne semble pas déclencher cet événement. Si j’utilise les boutons avant ou arrière, je vois l’événement. Je ne comprends pas assez bien popstate mais pour l’instant, je m’en tiens à un observateur de mutation de div.

J’ai réalisé que je faisais une chose stupide ici. Je modifie le thème et j’ajoute du code dans l’en-tête. Si je change de thème, ces modifications sont perdues. La bonne façon est d’ajouter le code à l’aide d’un plugin. J’ai utilisé le modèle ici : GitHub - discourse/discourse-plugin-skeleton: Template for Discourse plugins. Ensuite, j’ai ajouté du code JavaScript comme ceci :

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);
    }
};

Ensuite, j’ai exécuté ./launcher rebuild app puis activé le plugin.
Enfin, j’ai dû ajouter une politique CSP dans les paramètres pour autoriser le chargement de ces scripts. Je suis allé dans admin->settings->security puis j’ai ajouté files.extrastatic.dev à “content security policy script src” et j’ai cliqué sur appliquer.

Pas vrai.

Si vous ne modifiez pas l’API (par exemple, en utilisant 100% JavaScript), il n’est pas nécessaire d’utiliser un plugin, qui est plus fastidieux à déployer et à remplacer.

Si tout ce que vous faites est de modifier ou d’ajouter du JavaScript, un composant de thème devrait suffire.

2 « J'aime »

OK. Mais, lorsque je change de thème (ou si je permets à un utilisateur de choisir son propre thème), ne suis-je pas confronté au problème de devoir m’assurer que chaque thème est 1) modifiable pour que je puisse ajouter les nouvelles balises d’en-tête et, pire encore, 2) que je dois maintenir mon code sur tous les thèmes ?

Lorsque j’ai commencé à utiliser différents thèmes, certains indiquent que pour modifier le thème, il faut modifier le dépôt GitHub. Cela semble très fastidieux et inflexible. Mais, maintenir mes balises de script sur chaque thème semble très sujet aux erreurs et pose un problème encore plus important.

Qu’est-ce qui m’échappe ? Existe-t-il un moyen de résoudre ces problèmes en utilisant uniquement des thèmes ?

1 « J'aime »

Dans ce cas, vous en feriez un composant de thème et ajouteriez ce composant à tous les thèmes. (Je reconnais que cela ajoute une complexité supplémentaire).

Si vous souhaitez gérer le site de manière professionnelle, il est fortement recommandé de mettre tout votre code sur Github.

Cependant, au début, lorsque vous expérimentez simplement quelques idées, vous pouvez certainement le modifier “localement” si vous le souhaitez.

Lorsque les modifications de code se seront stabilisées, vous devriez probablement le placer dans un système de contrôle de version, mais je soutiens que plus tôt c’est mieux.

Une façon de combiner une évolution rapide avec GitHub est de la combiner avec ceci :

Et déployer sur un composant de thème de test dans un thème de test… mais là, nous entrons vraiment dans des choses complexes…

Ce qui vous permet de déployer à la volée, puis de sécuriser vos modifications dans un dépôt git une fois que vous êtes satisfait.

1 « J'aime »

Regardez GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes

C’est ce dont vous avez besoin 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(() =>{
        // code
    });
</script>
3 « J'aime »

Wow @RGJ, ça a l’air parfait ! Merci !

2 « J'aime »

Salut @RGJ J’ai essayé de comprendre comment faire cela et je dois admettre que je suis confus. Il semble que l’API des plugins ait un peu changé au fil des ans et je ne sais pas comment obtenir ce qui est actuel, ni voir un exemple.

Votre code semble entrer dans une page HTML quelque part car il est enveloppé par les balises script. J’utilisais du code comme celui-ci qui est le fichier JS lui-même.

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

Comment puis-je modifier mon plugin pour utiliser le code que vous avez fourni ? Ce code que vous avez suggéré est-il quelque chose qui va dans un plugin, ou dois-je le mettre dans un thème, ou l’ajouter autrement aux pages ?

J’ai essayé d’utiliser du code comme ceci :

export default {
  name: 'publicdo',
    initialize() {
     withPluginApi('0.1', api => {
                api.onPageChange(() => {
                   console.log('Exécutez mon code ici.');
                });
      });
    }
}

Mais cela échoue avec Uncaught (in promise) ReferenceError: withPluginApi is not defined, donc ce n’est clairement pas quelque chose que le JS chargé en général reçoit.

Vous devez simplement

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

3 « J'aime »

Vous devriez vraiment parcourir les sources liées dans Theme component (l’écosystème est presque entièrement open-source - faites-vous plaisir !).

Vous remarquerez que les importations sont souvent nécessaires et courantes (tout comme l’utilisation de api.onPageChange et d’autres fonctions utiles de l’API).

4 « J'aime »

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