Se você estiver visitando /tags/category-slug/tag-name e clicar no botão Novo Tópico, o compositor terá a tag pré-definida, conforme descrito aqui:
Isso é incrível. Mas agora eu (e pelo menos mais uma pessoa) queremos poder definir esse comportamento com uma tag padrão ao visitar /c/cat-slug/cat-id. Parece que um componente de tema deveria ser capaz de direcionar esse botão e modificá-lo ou ocultá-lo e adicionar um novo botão (há uma saída de plugin bem ali, que não consigo encontrar agora, mas vi há um minuto).
É suposto funcionar apenas em uma categoria específica, ou você precisaria que ele suportasse uma “tag padrão” para várias categorias, onde essa tag seja diferente para cada uma delas?
Imagino que eu poderia criar uma configuração para definir uma tag padrão para algumas categorias. Provavelmente consigo fazer isso, mas não sei onde ou como alterar o botão “Criar tópico” para que ele inclua essa tag padrão.
Os ids em elementos HTML devem ser únicos, ou seja… nenhum dois elementos na mesma visualização podem compartilhar o mesmo atributo id HTML. Portanto, isso é suficiente para começarmos.
Se eu pesquisar "create-topic" no Github, é isso que vejo…
Há apenas um resultado lá, então temos sorte. Se houver mais resultados, há coisas que você pode fazer para refinar ainda mais a lista, mas isso está fora do escopo deste tópico.
Então, vamos verificar esse arquivo.
Você verá então que a ação que o botão possui é definida assim:
action=action
Bem… isso não é muito útil… Então, o que agora?
Quando você vê action=action, significa que a ação está sendo passada para o componente a partir de um template pai.
Vamos tentar ver quais templates têm esse componente. Então, vamos ao Github e pesquisamos pelo nome do componente como ele seria usado em um template. Para este exemplo, usaríamos algo assim: "{{create-topic-button"
Note que eu adicionei apenas {{NOME_DO_COMPONENTE e pulei o resto. Não sabemos os outros argumentos passados a ele, então queremos uma pesquisa genérica.
Obtemos dois resultados… um deles está no plugin styleguide, então simplesmente ignoramos. O outro está no núcleo (core). Então, vamos ver como isso se parece:
Ahh… estamos chegando mais perto. Agora você vê que a ação para o botão é:
action=(action "clickCreateTopicButton")
Agora precisamos descobrir o que essa ação faz. Então, pesquisamos pelo nome da ação. Depois, filtramos para arquivos .js porque agora queremos ver a definição dessa ação no arquivo js do componente.
Novamente, obtemos apenas um resultado, então vamos olhar para ele.
Então, parece que a ação faz uma de duas coisas. Se a categoria for somente leitura e o usuário já não tiver um rascunho, ela mostra um alerta. Caso contrário, ela chama um método createTopic().
Estamos interessados neste último, então vamos olhar para ele.
Se você pesquisar por createTopic() naquele arquivo (pesquisa inline, não no Github)… você notará que há apenas uma referência para ele. O que houve? Como este componente está chamando um método que não está definido?
Bem, a resposta está mais acima no arquivo.
O que isso significa?
Eu não quero gastar muito tempo aqui, mas o Ember usa Classes. Pense em classes como pacotes de código reutilizáveis. Toda a linha destacada acima significa apenas:
Pegue o pacote Component do Ember, adicione o pacote FilterModeMixin a ele e deixe-me adicionar mais alguns métodos, ou sobrescrever alguns dos existentes, ao resultado para criar um novo componente Ember para meu aplicativo.
Então, agora vamos voltar para a ação que estamos tentando rastrear.
Ele chama this.createTopic(). Isso não é um método padrão de componente Ember. É um método personalizado do Discourse, então deve vir de FilterModeMixin. O que é FilterModeMixin? Bem… ele é definido no topo do arquivo.
import FilterModeMixin from "discourse/mixins/filter-mode";
Pare por um segundo e faça uma pesquisa inline por createTopic() naquele arquivo. Eu falo sério. Pare de ler e faça isso. Vou esperar… não trapaceie… estou de olho em você .
OK. Você pesquisou e não houve resultados. E agora?
O que descrevi acima é apenas um método para passar coisas para baixo. Se você não encontrar o que está procurando. Dê um passo para trás e tente uma abordagem diferente.
Então, vamos recapitular… onde estamos agora? Antes de ficarmos presos, estávamos olhando para o arquivo JS do componente d-navigation. Vamos olhar para seu template.
Novamente, usamos "{{NOME_DO_COMPONENTE" e pesquisamos.
Isso importa? Talvez. Isso importa para este caso? Não. Estamos apenas tentando descobrir de onde vem createTopic() ou o que é. Então, vamos apenas seguir com o primeiro resultado.
Sério, vamos falar sobre ações de rota. O que são elas? Bem. Elas são ações… de rota? Como em ações definidas na rota. Por que elas são legais? Porque as rotas no Discourse podem ser aninhadas.
Veja assim:
- rota-1
- rota-1-1
- rota-1-2
- rota-1-3
Se eu tiver um componente compartilhado que preciso usar nas rotas 111, 112 e 113 com parâmetros diferentes, não seria mais fácil se eu apenas definisse usar o mesmo componente em todas elas e passasse a mesma ação? Então, modificá-lo para cada rota se necessário?
É isso que as route-actions fazem.
OK, vamos voltar à pergunta. Estávamos olhando para:
createTopic=(route-action "createTopic")
no componente navigation/default.
Agora, só precisamos descobrir qual é a rota para verificar o que essa ação de rota faz.
Você quer modificar o comportamento do botão novo tópico nas páginas /c/cat-slug/cat-id. Então, vamos visitar uma dessas páginas. Por exemplo: http://localhost:4200/c/meta/6
Qual é esta rota? A menos que você esteja realmente familiarizado com o Discourse, você não seria capaz de dizer. Então, o que agora?
É aqui que a extensão Ember para seu navegador se torna útil.
Instale-a aqui se você ainda não a tiver. Vou esperar.
(o link é um repositório do Github, mas a descrição tem os links da extensão para diferentes navegadores)
OK, agora que você a instalou, visite a página novamente /c/cat-slug/cat-id e olhe para a página da extensão.
Quando carregar, clique em Rotas e, em seguida, alterne “Apenas rota atual”.
Parece que está fazendo uma de duas coisas. Se o usuário tiver um rascunho, ele o abre. Se não, chama openComposer() com um parâmetro. Qual é o próximo passo? Bem, você já deve saber a resposta agora. Precisamos descobrir de onde vem openComposer() ou o que ele faz.
Então, pesquisamos o arquivo por openComposer() e… é claro que não obtemos resultados. Não há nenhum método nessa rota chamado openComposer().
E agora? Lembre-se da parte sobre Classes Ember? Vamos tentar isso.
Temos isso no topo do arquivo de rota.
Isso significa que esta rota herda todos os métodos do pacote DiscourseRoute bem como os definidos no pacote OpenComposer.
O openComposer é mais provável o que queremos, então vamos olhar para ele. Antes de fazermos isso, porém… precisamos ver como openComposer é definido naquele arquivo.
import OpenComposer from "discourse/mixins/open-composer";
Olhe para a URL. Não é um componente Ember. Não é uma rota; não é um modelo. É um mixin. O que diabos é um mixin? A resposta muito, muito curta… é um pacote de funções reutilizáveis.
Você define esses no seu mixin.
add(number) {
return number + 1
}
substract(number) {
return number - 1
}
então adicione o mixin ao seu componente Ember, então você pode fazer algo assim:
// valor inicial é 1
myMethod () {
this.add(value) // retorna 2
this.substract(value) // retorna 0
}
Então, como isso se relaciona com o que estamos tentando fazer?
Bem, open-composer aqui.
import OpenComposer from "discourse/mixins/open-composer";
é um mixin. Um dos métodos nesse mixin é OpenComposer().
Está tudo bem se você se sentir confuso com isso. Eles compartilham o mesmo nome - exceto que um começa com uma letra maiúscula, o que indica que é uma Classe.
Eles significam coisas diferentes.
Para entender isso, você precisaria entender que o nome que você dá aos seus módulos importados não importa (neste caso particular), desde que sejam exportados como “default”.
Explicar isso está um pouco além do escopo deste tópico. Tudo o que você precisa saber é que isso:
id HTML do botão Novo tópico < ação do botão Novo tópico < ação do componente d-navigation < ação da rota discovery < mixin OpenComposer < método openComposer()
Então… este é o método que eventualmente é chamado quando você clica no botão + Novo Tópico naquela rota.
Estabelecemos como você pode descobrir a ação daquele botão na /c/cat-slug/cat-id, mas parece diferente do que acontece quando você visita /tags/category-slug/tag-name, que é o que você quer fazer.
Então, qual é o próximo passo? Vamos ver o que aquela rota faz para lidar com a ação createTopic().
Bem… você notará que ela lida com a ação de maneira diferente.
createTopic() {
if (this.get("currentUser.has_topic_draft")) {
this.openTopicDraft();
} else {
const controller = this.controllerFor("tag.show");
const composerController = this.controllerFor("composer");
composerController
.open({
categoryId: controller.get("category.id"),
action: Composer.CREATE_TOPIC,
draftKey: Composer.NEW_TOPIC_KEY
})
.then(() => {
// Pré-preencher o campo de entrada de tags
if (composerController.canEditTags && controller.get("model.id")) {
const composerModel = this.controllerFor("composer").get("model");
composerModel.set(
"tags",
[
controller.get("model.id"),
...makeArray(controller.additionalTags)
].filter(Boolean)
);
}
});
}
}
Essa diferença é basicamente o que você está pedindo aqui.
Então, tudo o que você precisa fazer é… modificar a ação createTopic() na rota discovery para fazê-la funcionar como na rota tag-show. Então, como você faz isso?
Lembre-se de como falamos sobre o Ember usar Classes? Sim, vamos ter que voltar a isso novamente.
A API de plugins permite que você modifique classes Ember através deste método.
Então, o que estamos tentando modificar aqui? A rota discovery… porque… lembre-se, é lá que createTopic() é definido quando você está em uma página como /c/cat-slug/cat-id.
O que isso faz? Quebra o botão + Novo Tópico; no entanto, nos diz que estamos na direção certa. Se você tentar adicionar o trecho acima, notará que clicar no botão não abre mais o compositor. Em vez disso, ele apenas imprime uma mensagem no console. Isso é uma coisa boa porque significa que alvoamos a Classe certa e a ação certa - route:discovery e createTopic().
Então, o que vem a seguir? Bem, lembre-se de que o botão em /tags/category-slug/tag-name faz exatamente o que queremos. Então, vamos copiar o código daquela rota - e adicionar as importações necessárias.
Isso vai funcionar? Não, mas estamos um passo perto. Por que não funciona? Porque as tags que ele adiciona quando o compositor abre não estão definidas. Por quê? Porque elas são carregadas do controlador tag.show - o que não é o que queremos. Vamos modificar o código para fazê-lo funcionar com a rota em que estamos.
Antes de fazermos isso, porém, precisamos de algum tipo de índice para nossas tags padrão desejadas. Vamos com um novo objeto assim:
// category-slug: [ARRAY_DE_TAGS_PADRAO]
const defaultTagIndex = {
// slug de palavra única
meta: ["a", "b", "c"],
core: ["g", "h"],
// slug com um traço
["general-chat"]: ["d", "e", "f"]
};
Isso basicamente significa que se o compositor for aberto na página da categoria meta, adicione as tags “a, b, c”.
Se o compositor for aberto na página da categoria core, adicione as tags “g, h” e assim por diante.
Agora que temos isso, podemos modificar a ação para fazê-la parecer assim.
Envolve tudo em um bloco try…catch. Se o código falhar, executamos this._super(...arguments).
Se você estiver familiarizado com o Ember, saberá o que this._super(...arguments) faz. Se não, aqui está uma explicação simples. Estamos sobrescrevendo o createTopic(), então se as sobrescrições falharem devido a um erro - talvez o núcleo tenha sido atualizado - então retorne ao método no núcleo conforme definido aqui.
Se o usuário tiver um rascunho de novo tópico, simplesmente retornamos a this._super(...arguments) e deixamos o núcleo fazer o seu trabalho.
Isso deve ser suficiente. Tudo o que você precisa adicionar agora é uma maneira de criar o índice de tags padrão através das configurações do tema.