Tenho experimentado com números de linha tanto para quebra quanto para rolagem no fórum do Docker. Isso ainda não está ao vivo, mas gostaria de saber se alguém tem algum comentário. Então, primeiro algumas capturas de tela:
Conteúdo quebrado
Acima, os números de linha ajudam a identificar quando uma linha termina e outra começa. Claro, existem outras maneiras de renderizar isso, mas este tópico trata dos números de linha. ![]()
Conteúdo rolado
Com preenchimento limitado no lado(s) onde o conteúdo está oculto, para indicar que a rolagem revelará mais:
Ou com sombras para indicar onde o conteúdo está oculto horizontalmente:
Algum feedback que recebi mostrou que as pequenas sombras podem ser confundidas com barras de rolagem, então um estilo diferente pode ser ótimo.
Adicionando os números de linha no lado do cliente
Ao usar rolagem horizontal, cada linha de conteúdo ocupará uma linha na tela. Então, em teoria, poderíamos usar JavaScript para injetar um <div> vertical com uma faixa de números de linha e posicioná-lo ao lado do conteúdo com rolagem. Mas, ao quebrar, um único número de linha pode se aplicar a várias linhas de conteúdo, para o que usar span::before em CSS junto com content: counter(...) parece mais fácil. É isso que é usado aqui.
Para usar ::before, cada linha no bloco de código pré-formatado primeiro precisa de algum elemento pai, como ser envolvido em um <span> usando api.decorateCookedElement. Isso requer que o último seja executado antes do highlight.js, pois às vezes o highlight.js não fecha as tags na mesma linha em que as abriu. Por exemplo, quando ele acha que algo é Mustache/Handlebars:
docker image ls --format '{{slice (printf "%s:%s" .Repository .Tag) 0 11}}'
O acima resulta na tag de fechamento </span> na próxima linha:
<code class="lang-handlebars hljs"><span class="xml">docker image ls --format '</span><span class="hljs-template-variable">{{<span class="hljs-name">slice</span> (<span class="hljs-name">printf</span> <span class="hljs-string">"%s:%s"</span> .Repository .Tag) <span class="hljs-number">0</span> <span class="hljs-number">11</span>}}</span><span class="xml">'
</span></code>
Claro, isso pode ser um bug temporário que provavelmente devo relatar. Mas, independentemente disso, é melhor prevenir. E a boa notícia: parece que, hoje, api.decorateCookedElement(...) realmente roda antes do highlight.js.
O próximo JavaScript envolve cada linha em um <span> e adiciona a classe CSS lines-scroll ou lines-wrap para indicar qual estilo é desejado (poderia até deixar o usuário alternar isso). Também adiciona lines-shadow para habilitar as sombras de rolagem. E adiciona algumas classes CSS ao <pre> e <code> pai para suportar estilização e garantir que as coisas sejam decoradas apenas uma vez.
Decorador JavaScript do tema
O seguinte também habilita números de linha e a outra estilização na visualização do editor. Altere a última linha para incluir onlyStream: true para desabilitar isso:
api.decorateCookedElement(decorateLines, {id: 'decorate-pre-code-lines', onlyStream: true});
Adicione tudo o seguinte ao “Comum, Cabeçalho” do seu componente em /admin/customize/themes/:
<script type="text/discourse-plugin" version="0.8">
const decorateLines = (post) => {
try {
// Combinações de 'number', 'wrap', 'scroll' e/ou 'shadow'
const classes = ['decorator', 'number', 'scroll', 'shadow'].map(c => `lines-${c}`);
const split = /^(.*)$/mg;
const elems = post.querySelectorAll('pre code:not(.lines-decorator)');
elems.forEach(elem => {
const count = elem.innerHTML.trim().match(split).length;
// O Discourse usa `<aside>` para citações de outras postagens do fórum
const quote = elem.closest('aside') ? ['lines-in-quote'] : []
elem.parentElement.classList.add(`lines-count-${count}`, ...classes, ...quote);
elem.classList.add(`lines-count-${count}`, ...classes, ...quote);
const lineClass = ['lines-line', ...quote].join(' ');
// Suponha que não tenhamos nenhum ouvinte de evento na linha,
// e trim para ocultar linhas em branco finais
elem.innerHTML = elem.innerHTML.trim().replace(/^(.*)$/mg, `<span class="${lineClass}">$1</span>`);
});
} catch (e) {
console.error(e);
}
}
api.decorateCookedElement(decorateLines, {id: 'decorate-pre-code-lines'});
</script>
Estilização Sass SCSS
O seguinte suporta quebra com e sem numeração de linhas, e rolagem com ou sem numeração de linhas e/ou sombras. Também suprime a numeração de linhas quando há apenas uma linha.
Isso não adiciona números de linha a citações de outras postagens, pois, se tal citação for apenas uma citação parcial, ela ainda começará a numerar do 1. Para adicionar numeração a citações, remova o :not(.lines-in-quote) da linha abaixo (que ocorre várias vezes no SCSS):
&.lines-number:not(.lines-count-1):not(.lines-in-quote) {
Acima é também onde a numeração para blocos de código de uma única linha está sendo suprimida.
Estilização Sass do tema
Adicione isso ao “Comum, CSS” do seu componente em /admin/customize/themes/:
pre.lines-decorator code.lines-decorator span.lines-line {
&::before {
box-sizing: content-box;
}
}
pre.lines-decorator {
--lines-bgcolor: var(--hljs-bg);
--lines-number-width: 2em;
--lines-number-padding-to-right-border: .5em;
--lines-shadow-width: .8em;
--lines-shadow-color: rgba(0, 0, 0, .15);
// Sem preenchimento para o PRE externo:
// - barras de rolagem não cobrindo o CODE mas próximas às bordas mais externas
// - texto próximo às bordas quando há conteúdo oculto
// - da mesma forma para as sombras
padding: 0;
// #f5f5f5 em `code.less`
background-color: var(--lines-bgcolor);
}
code.lines-decorator {
padding: 1.5em;
&.lines-wrap {
white-space: pre-wrap;
&:not(.lines-count-1) {
span.lines-line {
display: inline-block;
position: relative;
padding-left: 3em;
}
}
}
&.lines-scroll {
white-space: pre;
span.lines-line {
padding-right: 1.5em;
&::before {
position: sticky;
left: 0;
}
}
}
&.lines-wrap span.lines-line {
// Altura mínima para obter borda e fundo para linhas vazias (apenas
// Firefox precisa disso para o ::before, mas Chrome também para o span)
min-height: 1.5em;
&::before {
position: absolute;
left: 0;
}
}
&.lines-shadow {
background-image:
// As duas primeiras coberturas que rolam com o conteúdo, portanto aparecem
// na frente das sombras quando não há rolagem possível. Essas
// correspondem à cor de fundo para os primeiros 25%, e depois um gradiente
// até transparência total (assumindo que as coberturas são 4 vezes a largura
// das sombras).
linear-gradient(to right, var(--lines-bgcolor) 25%, transparent),
linear-gradient(to left, var(--lines-bgcolor) 25%, transparent),
// E atrás disso duas sombras em posições fixas, para indicar
// conteúdo rolagem, oculto se não coberto.
linear-gradient(to right, var(--lines-shadow-color), transparent),
linear-gradient(to left, var(--lines-shadow-color), transparent);
// Ou:
// radial-gradient(farthest-side at 0px 50%, var(--lines-shadow-color), transparent),
// radial-gradient(farthest-side at 100% 50%, var(--lines-shadow-color), transparent);
background-size:
// Defina a cobertura para 4 vezes a largura da sombra real, para corresponder
// ao ponto de parada de 25% no gradiente da cobertura
calc(4 * var(--lines-shadow-width)) 100%,
calc(4 * var(--lines-shadow-width)) 100%,
var(--lines-shadow-width) 100%,
var(--lines-shadow-width) 100%;
--shadow-left: 0;
&.lines-number:not(.lines-count-1):not(.lines-in-quote) {
// Se os números de linha estiverem visíveis, precisamos mover a sombra
--shadow-left: calc(var(--lines-number-width) + var(--lines-number-padding-to-right-border));
}
background-position: var(--shadow-left) center, right center, var(--shadow-left) center, right center;
background-attachment: local, local, scroll, scroll;
background-repeat: no-repeat;
}
&.lines-number:not(.lines-count-1):not(.lines-in-quote) {
padding-left: 0;
counter-reset: line-numbering;
span.lines-line::before {
display: inline-block;
content: counter(line-numbering);
counter-increment: line-numbering;
border-right: 1px solid #ccc;
width: var(--lines-number-width);
padding-right: var(--lines-number-padding-to-right-border);
margin-right: .5em;
height: 100%;
text-align: right;
font-family: var(--font-family);
color: var(--primary-medium);
background-color: var(--lines-bgcolor);
}
}
}
Desenvolvimento
Para desenvolvimento, usei o próximo código, permitindo definir parâmetros de consulta de URL, como ?avbPreview para habilitar o gancho do decorador JavaScript, e coisas como ?avbPreview&avbClasses=number,scroll para testar números de linha com rolagem mas sem sombras. Como todo o CSS é escopado, isso não deve afetar outros usuários durante os testes.
Versão de desenvolvimento/debug
<script type="text/discourse-plugin" version="0.8">
const decorateLines = (post) => {
try {
const params = new URL(document.location.href).searchParams;
const preview = params.has('avbPreview');
if (!preview) {
return;
}
const classes = ['decorator'].concat(
(params.get('avbClasses') || 'number,scroll,shadow').split(',')).map(c => `lines-${c}`);
const debug = params.has('avbDebug');
const split = /^(.*)$/mg;
const elems = post.querySelectorAll('pre code:not(.lines-decorator)');
elems.forEach(elem => {
const count = elem.innerHTML.trim().match(split).length;
// O Discourse usa `<aside>` para citações de outras postagens do fórum
const quote = elem.closest('aside') ? ['lines-in-quote'] : []
elem.parentElement.classList.add(`lines-count-${count}`, ...classes, ...quote);
elem.classList.add(`lines-count-${count}`, ...classes, ...quote);
const lineClass = ['lines-line', ...quote].join(' ');
if (debug) {
console.log('===== Reescrevendo; fonte\n' + elem.innerHTML.trim());
console.log('===== Reescrevendo; resultado\n' + elem.innerHTML.trim().replace(split, `<span class="${lineClass}">$1</span>`));
}
// Suponha que não tenhamos nenhum ouvinte de evento na linha,
// e trim para ocultar linhas em branco finais
elem.innerHTML = elem.innerHTML.trim().replace(/^(.*)$/mg, `<span class="${lineClass}">$1</span>`);
});
} catch (e) {
console.error(e);
}
}
api.decorateCookedElement(decorateLines, {id: 'decorate-pre-code-lines'});
</script>




