Se você deseja adicionar conteúdo a uma página específica, sua melhor opção é um plugin-outlet. Em resumo, plugin-outlets são espaços reservados nos modelos (templates) do Discourse que você pode usar para inserir novo conteúdo.
O primeiro passo é verificar se existe um plugin-outlet na página que você deseja atingir. Há um componente de tema que você pode instalar para ajudar nisso.
(deprecated) Plugin outlet locations theme component
Depois de instalar esse componente, ative-o, vá até a página desejada e verifique o que está disponível para trabalhar. No seu caso, um plugin-outlet existe (destacado em verde)
Portanto, o que queremos é above-user-profile.
Suponha que ele não existisse… o que fazer? Nesse caso, a melhor opção é pedir que ele seja adicionado ou enviar um PR (Pull Request) para incluí-lo no núcleo (core). Na maioria das vezes, ele será aceito se seu caso de uso fizer sentido.
De qualquer forma, como já disse, ele já existe neste caso. Então, vamos ver como você pode adicionar markup a ele. Você não precisará mais do componente mencionado acima para o restante deste guia, então pode desativá-lo agora, já que você já tem o nome do plugin-outlet.
Tudo o que você precisa fazer é adicionar algo assim na aba “header” do seu tema.
<script type="text/x-handlebars" data-template-name="/connectors/NOME_DO_OUTLET/NOME_DA_CUSTOMIZACAO">
Seu markup vai aqui...
</script>
Você precisa alterar NOME_DO_OUTLET para o nome do outlet que deseja atingir. Em seguida, altere NOME_DA_CUSTOMIZACAO para o nome que deseja dar a essa personalização. O nome pode ser qualquer coisa, mas tente ser descritivo se possível. É uma boa prática. Assim, chegamos a isso:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
Seu markup vai aqui... como
<h1>Olá Mundo!!</h1>
</script>
Vamos tentar isso e ver o que acontece… lembre-se, o trecho acima vai na aba common > header do seu tema.
e…
Até agora tudo bem, mas vamos aprofundar.
Você não quer que seus vídeos apareçam em todos os perfis; deseja que eles sejam exibidos apenas sob certa condição. Então, como fazer isso? Bem, você precisará de duas coisas: alguns dados para consumir e um pouco de JavaScript.
Vamos encontrar os dados. Lembra quando eu disse que plugin-outlets são espaços reservados? Bem, qual é o ponto de tê-los sem contexto? É por isso que o Discourse passa as partes relevantes do contexto para cada plugin-outlet… mas, primeiro, vamos dar um passo para trás. Quando você adiciona isso
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
Seu markup vai aqui, como...
<h1>Olá Mundo!!</h1>
</script>
Parece HTML — e as tags script são —, mas o que está dentro delas é tratado como código Handlebars.
Isso significa que você pode fazer algo assim em vez disso:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
{{log this}}
</script>
e verificar o console do navegador. Você verá isso sempre que o outlet for renderizado; ou seja, quando estiver em uma página de usuário.
Agora, alguma dessas informações é útil? Sim… mas não no momento. Voltaremos a isso. Vamos dar outro passo para trás e ver como o Discourse passa o contexto para o outlet. Se você pesquisar pelo nome do outlet no GitHub — ou localmente —, obterá isso:
Repository search results · GitHub
Vamos abrir esse arquivo. A primeira coisa que você vê é esta linha:
Olhe para a última parte dessa linha:
args=(hash model=model)
Você verá que o Discourse passa model como um argumento para o outlet. Para todos os efeitos práticos e para manter as coisas simples, model = data.
Portanto, um dos argumentos do nosso outlet é model, e é lá que estarão os dados que queremos. Então, vamos voltar ao nosso trecho:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
{{log this}}
</script>
e vamos alterá-lo para isso:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
- {{log this}}
+ {{log args}}
</script>
Agora obtemos isso no console:
Você pode navegar por esses dados e ver se eles contêm o que você precisa. Deveriam, já que contêm todos os dados sobre o usuário usados em outros elementos daquela página. É o “modelo” (model) da página do usuário para aquele usuário específico.
Uma das propriedades disponíveis lá é… rolagem de tambor
… os grupos aos quais o usuário pertence.
Então, se você fizer:
{{log args.model.groups}}
você obterá todos os grupos aos quais o usuário pertence no console.
Certo, agora temos os dados de que precisamos, então a única coisa que resta é adicionar alguma condição(s) baseada neles.
Você pode ser tentado a pensar que podemos fazer isso no mesmo trecho, mas, infelizmente, não podemos. Handlebars é uma linguagem de template. Ela tem suporte muito, muito básico para lógica — nada além de condições simples verdadeiro/falso e loops. Você não pode fazer comparações e outras coisas assim.
Então, onde exatamente você pode fazer isso? Em uma classe de conector, soa sofisticado… eu sei.
Em resumo, uma classe de conector é essencialmente um pedaço de JavaScript anexado ao outlet. É muito mais sutil do que isso, mas é tudo o que você realmente precisa saber por enquanto.
Então, vamos criar uma. Fazemos assim:
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('NOME_DO_OUTLET', 'NOME_DA_CUSTOMIZACAO', {
});
</script>
NOME_DO_OUTLET e NOME_DA_CUSTOMIZACAO aqui devem ser os mesmos que usamos acima. Então vamos alterá-los:
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
});
</script>
Esse trecho também vai na aba common > header do seu tema. Então, agora você deve ter algo assim:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
{{log args.model.groups}}
</script>
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
});
</script>
Dentro da nossa classe de conector, podemos fazer algum trabalho… mas… precisamos ter cuidado, pois não é apenas como qualquer arquivo JavaScript. Por falta de uma descrição melhor… pense nisso como um componente Ember em dieta. Expandir sobre isso está um pouco fora do escopo aqui, então vamos seguir em frente.
Existem quatro métodos conectados a ele por padrão:
actions permite que você defina ações assim:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
actions: {
myAction() {
// faça algo
}
}
});
Você pode então chamar essa ação dentro do outlet, como quando um botão é pressionado. Não precisaremos disso aqui, então vamos seguir em frente.
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
shouldRender(args, component) {
// retorne true ou false aqui
}
});
Também não usaremos este, pois o outlet só é renderizado em páginas de perfil e não temos outros requisitos por enquanto. No entanto, você pode usá-lo para adicionar quaisquer condições que deseje testar antes que o outlet seja renderizado. Por exemplo, o nível de confiança do usuário atual ou coisas assim. Seguindo em frente…
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
// faça algo
}
});
Este é o que queremos focar. Quaisquer condições ou variáveis de JavaScript que você deseja definir vão aqui. Antes de nos aprofundarmos nele, vamos cobrir o último método primeiro, por completude:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
teardownComponent(args, component) {
// faça algo
}
});
Isso é acionado quando o outlet está prestes a ser removido. Portanto, permite que você faça qualquer limpeza necessária, como remover ouvintes de eventos e assim por diante.
Ok, vamos voltar a setupComponent:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
// faça algo
}
});
Você pode ver que há duas coisas passadas para ele. Primeiro, há args e depois há component.
args aqui é a mesma coisa que examinamos anteriormente. São os dados de contexto que o Discourse passou para o outlet. Então, se você fizer:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
console.log(args.model.groups);
}
});
você verá as mesmas informações no console do navegador que vimos antes. Os grupos aos quais o proprietário do perfil pertence. É aqui que a diversão começa; agora você tem os dados e o gancho correto. Então você pode fazer o que quiser aqui. Então, se eu quiser que o vídeo apareça apenas nos perfis de membros que pertencem a um determinado grupo, posso fazer isso:
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
setupComponent(args, component) {
const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
const showVideo = inGroup.length ? true : false;
console.log(showVideo);
}
});
Se você tentar isso em uma página de perfil que pertence a um usuário do grupo staff, ele imprimirá true no console. Então, agora a única coisa que nos resta fazer é passar isso para o template do outlet. Veja como você pode fazer isso.
component passado para setupComponent aqui é compartilhado entre o conector e o outlet. Você pode passar coisas para o outlet definindo-as como propriedades no componente, assim:
const TARGET_GROUP = "staff"
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
setupComponent(args, component) {
const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
const showVideo = inGroup.length ? true : false;
- console.log(showVideo);
+ component.setProperties({showVideo})
}
});
Agora, se voltarmos ao template e fizermos algo como:
{{log showVideo}}
e ele imprimirá o mesmo resultado. Então, agora colocamos isso em uma condição Handlebars e adicionamos seu markup dentro dela, assim:
<script
type="text/x-handlebars"
data-template-name="/connectors/above-user-profile/add-profile-videos"
>
{{#if showVideo}}
<video playsinline autoplay muted loop id="myVideo" poster="[INSERIR LINK]">
<source src="[INSERIR LINK]" type="video/webm">
<source src="[INSERIR LINK]" type="video/mp4">
</video>
{{/if}}
</script>
então verifique uma página de perfil de um usuário staff. Você verá que o vídeo é carregado.
Assim que você navegar para fora do perfil do membro da equipe, o vídeo desaparecerá. O vídeo não aparecerá em perfis de usuários que não estão no grupo staff.
Então, vamos juntar tudo isso. Isso é a mesma coisa de acima.
Aqui está o CSS que usei. Aba common > css:
#myVideo {
position: fixed;
top: var(--header-offset);
min-height: 100vh;
left: 0;
z-index: -1;
}
.user-content {
background: none;
}
.user-main {
padding: 0.5em;
background: rgba(var(--secondary-rgb), 0.8);
}
// se você quiser que funcione no mobile também
.mobile-view {
body[class*="user-"] {
background: none;
.user-main,
.user-content {
padding: 0.5em;
background: rgba(var(--secondary-rgb), 0.8);
}
}
}
HTML / JavaScript / Handlebars. Isso vai na aba common > header do seu tema:
<script
type="text/x-handlebars"
data-template-name="/connectors/above-user-profile/add-profile-videos"
>
{{#if showVideo}}
<video playsinline autoplay muted loop id="myVideo" poster="[INSERIR LINK]">
<source src="[INSERIR LINK]" type="video/webm">
<source src="[INSERIR LINK]" type="video/mp4">
</video>
{{/if}}
</script>
<script type="text/discourse-plugin" version="0.8">
const TARGET_GROUP = "staff"
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
setupComponent(args, component) {
const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
const showVideo = inGroup.length ? true : false;
component.setProperties({showVideo})
}
});
</script>
Altere TARGET_GROUP para o nome do grupo que deseja atingir e adicione os atributos src para seus vídeos.
Este post foi um pouco longo… não se deixe desencorajar por isso. Uma vez que você compreenda o conceito, tudo o que fizemos acima pode ser feito em menos de 3 a 5 minutos.
A parte legal aqui é que tudo o que discutimos é praticamente o mesmo para qualquer plugin-outlet. A única coisa que muda é o nome. Portanto, isso se aplica a qualquer modificação de plugin-outlet que você queira fazer no futuro.
- Encontre o nome do outlet
- Obtenha os dados
- Processe os dados em um conector
- Passe as propriedades de volta para o template




