Estive a escrever uma aplicação Svelte que precisa de se instalar em cada página. Descobri como adicionar o meu ficheiro JS à secção head e fazê-lo carregar na primeira vez que a página é carregada. No entanto, ao experimentar, percebi que o discourse carrega novo conteúdo via XHR e substitui certas secções, e assim a minha aplicação não era reinicializada quando uma nova página era carregada.
Experimentei várias tentativas para ser notificado quando a página muda, mas parece que o Ember não fornece os ganchos, e não consegui encontrar quaisquer eventos personalizados para os quais pudesse escutar.
Parece que uma forma é adicionar um observador de mutações ao DOM e observar as mudanças. Descobri que o "#topic div parece ser aquele que é recarregado (e os atributos mudam para que possa apenas observar as mudanças de atributos). Configurei um observador de mutações lá (observadores de mutações são a nova e performática forma de observar mudanças no DOM). A forma como funciona é observar as mudanças e, quando ocorrem, executar uma função de retorno para recarregar a minha aplicação Svelte para essa página.
Estou a gostar desta abordagem e gostaria de feedback.
Uma pergunta: devo antes observar as mudanças no URL? É uma ideia melhor registar um ouvinte para popstate?
Para o usar, faça algo como isto no seu 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">
Depois, dentro da sua biblioteca, pode chamar on-discourse assim:
function log(...msg) {
console.log('svelte', ...msg);
}
// Este é o código de instalação 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 em caso de alterações
window.onDiscourse && window.onDiscourse( setup );
// Carregar a aplicação na primeira carga da página
window.addEventListener('load', () => {
setup();
});
log('Finished custom Svelte app);
})();
Basicamente, basta chamar window.onDiscourse(callback) com a sua função de retorno (e pode executá-la várias vezes para instalar várias funções de retorno), e depois, quando ocorre uma mutação, essa função de retorno é executada para inicializar a sua aplicação.
Aqui está o código completo para on-discourse.js. (edit: atualizei isto para usar #topic, que parece ser uma boa coisa para observar, porque os atributos mudam quando a página carrega, e então a mutação só precisa de observar as mudanças de atributos, em vez de procurar em toda a árvore DOM por #main-outlet)
let mutationObservers = [];
function log(...msg) {
console.log("on-discourse", ...msg);
}
function observeMainOutlet() {
log('Observing main outlet');
// Seleciona o nó que será observado para mutações
const targetNode = document.getElementById("topic");
if (targetNode) {
// Opções para o observador (quais mutações observar)
const config = { attributes: true };
// cria uma instância de observador para redefinir quando childList muda
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 );
}
});
}
});
});
// Começa a observar o nó alvo para as mutações configuradas
observer.observe(targetNode, config);
// Mais tarde, pode parar 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');