Feedback zu "on-discourse" Javascript für die Einrichtung von benutzerdefiniertem JS für jede Seite?

Ich habe eine Svelte-App geschrieben, die sich auf jeder Seite selbst installieren muss. Ich habe herausgefunden, wie ich meine JS-Datei in den Head-Bereich einfügen und sie beim ersten Laden der Seite laden kann. Bei meinen Experimenten stellte ich jedoch fest, dass Discourse neue Inhalte über XHR abruft und bestimmte Abschnitte ersetzt, sodass meine App bei jedem Laden einer neuen Seite nicht neu initialisiert wurde.

Ich habe verschiedene Versuche unternommen, um benachrichtigt zu werden, wenn sich die Seite ändert, aber es scheint, dass Ember keine Hooks bereitstellt, und ich konnte keine benutzerdefinierten Ereignisse finden, auf die ich hören konnte.

Es scheint, dass eine Möglichkeit darin besteht, einen Mutationsbeobachter zum DOM hinzuzufügen und auf Änderungen zu achten. Ich habe festgestellt, dass das „#topic div“ dasjenige zu sein scheint, das neu geladen wird (und die Attribute sich ändern, sodass man nur auf Attributänderungen achten kann). Ich habe dort einen Mutationsbeobachter eingerichtet (Mutationsbeobachter sind der neuere, performante Weg, um DOM-Änderungen zu beobachten). Die Funktionsweise besteht darin, auf Änderungen zu achten und bei deren Auftreten einen Callback auszuführen, um meine Svelte-App für diese Seite neu zu laden.

Dieser Ansatz gefällt mir und ich hätte gerne Feedback dazu.

Eine Frage: Sollte ich stattdessen auf Änderungen an der URL achten? Ist das eine bessere Idee, einen Listener für popstate zu registrieren?

Um es zu verwenden, machen Sie etwas Ähnliches in Ihrem Theme/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">

Dann können Sie innerhalb Ihrer Bibliothek on-discourse wie folgt aufrufen:

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

// Dies ist der Svelte-Installationscode
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);  
})();

Im Grunde rufen Sie einfach window.onDiscourse(callback) mit Ihrem Callback auf (und Sie können ihn mehrmals ausführen, um mehrere Callbacks zu installieren), und wenn dann eine Mutation auftritt, wird dieser Callback ausgeführt, um Ihre App zu initialisieren.

Hier ist der vollständige Code für on-discourse.js. (Bearbeitet: Ich habe dies aktualisiert, um #topic zu verwenden, was eine gute Sache zu beobachten zu sein scheint, da sich die Attribute beim Laden der Seite ändern und die Mutation dann nur auf Attributänderungen achten muss, anstatt den gesamten DOM-Baum nach #main-outlet zu durchsuchen)

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

Ich glaube, Sie möchten Ihren Code eher in einen Initialisierer als in einen Kopf einfügen. Sehen Sie sich den Entwicklerleitfaden an, um zu erfahren, wie Sie Ihr JS in separate Dateien einfügen können.

1 „Gefällt mir“

Vielen Dank für die Antwort!

Ich glaube, das ist teilweise der Grund für meine Verwirrung. Ich kann keine Referenzen finden, wie man Discourse durch Hinzufügen meines eigenen JS erweitert.

Wenn ich eine DuckDuckGo-Suche nach “Discourse developer guide” durchführe, ist der erste Link ein Link zum Github-Repo.

Der nächste Link führt zur “Discourse Advanced Developer Install Guide”. Diese Anleitung soll erklären, wie man Rails für die Entwicklung einrichtet, enthält aber keine Links zur Installation von benutzerdefiniertem JS, soweit ich das beurteilen kann. Ich möchte einen komplizierten Build-Prozess vermeiden, an den ich mich aus meinen Rails-Tagen erinnere. Ich würde diesen JS-Erweiterungscode wirklich gerne isoliert entwickeln und dann ein Skript-Tag in meine Seite einfügen. Daher möchte ich keine Rails-Umgebung lokal einrichten, um ihn zu erstellen. Vielleicht übersehe ich den Nutzen davon? Aber ich mag es wirklich, einfach einen Docker-Container zu aktualisieren, der ein Theme mit ein paar <script>-Tags verwendet.

Der nächste Link ist ein “Beginner’s guide to developing Discourse Themes”, der sich mit der Entwicklung von Themes beschäftigt, nicht damit, was ich brauche, oder?

Ich sehe Links zur Discourse API, was offensichtlich nicht das ist, was ich will.

Wenn ich nach “discourse javascript initializer” suche, sehe ich diesen Link von vor 5 Jahren: Execute JavaScript code from a plugin once after load Aber das scheint so, als würde ich mich in Rails einklinken, und ich habe das Gefühl, es sollte einen einfacheren Weg geben, und dieser Thread scheint auch ungelöst zu sein?

Ein weiterer Link zu “discourse javascript initializer” schlägt vor, das zu tun, was ich tue, um das JS zu installieren, hat aber keine Vorschläge, wie sichergestellt werden kann, dass jederzeit, wenn sich der Seiteninhalt ändert (entweder durch eine vollständige Seitenaktualisierung oder eine XHR “turbolinks”-ähnliche Anfrage): https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

Sollte ich diese Diskussion durchgehen? A versioned API for client side plugins

Oder vielleicht diese? Auf den ersten Blick verstehe ich die Syntax nicht (diese Annotationen sehen nicht wie JS aus, sind das Rails-Konventionen?), daher bin ich mir nicht sicher, ob das das ist, was ich brauche: Using Plugin Outlet Connectors from a Theme or Plugin

Das ist nicht ganz trivial.

Discourse ist eine EmberJS-App.

Idealerweise sollten JavaScript-Erweiterungen im EmberJS-Framework unter Verwendung einer Theme Component (oder eines Plugins), der Discourse JavaScript API, wo angebracht, und Plugin-Outlets geschrieben werden.

Das Hauptproblem, auf das Sie bei der Verwendung von Ad-hoc-externen Skripten stoßen werden, ist, sie zur richtigen Zeit auszuführen.

Um dies sicherzustellen, müssen Sie sie mit Aktionen in einer Component verknüpfen (die bei Einfügung oder Aktualisierung ausgelöst werden kann).

1 „Gefällt mir“

Das ist eine gute Bestätigung. Aber ich muss sagen, ich bin wirklich zufrieden mit meinem Mutation Observer Code. Er erkennt, wenn sich der Seiteninhalt korrekt ändert, und erlaubt mir dann, meinen benutzerdefinierten JS-Code auszuführen. Er funktioniert einwandfrei, und ich musste weder Ember lernen noch die RoR-App in irgendeiner Weise modifizieren. Ich bin vorerst wirklich zufrieden mit dieser Lösung. Danke für all die Kommentare.

1 „Gefällt mir“

Ich habe gerade versucht, einen Popstate-Ereignisbeobachter zu verwenden. Wenn das funktioniert hätte, wäre der Code 5 Zeilen statt 20. Beim Herumklicken scheint dieses Ereignis jedoch nicht ausgelöst zu werden. Wenn ich die Vorwärts- oder Rückwärtstasten verwende, sehe ich das Ereignis. Ich verstehe Popstate nicht offensichtlich genug, aber vorerst bleibe ich bei einem Div-Mutationsbeobachter.

Mir ist aufgefallen, dass ich hier eine dumme Sache gemacht habe. Ich ändere das Theme und füge Code zum Head hinzu. Wenn ich zu einem anderen Theme wechsle, gehen diese Änderungen verloren. Der richtige Weg ist, den Code über ein Plugin hinzuzufügen. Ich habe hier die Vorlage verwendet: GitHub - discourse/discourse-plugin-skeleton: Template for Discourse plugins. Dann habe ich JavaScript-Code wie folgt hinzugefügt:

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

Dann habe ich ./launcher rebuild app ausgeführt und das Plugin aktiviert.
Zuletzt musste ich die CSP-Richtlinie in den Einstellungen hinzufügen, um das Laden dieser Skripte zu ermöglichen. Ich ging zu Admin-Einstellungen-Sicherheit, fügte files.extrastatic.dev zu “content security policy script src” hinzu und klickte auf Anwenden.

Nicht wahr.

Wenn Sie die API nicht ändern (z. B. 100 % JavaScript verwenden), müssen Sie kein Plugin verwenden, das umständlicher zu implementieren und auszutauschen ist.

Wenn Sie nur JavaScript ändern oder hinzufügen, sollte eine Theme Component ausreichen.

2 „Gefällt mir“

OK. Aber wenn ich Themes wechsle (oder einem Benutzer erlaube, sein eigenes Theme zu wählen), stehe ich dann nicht vor dem Problem, dass ich sicherstellen muss, dass jedes Theme 1) editierbar ist, damit ich die neuen Head-Tags hinzufügen kann, und noch schlimmer, 2) meinen Code über alle Themes hinweg pflegen muss?

Als ich anfing, verschiedene Themes zu verwenden, gaben einige an, dass man das GitHub-Repository bearbeiten muss, um das Theme zu bearbeiten. Das scheint wirklich mühsam und unflexibel zu sein. Aber die Pflege meiner Skript-Tags über jedes Theme hinweg scheint sehr fehleranfällig und ein noch größeres Problem zu sein.

Was übersehe ich? Gibt es eine Möglichkeit, diese Probleme nur durch die Verwendung von Themes zu lösen?

1 „Gefällt mir“

In diesem Fall würden Sie es zu einer Theme-Komponente machen und diese Komponente zu allen Themes hinzufügen. (Ich gebe zu, das ist eine zusätzliche Komplexität).

Wenn Sie die Website professionell betreiben möchten, ist es in der Tat eine sehr gute Idee, sicherzustellen, dass Ihr gesamter Code auf Github liegt.

Wenn Sie jedoch gerade erst einige Ideen ausprobieren, können Sie damit natürlich “lokal” herumspielen, wenn Sie möchten.

Wenn sich die Codeänderungen beruhigt haben, sollten Sie sie wahrscheinlich in die Quellcodeverwaltung aufnehmen, aber ich würde argumentieren, je früher, desto besser.

Eine Möglichkeit, schnelle Entwicklung mit GitHub zu kombinieren, ist die Kombination mit Folgendem:

Und stellen Sie es in einer Test-Theme-Komponente in einem Test-Theme bereit … aber jetzt werden wir wirklich verrückt …

Dies ermöglicht es Ihnen, im Handumdrehen bereitzustellen, und Sie können Ihre Änderungen dann in einem Git-Repository sichern, sobald Sie zufrieden sind.

1 „Gefällt mir“

Schauen Sie sich GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes an

Das ist es, was Sie brauchen 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 „Gefällt mir“

Wow @RGJ, das sieht absolut perfekt aus! Danke!

2 „Gefällt mir“

Hallo @RGJ, ich habe versucht herauszufinden, wie das geht, und muss zugeben, dass ich verwirrt bin. Es scheint, dass sich die Plugin-API im Laufe der Jahre ein wenig geändert hat und ich bin mir nicht sicher, wie ich das Aktuelle bekomme oder ein Beispiel sehe.\n\nIhr Code scheint irgendwo in eine HTML-Seite zu gelangen, da er von den Skript-Tags umschlossen ist. Ich habe Code wie diesen verwendet, der die JS-Datei selbst ist.\n\nhttps://extrastatic.dev/publicdo/publicdo-discourse-plugin/-/blob/main/assets/javascripts/discourse/initializers/kanji.js?ref_type=heads\n\nWie kann ich mein Plugin ändern, um den von Ihnen bereitgestellten Code zu verwenden? Ist dieser Code, den Sie vorgeschlagen haben, etwas, das in ein Plugin gehört, oder füge ich es in ein Theme ein oder füge es anderweitig zu den Seiten hinzu?\n\nIch habe versucht, Code wie diesen zu verwenden:\n\n\nexport default {\n name: 'publicdo',\n initialize() {\n withPluginApi('0.1', api =\u003e {\n api.onPageChange(() =\u003e {\n console.log('Run my code here.');\n });\n });\n }\n}\n\n\nAber das schlägt mit Uncaught (in promise) ReferenceError: withPluginApi is not defined fehl, also ist es eindeutig nichts, das allgemein geladenes JS empfängt.

Sie müssen einfach
import { withPluginApi } from "discourse/lib/plugin-api";

3 „Gefällt mir“

Sie sollten sich wirklich die Quellen ansehen, die in Theme component verlinkt sind (das Ökosystem ist fast vollständig Open-Source – bedienen Sie sich!).

Sie werden feststellen, dass Importe oft notwendig und üblich sind (ebenso wie die Verwendung von api.onPageChange und anderen nützlichen API-Funktionen).

4 „Gefällt mir“

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