Feedback sobre javascript "on-discourse" para configurar JS personalizado para cada página?

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

Acho que você quer colocar seu código em um inicializador em vez de em um cabeçalho. Veja o guia do desenvolvedor e veja como você pode colocar seu JS em arquivos separados.

1 curtida

Muito obrigado pela resposta!

Acho que isso é parcialmente o motivo da minha confusão, estou tendo dificuldade em encontrar referências para estender o Discourse adicionando meu próprio JS.

Quando faço uma busca no DuckDuckGo por “Discourse developer guide”, o primeiro link que recebo é um link para o repositório do github.

O próximo link é para o “Discourse Advanced Developer Install Guide”. Este guia é para te dizer como configurar o Rails para desenvolvimento, mas não tem nenhum link sobre como instalar JS personalizado, tanto quanto posso dizer. Estou tentando evitar um processo de build complicado, que é o que me lembro dos meus dias de Rails. Eu realmente gostaria de desenvolver este código de extensão JS em isolamento e, em seguida, colocar uma tag de script no meu site. Então, eu realmente não quero ter que configurar um ambiente Rails localmente para poder construí-lo; talvez eu esteja perdendo a utilidade disso? Mas, eu realmente gosto de poder apenas atualizar um contêiner docker que usa um tema com algumas tags <script>.

O próximo link é um “Guia para iniciantes no desenvolvimento de temas Discourse”, que é sobre o desenvolvimento de temas, não o que eu preciso, certo?

Vejo links para a API do Discourse, que obviamente não é o que eu quero.

Se eu pesquisar por “discourse javascript initializer”, vejo este link de 5 anos atrás: Execute JavaScript code from a plugin once after load Mas, isso parece que estou me conectando ao Rails, e sinto que deveria haver uma maneira mais simples, e este tópico também parece não resolvido?

Outro link para “discourse javascript initializer” sugere fazer o que estou fazendo para instalar o JS, mas não tem sugestões sobre como garantir que sempre que o conteúdo da página mudar (seja através de uma atualização completa da página ou solicitação XHR “turbolinks”-ish): https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

É esta a discussão que devo rever? A versioned API for client side plugins

Ou, talvez este? À primeira vista, não entendo a sintaxe (essas anotações não parecem JS para mim, são convenções do Rails?) então não tenho certeza se é isso que preciso: Using Plugin Outlet Connectors from a Theme or Plugin

Sim, não é nada trivial.

O Discourse é um aplicativo EmberJS.

Idealmente, as extensões JavaScript devem ser escritas no framework EmberJS usando um Componente de Tema (ou Plugin), a API JavaScript do Discourse, quando apropriado, e os plugin outlets.

O principal problema que você encontrará ao usar scripts externos ad hoc é fazê-los disparar no momento certo.

Para garantir isso, você precisa vinculá-los a ações em um Componente (que pode ser feito para disparar na inserção ou atualização).

1 curtida

Isso é uma boa confirmação. Mas, tenho que dizer, estou muito feliz com meu código de observador de mutação. Ele detecta quando o conteúdo da página muda corretamente e, em seguida, permite que eu execute meu código JS personalizado. Está funcionando perfeitamente e não precisei aprender Ember, nem modificar o aplicativo RoR de forma alguma. Estou muito feliz com esta solução por enquanto. Obrigado por todos os comentários.

1 curtida

Acabei de tentar usar um observador de eventos popstate. Se tivesse funcionado, o código teria 5 linhas em vez de 20. No entanto, clicar por aí não parece acionar esse evento. Se eu usar os botões de avançar ou voltar, eu vejo o evento. Eu não entendo popstate o suficiente, mas por enquanto estou mantendo um observador de mutação de div.

Percebi que estava fazendo uma coisa tola aqui. Estou modificando o tema e adicionando código ao cabeçalho. Se eu mudar para um tema diferente, essas alterações serão perdidas. A maneira correta é adicionar o código usando um plugin. Usei o template aqui: GitHub - discourse/discourse-plugin-skeleton: Template for Discourse plugins. Então, adicionei código JavaScript assim:

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

Então, executei ./launcher rebuild app e depois ativei o plugin.
Por último, precisei adicionar a política CSP nas configurações para permitir o carregamento desses scripts. Fui em admin-settings-security e adicionei files.extrastatic.dev a “content security policy script src” e cliquei em aplicar.

Não é verdade.

Se você não está modificando a API (por exemplo, usando 100% de JavaScript), não há necessidade de usar um Plugin, que é mais complicado de implantar e substituir.

Se tudo o que você está fazendo é modificar ou adicionar algum JavaScript, um Componente de Tema deve ser suficiente.

2 curtidas

OK. Mas, quando troco de temas (ou se permito que um usuário escolha seu próprio tema), não enfrento o problema de precisar garantir que cada tema seja 1) editável para que eu possa adicionar as novas tags de cabeçalho e, pior ainda, 2) preciso manter meu código em todos os temas?

Quando comecei a usar temas diferentes, alguns indicam que para editar o tema você precisa modificar o repositório do github. Isso parece muito oneroso e inflexível. Mas, manter minhas tags de script em cada tema parece muito propenso a erros e um problema ainda maior.

O que estou perdendo? Existe uma maneira de resolver esses problemas apenas usando temas?

1 curtida

Neste caso, você o tornaria um Componente de Tema e adicionaria esse Componente a todos os Temas. (Admito que isso é uma complexidade adicional).

Se você quiser executar o site profissionalmente, garantir que todo o seu código esteja no Github é uma ótima ideia mesmo.

No entanto, inicialmente, quando você está apenas experimentando algumas ideias, com certeza, você pode mexer nele “localmente”, se assim desejar.

Quando as alterações de código se estabilizarem, você provavelmente deve colocá-lo em controle de versão, mas eu argumentaria que quanto antes, melhor.

Uma maneira de combinar evolução rápida com o GitHub é combiná-lo com isto:

E implantar em um Componente de Tema de teste em um Tema de teste … mas agora estamos realmente entrando no lado divertido …

O que permite que você implante rapidamente, então você pode proteger suas alterações em um repositório git quando estiver satisfeito.

1 curtida

Dê uma olhada em GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes

É isso que você precisa 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 curtidas

Uau @RGJ, isso parece perfeito! Obrigado!

2 curtidas

Olá @RGJ, tenho tentado descobrir como fazer isso e tenho que admitir que estou ficando confuso. Parece que a API de plugins mudou um pouco ao longo dos anos e não tenho certeza de como obter o que está atualizado ou ver um exemplo.

Seu código parece entrar em uma página HTML em algum lugar, pois está envolvido pelas tags de script. Eu estava usando um código como este, que é o próprio arquivo JS.

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

Como modifico meu plugin para usar o código que você forneceu? Este código que você sugeriu é algo que vai em um plugin, ou eu o coloco em um tema, ou de outra forma o adiciono às páginas?

Tentei usar um código como este:

export default {
  name: 'publicdo',
    initialize() {
     withPluginApi('0.1', api => {
                api.onPageChange(() => {
                   console.log('Executar meu código aqui.');
                });
      });
    }
}

Mas isso falha com Uncaught (in promise) ReferenceError: withPluginApi is not defined, então claramente não é algo que o JS carregado em geral recebe.

Você deve simplesmente

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

3 curtidas

Você deveria realmente navegar pelas fontes vinculadas em Theme component (o ecossistema é quase todo de código aberto - aproveite!).

Você notará que as importações são frequentemente necessárias e comuns (assim como o uso de api.onPageChange e outras funções úteis da API).

4 curtidas

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