Recentemente comecei a federar as coisas do Computaria no dev.to. E percebi sabe o quê? Que não tinha menção à minha conta do dev.to no footer. Tenho diversas outras redes sociais, mas não essa conta específica.

Então vamos adicionar?

Antes

Originalmente eu adicionava na mão todos os detalhes do footer. O formato original era adicionar um elemento na lista:

<div class="footer-col footer-col-2">
  <ul class="social-media-list">
    {% if site.gitlab_username %}
      <li>
        {% include icon-gitlab.html username=site.gitlab_username %}
      </li>
    {% endif %}
    <!-- outros elementos -->
  </ul>
</div>

É uma lista uniforme seguindo sempre a mesma exata lógica:

  • se eu tiver um user name
  • adiciona um <li> cujo conteúdo é um icon-<media>.html passando como argumento username=<usuário checado antes>

E qual o formato geral do icon-<media>.html? Por exemplo, o icon-gitlab.html? (Exibido aqui pretty-printed)

<a href="https://gitlab.com/{{ include.username }}">
    <span class="icon icon--gitlab">{% include icon-gitlab.svg %}</span>
    <span class="username">{{ include.username }}</span>
</a>

Basicamente um link <a> com href usando o username passado como parâmetro. O conteúdo é um <span> com classes icon e icon--<media>, e o conteúdo desse <span> é um SVG incluído via Liquid:{% include icon-<media>.svg %}.

E depois tem um span com o username com a classe username.

Então, manter isso é bem trabalhoso… gostaria de um jeito que não precisar fazer tudo isso na mão.

O que é comum

Basicamente, a única coisa que realmente é única para cada elemento é o SVG de cada rede. De resto, poderíamos ter, em termos de Liquid:

<a href="{{ include.media_base_url}}/{{ include.username }}">
    <span class="icon icon--{{include.media_name}}">{% include icon-{{include.media_name}}.svg %}</span>
    <span class="username">{{ include.username }}</span>
</a>

Testando o Liquid

Ok, vamos tentar renderizar o link do gitlab. Primeiramente, criar o ícone template para incluir os links. Vou abstrair no _includes/social/icon.html.

Vamos testar? Vou colocar dentro do <ul> com a classe adequada para efeitos de exibição, para pegar as classes corretas. Vou também por em primeiro lugar o ícone original usado do gitlab e depois o ícone comum:

<ul class="social-media-list">
    <li>{% include icon-gitlab.html
                   username=site.gitlab_username %}</li>
    <li>{% include social/icon.html
                   username=site.gitlab_username
                   media_base_url="https://gitlab.com/"
                   media_name="gitlab" %}</li>
</ul>

Bem, foi sucesso, os dois ícones ficaram iguais, a única diferença foi uma barra a mais na URL dentro do href. Mas isso não é crítico e não interfere nada por hora.

Refatorando: datafiles

Para evitar ficar dando a manutenção manual em cada item individual, resolvi usar uma espécie de banco de dados de arquivo. Isso permite que eu consiga trabalhar sem me preocupar em montar manualmente cada elemento. E o Jekyll oferece já uma solução: datafiles.

Vamos criar um desses datafiles? _data/social_media.yaml.

Primeiro, só para ver se de fato vai ter alguma iteração:

{% for social_media in site.data.social_media %}
oi oi oi
{% endfor %}

oi oi oi

oi oi oi

oi oi oi

oi oi oi

oi oi oi

oi oi oi

oi oi oi

Muito bom, de fato iterou. Agora, vamos listar os atributos? Por em um <li> sem pretenção, separado por vírgulas. Pelo experimento anterior eu preciso de 3 atributos:

  • media_name
  • username
  • media_base_url

Vamos ver como fica?

<ul>
{% for social_media in site.data.social_media %}
    <li>
        {{social_media.media_name}}, {{social_media.username}}, {{social_media.media_base_url}}
    </li>
{% endfor %}
</ul>

Os dados:

- media_name: "gitlab"
  username: site.gitlab_username
  media_base_url: "https://gitlab.com/"
- media_name: "github"
  username: site.github_username
  media_base_url: "https://github.com/"

Renderizou assim (eu fiz algumas alterações nos dados depois):

  • gitlab, site.gitlab_username, https://gitlab.com/
  • github, site.github_username, https://github.com/

Hmmm, eu queria ter acesso a dados do _config.yml onde já tenho o gitlab_username e outras coisas, e isso não deu certo. Eu queria poder cadastrar isso como referências, não literais. Mas como eu consigo testar se isso funciona mesmo?

Primeiro teste, botar duas vezes a expansão Liquid, algo como {{ {{ "site.gitlab_username" }} }}?

Nah, não funcionou como eu esperava…

Erro de Liquid: abrir chave não esperado

Hmmm, procurando um pouco mais sobre Liquid achei essas outras documentações:

E no Shopify Liquid achei uma referência ao operador [] na expansão de valores. Então, vamos testar? Pela doc, {{ site.gitlab_username }} deveria renderizar igual a {{ site["gitlab_username"] }}. Vamos testar?

<ul>
    <li>{{ site.gitlab_username }}</li>
    <li>{{ site["gitlab_username"] }}</li>
</ul>
  • jefferson.quesado
  • jefferson.quesado

Funcionou!! Vamos refazer o experimento de como renderizar? Ajustando para usar o []:

<ul>
{% for social_media in site.data.social_media %}
    <li>
        {{social_media.media_name}}, {{ site[social_media.username]}}, {{social_media.media_base_url}}
    </li>
{% endfor %}
</ul>

Os dados:

- media_name: "github"
  username: github_username
  media_base_url: "https://github.com/"
- media_name: "gitlab"
  username: gitlab_username
  media_base_url: "https://gitlab.com/"

Renderizou assim (eu fiz algumas alterações nos dados depois):

  • gitlab, jefferson.quesado, https://gitlab.com/
  • github, jeffque, https://github.com/

Massa!

Vamos alterar o footer. No lugar de usar literalmente cada verificação, vou simplesmente usar o datafile:

<ul class="social-media-list">
    {% for social_media in site.data.social_media %}
    <li>{% include social/icon.html
                   username=site[social_media.username]
                   media_base_url=social_media.media_base_url
                   media_name=social_media.media_name %}</li>
    {% endfor %}
</ul>

E…

Sintaxe inválida, username=site[social_media.username]

Ok, ok… e se no lugar de usar diretamente o site[social_media.username] eu atribuir a uma variável?

<ul class="social-media-list">
    {% for social_media in site.data.social_media -%}
    {%- assign username = site[social_media.username] -%}
    <li>{%- include social/icon.html
                   username=username
                   media_base_url=social_media.media_base_url
                   media_name=social_media.media_name -%}</li>
    {% endfor %}
</ul>

Para temporariamente esses dados:

- media_name: "gitlab"
  username: gitlab_username
  media_base_url: "https://gitlab.com/"
- media_name: "github"
  username: github_username
  media_base_url: "https://github.com/"

Renderizou assim

Muito bem, hora de expandir

Um parêntese antes de expandir, controlando o espaçamento

Na real, a renderização foi (com exceção do SVG) assim:

<ul class="social-media-list">
  
  
  <li><a href="https://gitlab.com//jefferson.quesado"><span class="icon icon--gitlab">{% include icon-gitlab.svg %}
</span><span class="username">jefferson.quesado</span></a></li>
  
  
  <li><a href="https://github.com//jeffque"><span class="icon icon--github">{% include icon-github.svg %}
</span><span class="username">jeffque</span></a></li>
  
</ul>

E isso tinha muito espaço em branco. Vou colocar aqui o diff da versão que renderizou com esses espaços para o da versão que gerou sem o excesso de espaços:

 <ul class="social-media-list">
-    {% for social_media in site.data.social_media %}
-    {% assign username = site[social_media.username] %}
-    <li>{% include social/icon.html
+    {% include social/icon.html
+    {% for social_media in site.data.social_media -%}
+    {%- assign username = site[social_media.username] -%}
+    <li>{%- include social/icon.html
                    username=username
                    media_base_url=social_media.media_base_url
-                   media_name=social_media.media_name %}</li>
+                   media_name=social_media.media_name -%}</li>
     {% endfor %}
 </ul>

Notou a diferença? Foram só os tracinhos nos filtros. Isso permitiu fazer o controle de espaços. Colocar o traço no começo do for causou uma ausência muito grande de espaços que me incomodou, o <li> encostado no <ul>.

A exceção: StackOverflow

Bem, o StackOverflow segue um outro padrão que não uma URL seguido do nome do usuário. O padrão é:

https://pt.stackoverflow.com/users/<userid>/<username>

Ou seja, agora tenho duas variáveis, não apenas uma. Inclusive o mais importante é o ID para mostrar o usuário correto. Inclusive, ao tentar acessar https://pt.stackoverflow.com/users/64969/, você é direcionado para https://pt.stackoverflow.com/users/64969/jefferson-quesado.

Vamos definir um novo campo no yaml: media_url_literal. Posto isto, não se faz necessário mais a construção da URL. Vamos passar isso adiante e deixar com o icon para lidar com qual usar.

No href, vou por para renderizar o include.media_url_literal se ele existir, caso contrário vou para o método de construção de URL em cima do username que já tinha antes:

<a href="{%- if include.media_url_literal -%}{{include.media_url_literal}}{%- else -%}{{ include.media_base_url }}/{{ include.username }}{% endif %}">
    <span class="icon icon--{{include.media_name}}">{% include icon-{{include.media_name}}.svg %}</span>
    <span class="username">{{ include.username }}</span>
</a>

E na chamada eu simplesmente passo o novo atributo:

{%- include social/icon.html
            username=username
            media_base_url=social_media.media_base_url
            media_url_literal=social_media.media_url_literal
            media_name=social_media.media_name -%}

Username literal

Peguei outro caso excepcional: quando preciso lidar com nome literal, no lugar de nome via referência. Para lidar com isso, aproveitei que já tinha uma atribuição sendo feita à variável username e coloquei um trecho condicional.

Saí disso:

{%- assign username =  site[social_media.username] -%}

Para isso:

{%- if social_media.username_literal -%}
  {%- assign username =  social_media.username_literal -%}
{%- else -%}
  {%- assign username =  site[social_media.username] -%}
{% endif %}

E assim consegui renderizar do jeito que eu queria.

Mas, sinceramente? Isso me parece um tanto quanto… cumbersome… muitos comandos Liquid. Será que eu posso usar a tag liquid?

Pelo que eu li, seria assim:

{%- liquid
  if social_media.username_literal
    assign username =  social_media.username_literal
  else
    assign username =  site[social_media.username]
  endif
-%}

E a resposta é…

… não:

Unknown tag ‘liquid’

Ok, ao menos tentei. Talvez atualizar o Liquid usado no computaria no futuro?

Escondendo o que não quero mostrar

Ok, não tenho gostado do ramo que o Twitter tomou, inclusive em maior parte saí de lá. Mas… a rede social tá lá, né? Já que estou catalogando minhas redes sociais, vou ao menos cadastrar o Twitter no meu datafile. Mas para isso preciso também esconder ele, já que ativamente não quero mais mostrar.

Tentei inicialmente usar no for um filtro where, o que não deu muito certo…

{% for social_media in social_media_shown | where: "show" %}
{% endfor %}

Não fez efeito algum. Então resolvi fazer o exemplo que estava vendo associado ao where: colocar no assign.

Tentei isso

{% assign social_media_shown = site.data.social_media | where: "show" %}

e com isso obtive

Liquid error (line 485): wrong number of arguments (given 2, expected 3)

Ué. Mas é bem dizer o exemplo do site! Mas, será que… Bem, o próprio Liquid admite que existem duas versões do Liquid, o Spotify Liquid e o Jekyll Liquid. Será que eu peguei o caso em que diferenciam?

Vamos ver a documentação do Jekyll Liquid em relação ao where

Select all the objects in an array where the key has the given value.

E o exemplo

{{ site.members | where:"graduation_year","2014" }}

É, peguei justamente o caso em que o Jekyll Liquid difere do padrão…

Mas olha que legal logo ali debaixo do where, em where_exp!

Select all the objects in an array where the expression is true.

Com os exemplos:

{{ site.members | where_exp:"item", "item.graduation_year < 2014" }}

Ou seja, o primeiro argumento se torna o alias do elemento dentro da expressão, e o segundo argumento é a expressão em si! Será que temos alguma expressão para pegar apenas quem tem show != false?

{% assign social_media_shown = site.data.social_media | where_exp: "social_media", "social_media.show != false" %}
{%- for social_media in social_media_shown -%}
- {{social_media.media_name}}
{% endfor %}
  • gitlab
  • github
  • bsky
  • stackoverflow
  • instagram
  • devto

Com o resultado esperado.

Limpeza final

Basicamente os icon-<media>.html perderam sentido de ser. O icon-gitlab.html vai ser mantido por conta do exemplo usado nesta publicação e também por conta das coisas no sobre.

O icon-stackoverflow também é usado na mesma página. Então vou manter esses dois. Já os outros não tem necessidade. Vou remover e deixar o _includes limpo.