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