Jekyll Capture is not preserving new lines/carriage returns

I’m trying to capture a variable that has lines within it.

I’ve simplified the task for demonstration to this:

{% capture new %}This is
not
working{% endcapture %}

I then want to “print the variable”

New code: {{ new }}

I’m getting this as my output:

New code: This is not working

Where did the line breaks go?

I would expect something like:

New code: This is
not
working

Is there something in my settings which are hiding the line breaks?

The line-breaks are preserved. Perhaps not rendered by the browser due to some CSS…
To test, do one of the following:

  • Use the inspect Liquid filter:
    New code: {{ new | inspect }}
    
  • Wrap the code in <pre> tags:
    <pre>New code: {{ new }}</pre>
    

You are correct the insect shows the following:

{% capture txt %}Hello
I'm a new line 1
I'm new line 2{% endcapture %}

{{ txt | inspect }}

New code: "Hello\nI’m a new line 1\nI’m new line 2"

{{ txt }}

New code: Hello I’m a new line 1 I’m new line 2

Do you know any css selector that would prevent the line breaks from being displayed?

If you are using Markdown, then line-breaks are encoded with two spaces at the end of the line. Example below with visible SP symbols instead of spaces:

{% capture txt %}Hello␠␠
I'm a new line 1␠␠
I'm new line 2␠␠{% endcapture %}

New code: {{ txt }}

Which will render in HTML as:

New code: Hello
I’m a new line 1
I’m new line 2

Your right that if I save with two spaces at the end the variable will render as you demonstrated

The problem however is that I’m capturing the lines within a variable txt (as shown above) and then passing the variable to a javascript. Within the javascript function I created an alert box to print the value of the variable. No matter if I add two spaces at the end or add no spaces, the variable is always shown as one line. If it helps I can post the code if necessary, however the problem is I’m not getting the line breaks.

My code BTW just copies the variable value to the system clipboard.

To use the txt variable in Javascript, you’ll need to encode it as Javascript value, which can be done with the jsonify filter. For example:

<script> alert( {{ txt | jsonify }} ) </script>

Hmm that doesn’t seem to quite work

There are three files involved here: (the markdown file, an _include file, and the script file)

Here are snippets:
Markdown file:

{% capture txt %}Hello
I'm a new line 1
I'm new line 2  {% endcapture %}
{% include code.html header="Code Code" code=txt lang="bash" %}

code.html

{% assign cp = include.code %}
<div
  class="copy-code-button"
  data-code={{ cp | jsonify }}
  title="Copy to clipboard"
  tabindex="0"
></div>
```{{ include.lang }}
{{ include.code }}
```

And finally the script file:

const copyCode = copyCodeButton => {
  const tempTextArea = document.createElement("textarea");
  tempTextArea.textContent = copyCodeButton.getAttribute("data-code");
  document.body.appendChild(tempTextArea);

  const selection = document.getSelection();
  selection.removeAllRanges();
  tempTextArea.select();
  alert("Copied  text: " + tempTextArea.textContent);
  document.execCommand("copy");
  selection.removeAllRanges();
  document.body.removeChild(tempTextArea);

  copyCodeButton.classList.add("copied");
  setTimeout(() => {
    copyCodeButton.classList.remove("copied");
  }, 2000);

};

document.addEventListener("keyup", keyEvent => {
  if (keyEvent.keyCode === 13) {
    copyCode(keyEvent.target);
  }
});

document.querySelectorAll(".copy-code-button").forEach(copyCodeButton => {
  copyCodeButton.addEventListener("click", clickEvent =>
    copyCode(clickEvent.target)
  );
});

With the jsonify file I’m literally getting in the textbox:

Copied  text: Hello \nI'm a new line 1 \nI'm new line 2

I have a feeling I’m not approaching this correctly so I’m a little confused. Honestly I’m just trying to copy the text as written with the line breaks to the clipboard.

Thanks for posting more code. I see now that you want to encode the text as an HTML element attribute, not as Javascript. Note that it can be tricky to precisely use HTML inside Markdown – in theory it works, but there are all sorts of edge cases (like newline handling).

You’ve named your include code.html, but because it is include in a Markdown file, it is interpreted as Markdown, so newlines are problematic.

One kludgy workaround is to URI-encode the text, and decode it as needed. For example:

<button
 data-txt="{{ txt | uri_escape }}"
 onclick="alert(decodeURI(this.getAttribute('data-txt')))">
 Click Me
</button>

Stepping back, I think a better design would be to use innerText to directly fetch the text from the code block, rather then duplicate it in a data attribute. This will avoid the duplication, and simplify the code and markup.

I understand from 10,000ft what you are suggesting, however I’m looking at my markup and I don’t know how to fetch the innerHTML of what Ive generated given the highlighting:

I’m using markdown with a theme and there is a bunch of language highlights to the actual code block. How do you fetch innerhtml from that?

With innerText, all the highlighting spans inside a code block will be ignored/stripped.

Here’s a concrete example: On any page with a Markdown code-block (including this page), try pasting the following into the Javascript console:

alert(document.querySelector('pre > code').innerText)

It will display the text of the first code-block on the page.

@kevdog have you been through Jekyll filter list? There is one made for this.

newline_to_br

Replaces every newline ( \n ) in a string with an HTML line break ( <br /> ).

Input

{% capture string_with_newlines %}
Hello
there
{% endcapture %}

{{ string_with_newlines | newline_to_br }}

Output


<br />
Hello<br />
there<br />

Source:

@MichaelCurrin
That solution isn’t going to work since I dont want to copy test to clipboard with resulting
statements in it

@chuckhoupt
Based on your advice I almost have everything however I need a little help

I currently have a div with the following classes

`<div class="language-bash testid highlighter-rouge" id="testid">`

Not the language may change so its possible I could have language-ruby, language-bash, language-js etc. I want to filter for divs containing the class names language-* and testid

Filtering for either is fairly easy:

var codeblocks = document.querySelectorAll('div[class^=language-]');
var anothercodeblocks = document.querySelectorAll(".testid")

however how do I combine the search – I want to do an AND search.

To make the matter more complex, I’m using the first statement to get all the pre>code blocks…

var codeblocks = document.querySelectorAll('div[class^=language-] > div > pre > code');

I want to do something like

 var codeblocks = document.querySelectorAll('div[class^=language-] .testid > div > pre > code');

Which is my sloppy not working way of filtering for (give me all div blocks with class atrributes that either start with language- AND with an attribute .testid AND contain the subblocks div > pre > code.

Does that make any sense??

Other than that I have the button logic working using your innerhtml help and the clipboard.js library.

If you have an ID, wouldn’t you use an ID-selector to get the element? For example, to get the code-text in your example div, you might do the following (untested code):

document.querySelector('#testid code').innerText

Here’s another alternative design to think about. Rather then laboriously give everything IDs, you could locate the code relative to the control element. For example, here’s a code-block in Markdown with a button to show the innerText (tested code):

```c
main()
{
    printf("Hello World!\n");
}
```
<button
 onclick="alert(this.parentNode.previousElementSibling.querySelector('code').innerText)">
Raw Code
</button>

That button can be placed after any Markdown code-block and it will work without modification. This also makes it easy to dynamically add the button to all code-blocks on a page, which would allow you to write plain Markdown code-blocks, and then have them progressively enhanced with clipboard buttons, etc.

@chuckhoupt

Well the solution to my dilemma turned out to be a hell of a lot more work than it should have been. Let me preface this statement with the knowledge I’m learning everything on the fly here – no js, markdown experience minus maybe about 1 month.

Goal was to create a Copy button to copy text within code block. I unfortunately had done some css styling to introduce a “header” to the code block and also wanted my “copy button” to be on top right hand side above code block. Here is a picture – its nothing fancy.

Anyway your advice above gave me the knowledge of “how to walk the node tree” for lack of a better term.

I ended up using clipboard.js for the clipboard functionality. I also wanted a tooltip that would indicate when the text was copied:

Which meant a lot of learning how to work with external libraries like jsquery, And bootstrap.

So lets talk about the solution here for all you novices like me that want to do this:
Copy required libraries:
Within head.html -

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.0/css/bootstrap.min.css" integrity="sha256-/ykJw/wDxMa0'^'AQhHDYfuMEwVb4JHMx9h4jD4XvHqVzU=" crossorigin="anonymous" />

And somewhere in your body.html you can put these libraries:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous'^'"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.0/js/bootstrap.bundle.js" integrity="sha256-KrRa8Ba46ro/+RPPjj/MSJqZViXxrnTp8Nyg5zLpHpQ=" cr'^'ossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.6/clipboard.min.js" integrity="sha256-inc5kl9MA1hkeYUt+EC3BhlIgyp/2jDIyBLS6k3UxPI=" crossorigin="'^'anonymous"></script>
<script src="/assets/scripts/copy_button.js"></script>

So the actual copy_button.js script is what @chuckoupt greatly helped me out with:

var codeblocks = document.querySelectorAll('div[class^=language-] > div > pre > code');
var countID = 0;

codeblocks.forEach((codeblock) => {

  codeblock.setAttribute("id", "code" + countID);

  var btn = document.createElement('button');
  btn.className = "btn";
  btn.innerHTML = "Copy";
  btn.setAttribute("data-clipboard-action", "copy");
  btn.setAttribute("data-clipboard-target", "#code" + countID);

  codeblock.parentNode.parentNode.parentNode.parentNode.previousElementSibling.appendChild(btn);

  countID++;

});

function setTooltip(btn, message) {
  $(btn).tooltip('hide')
    .attr('data-original-title', message)
    .tooltip('show');
}

function hideTooltip(btn) {
  setTimeout(function() {
    $(btn).tooltip('hide');
  }, 5000);
}

$('button').tooltip({
  trigger: 'click',
  placement: 'top'
});

var clipboard = new ClipboardJS('.btn');

clipboard.on('success', function(event) {
  setTooltip(event.trigger, 'Copied!');
  hideTooltip(event.trigger);
  console.log(event);
  event.clearSelection();
});

clipboard.on('error', function(event) {
   console.log(event);
});

What’s probably not going to be universal is the line in the script above:
codeblock.parentNode.parentNode.parentNode.parentNode.previousElementSibling.appendChild(btn);

This is the “node walk” I was discussing above. Why the complexity?? I’m using a theme for my layout called just-the-docs which tends to bury the code blocks many nodes deep. This was coupled with my flexbox layout like this:

<div class="containerlayout">
  <div class="itemlayout">
    C
  </div>
  <div class="itemlayout">
  </div>
  <div class="itemlayout">
    {{ code | markdownify }}
  </div>
</div>

What I needed to do was “walk back up the tree” from {{ code | markdownify }} and insert the actual button into the empty div block located above. Knowing how to walk the node tree, you can actually place your button anywhere. There are a lot of different methods for placement, but for this example I used the code block as a reference and then walked backwards from there.

In terms of making this more universal, its possible to turn my actual formatting into and _includes file (I call this code.html however you can call it anything)-- something like this:

{% assign header = include.header %}
{% assign language = include.lang %}
{% capture code %}```{{ language }}
{{ include.code }}
```{% endcapture %}

<div class="containerlayout">
  <div class="itemlayout">
    {{ header }}
  </div>
  <div class="itemlayout">
  </div>
  <div class="itemlayout">
    {{ code | markdownify }}
  </div>
</div>

So from your main file – in order to produce a box like demonstrated above, all you need to do is:

{% caputure code %}
This is my code
This is my code line #2
This is my code line #3
{% endcapture %}
{% include code.html header="C Language" lang="c" code=code %}

I have no idea if that helps anyone or not. Thanks @chuckhoupt who guided me through the way, and I’m sure he could probably post a solution that was far more easier and probably efficient.

1 Like