Feedback su javascript "on-discourse" per impostare JS personalizzato per ogni pagina?

Sto scrivendo un’app Svelte che deve installarsi in ogni pagina. Ho capito come aggiungere il mio file JS nella sezione head e farlo caricare al primo caricamento della pagina. Tuttavia, mentre sperimentavo, mi sono reso conto che discourse carica nuovi contenuti tramite XHR e sostituisce determinate sezioni, quindi la mia app non veniva re-inizializzata quando veniva caricata una nuova pagina.

Ho provato vari tentativi per essere avvisato quando la pagina cambia, ma sembra che Ember non fornisca gli hook e non sono riuscito a trovare alcun evento personalizzato a cui potessi ascoltare.

Sembra che un modo sia aggiungere un osservatore di mutazioni al DOM e monitorare le modifiche. Ho scoperto che il `#topic div sembra essere quello che viene ricaricato (e gli attributi cambiano in modo da poter semplicemente monitorare le modifiche degli attributi). Ho impostato un osservatore di mutazioni lì (gli osservatori di mutazioni sono il modo performante più recente per monitorare le modifiche del DOM). Il modo in cui funziona è monitorare le modifiche e, quando si verificano, eseguire una callback per ricaricare la mia app Svelte per quella pagina.

Mi piace questo approccio e vorrei un feedback.

Una domanda: dovrei invece monitorare le modifiche all’URL? È una buona idea registrare un listener per popstate?

Per usarlo, fai qualcosa di simile in questo tuo 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">

Quindi, all’interno della tua libreria puoi chiamare on-discourse in questo modo:

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

// Questo è il codice di installazione di 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');
  // Re-install on changes
  window.onDiscourse && window.onDiscourse( setup );
  // Load the app the first page load
  window.addEventListener('load', () => {
    setup();
  });
  log('Finished custom Svelte app);  
})();

In sostanza, basta chiamare window.onDiscourse(callback) con la tua callback (e puoi eseguirla più volte per installare più callback), e poi quando si verifica una mutazione, quella callback viene eseguita per inizializzare la tua app.

Ecco il codice completo per on-discourse.js. (edit: ho aggiornato questo per usare #topic, che sembra una buona cosa da monitorare, perché gli attributi cambiano quando la pagina si carica, e poi la mutazione deve solo monitorare le modifiche degli attributi, piuttosto che cercare nell’intero albero DOM per #main-outlet)

let mutationObservers = [];

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

function observeMainOutlet() {
  log('Observing main outlet');
  // Select the node that will be observed for mutations
  const targetNode = document.getElementById("topic");
 
  if (targetNode) {
    // Options for the observer (which mutations to observe)
    const config = { attributes: true };
    
   // create an observer instance to reset when childList changes
    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 );	    	      
		}
	 });
       }
      });
    });
    
    // Start observing the target node for configured mutations
    observer.observe(targetNode, config);
    
    // Later, you can stop observing
    // 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');

Penso che tu voglia mettere il tuo codice in un inizializzatore piuttosto che in una head. Dai un’occhiata alla guida per sviluppatori e vedi come puoi mettere il tuo JS in file separati.

1 Mi Piace

Grazie mille per la risposta!

Penso che questo sia in parte il motivo per cui sono confuso, ho difficoltà a trovare riferimenti all’estensione di Discourse aggiungendo il mio JS.

Quando faccio una ricerca su DuckDuckGo per “Discourse developer guide”, il primo link che ottengo è un link al repository GitHub.

Il link successivo è alla “Discourse Advanced Developer Install Guide”. Questa guida serve a spiegare come impostare Rails per lo sviluppo, ma non contiene link su come installare JS personalizzato per quanto posso vedere. Sto cercando di evitare un processo di build complicato, che è ciò che ricordo dei miei giorni con Rails. Mi piacerebbe davvero sviluppare questo codice di estensione JS in isolamento, e poi inserire un tag script nel mio sito. Quindi, non voglio davvero dover configurare un ambiente Rails localmente per poterlo costruire; forse mi sfugge l’utilità di ciò? Ma mi piace molto poter semplicemente aggiornare un container Docker che utilizza un tema con alcuni tag <script>.

Il link successivo è una “Beginner’s guide to developing Discourse Themes” che riguarda lo sviluppo di temi, non ciò di cui ho bisogno, giusto?

Vedo link all’API di Discourse che ovviamente non è ciò che voglio.

Se cerco “discourse javascript initializer” vedo questo link di 5 anni fa: Execute JavaScript code from a plugin once after load Ma, questo sembra che stia collegandomi a Rails, e sento che dovrebbe esserci un modo più semplice, e anche questo thread sembra irrisolto?

Un altro link a “discourse javascript initializer” suggerisce di fare ciò che sto facendo per installare il JS, ma non ha suggerimenti su come assicurarsi che ogni volta che il contenuto della pagina cambia (sia tramite un aggiornamento completo della pagina o una richiesta XHR “alla Turbolink”): https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

È questa la discussione che dovrei rivedere? A versioned API for client side plugins

O, forse questo? A prima vista non capisco la sintassi (quelle annotazioni non sembrano JS, sono convenzioni di Rails?) quindi non sono sicuro se sia ciò di cui ho bisogno: Using Plugin Outlet Connectors from a Theme or Plugin

Sì, non è tutto banale.

Discourse è un’app EmberJS.

Idealmente, le estensioni JavaScript dovrebbero essere scritte nel framework EmberJS utilizzando un componente tema (o plugin), l’API JavaScript di Discourse dove appropriato e gli outlet dei plugin.

Il problema principale che incontrerai nell’utilizzo di script esterni ad hoc è farli attivare al momento giusto.

Per assicurarti di ciò, devi collegarli alle azioni in un componente (che può essere fatto attivare all’inserimento o all’aggiornamento).

1 Mi Piace

Questa è una buona conferma. Ma devo dire che sono davvero contento del mio codice di MutationObserver. Rileva correttamente quando il contenuto della pagina cambia e poi mi permette di eseguire il mio codice JS personalizzato. Funziona magnificamente e non ho dovuto imparare Ember, né modificare l’app RoR in alcun modo. Sono davvero contento di questa soluzione per ora. Grazie per tutti i commenti.

1 Mi Piace

Ho appena provato a usare un osservatore di eventi popstate. Se avesse funzionato, il codice sarebbe di 5 righe invece di 20. Tuttavia, fare clic non sembra attivare quell’evento. Se uso i pulsanti avanti o indietro, vedo l’evento. Non capisco abbastanza chiaramente popstate ma per ora mi attengo a un osservatore di mutazioni div.

Mi sono reso conto che stavo facendo una cosa sciocca qui. Sto modificando il tema e aggiungendo codice all’head. Se passo a un tema diverso, queste modifiche vengono perse. Il modo giusto è aggiungere il codice usando un plugin. Ho usato il template qui: GitHub - discourse/discourse-plugin-skeleton: Template for Discourse plugins. Poi, ho aggiunto codice JavaScript in questo modo:

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

Poi, ho eseguito ./launcher rebuild app e poi ho abilitato il plugin.
Infine, ho dovuto aggiungere la policy CSP nelle impostazioni per consentire il caricamento di tali script. Sono andato su admin->impostazioni->sicurezza, ho aggiunto files.extrastatic.dev a “content security policy script src” e ho cliccato su applica.

Non è vero.

Se non stai modificando l’API (ad esempio, usando JavaScript al 100%) non c’è bisogno di usare un Plugin, che sono più macchinosi da distribuire e sostituire.

Se tutto ciò che stai facendo è modificare o aggiungere del JavaScript, un Theme Component dovrebbe essere sufficiente.

2 Mi Piace

OK. Ma, quando cambio tema (o se permetto a un utente di scegliere il proprio tema), non mi trovo di fronte al problema di dovermi assicurare che ogni tema sia 1) modificabile in modo da poter aggiungere i nuovi tag head e, peggio ancora, 2) dover mantenere il mio codice su tutti i temi?

Quando ho iniziato a usare temi diversi, alcuni indicano che per modificare il tema è necessario modificare la repository di GitHub. Questo sembra davvero oneroso e inflessibile. Tuttavia, mantenere i miei script tag su ogni tema sembra molto soggetto a errori e un problema ancora più grande.

Cosa mi sfugge? Esiste un modo per risolvere questi problemi utilizzando solo i temi?

1 Mi Piace

In questo caso, lo renderesti un componente del tema e aggiungeresti quel componente a tutti i temi. (Ammetto che questa è una complessità aggiuntiva).

Se vuoi gestire il sito professionalmente, assicurarti che tutto il tuo codice sia su Github è davvero una buona idea.

Tuttavia, inizialmente, quando stai solo provando alcune idee, certo, puoi modificarlo “localmente” se ti piace.

Quando le modifiche al codice si saranno stabilizzate, dovresti probabilmente inserirle nel controllo di origine, ma sostengo che prima è meglio.

Un modo per combinare l’evoluzione rapida con GitHub è combinarlo con questo:

E distribuirlo su un componente del tema di test in un tema di test … ma ora stiamo davvero diventando stravaganti …

Il che ti consente di distribuire al volo, quindi puoi proteggere le tue modifiche in un repository git una volta che sei soddisfatto.

1 Mi Piace

Dai un’occhiata a GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes

Questo è ciò di cui hai bisogno 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 Mi Piace

Wow @RGJ sembra perfetto! Grazie!

2 Mi Piace

Ciao @RGJ, ho cercato di capire come fare e devo ammettere che mi sto confondendo. Sembra che l’API dei plugin sia cambiata un po’ nel corso degli anni e non sono sicuro di come ottenere quella attuale, o di vedere un esempio.

Il tuo codice sembra finire in una pagina HTML da qualche parte, dato che è racchiuso dai tag script. Io stavo usando un codice come questo, che è il file JS stesso.

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

Come modifico il mio plugin per usare il codice che hai fornito? Questo codice che hai suggerito va in un plugin, o lo metto in un tema, o lo aggiungo in altro modo alle pagine?

Ho provato a usare un codice come questo:

export default {
  name: 'publicdo',
    initialize() {
     withPluginApi('0.1', api => {
                api.onPageChange(() => {
                   console.log('Esegui il mio codice qui.');
                });
      });
    }
}

Ma questo fallisce con Uncaught (in promise) ReferenceError: withPluginApi is not defined, quindi chiaramente non è qualcosa che il JS caricato in generale riceve.

Devi semplicemente

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

3 Mi Piace

Dovresti davvero consultare le fonti collegate in Theme component (l’ecosistema è quasi interamente open-source - approfittane!).

Noterai che le importazioni sono spesso necessarie e comuni (così come l’uso di api.onPageChange e altre utili funzioni dell’API).

4 Mi Piace

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