Skip to content

Commit

Permalink
Use shared toolbar for code block label + buttons (#112)
Browse files Browse the repository at this point in the history
* Move toolbar into label

* Add change to changelog

* Cleanup css props

* Fix unit tests

* Update copyright, fix cornercase for no toolbar code-blocks, update jsdoc and readme

* Include name fallback in escapeHtml call

* Remove misplaced line
  • Loading branch information
dbadea-heits authored May 22, 2024
1 parent d5c2f94 commit 8acbe30
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Any non-code changes should be prefixed with `(docs)`.
See `PUBLISH.md` for instructions on how to publish a new version.
-->

- (minor) Use shared toolbar for code block label + buttons

## v1.13.0 - 6056063

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ _No options are available for this plugin._
### fence_label

<details>
<summary>Add support for label markup at the start of a fence, translating to a label div before the fence.</summary>
<summary>Add support for label markup at the start of a fence, translating to a label div before the fence if there is a toolbar rendered or a label defined..</summary>

Markup must be at the start of the fence, though may be preceded by other metadata markup using square brackets.

Expand Down
12 changes: 12 additions & 0 deletions fixtures/full-output.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,47 +129,58 @@ <h2 id="step-1-basic-markdown"><a class="hash-anchor" href="#step-1-basic-markdo
<span class="token punctuation">}</span>
</code></pre>
<p>Examples can have line numbers, and every code block has a ‘Copy’ button to copy just the code:</p>
<div class="code-label" title=""></div>
<pre class="prefixed line_numbers language-javascript"><code><ol><li data-prefix="1"><span class="token keyword">const</span> test <span class="token operator">=</span> <span class="token string">&apos;hello&apos;</span><span class="token punctuation">;</span>
</li><li data-prefix="2"><span class="token keyword">const</span> other <span class="token operator">=</span> <span class="token string">&apos;world&apos;</span><span class="token punctuation">;</span>
</li><li data-prefix="3">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>test<span class="token punctuation">,</span> other<span class="token punctuation">)</span><span class="token punctuation">;</span>
</li></ol>
</code></pre>
<p>Here’s output from a command with a secondary label:</p>
<div class="code-label" title=""></div>
<pre><code><div class="secondary-code-label" title="Output">Output</div>Could not connect to Redis at 127.0.0.1:6379: Connection refused
</code></pre>
<p>This is a non-root user command example:</p>
<div class="code-label" title=""></div>
<pre class="prefixed command language-bash"><code><ol><li data-prefix="&#x24;"><span class="token function">sudo</span> <span class="token function">apt-get</span> update
</li><li data-prefix="&#x24;"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> python3
</li></ol>
</code></pre>
<p>This is a root command example:</p>
<div class="code-label" title=""></div>
<pre class="prefixed super_user language-bash"><code><ol><li data-prefix="#">adduser sammy
</li><li data-prefix="#"><span class="token function">shutdown</span>
</li></ol>
</code></pre>
<p>This is a custom prefix command example:</p>
<div class="code-label" title=""></div>
<pre class="prefixed custom_prefix language-bash"><code><ol><li data-prefix="mysql&gt;">FLUSH PRIVILEGES<span class="token punctuation">;</span>
</li><li data-prefix="mysql&gt;">SELECT * FROM articles<span class="token punctuation">;</span>
</li></ol>
</code></pre>
<p>A custom prefix can contain a space by using <code>\s</code>:</p>
<div class="code-label" title=""></div>
<pre class="prefixed custom_prefix language-bash"><code><ol><li data-prefix="(my-server) mysql&gt;">FLUSH PRIVILEGES<span class="token punctuation">;</span>
</li><li data-prefix="(my-server) mysql&gt;">SELECT * FROM articles<span class="token punctuation">;</span>
</li></ol>
</code></pre>
<p>Indicate where commands are being run with environments:</p>
<div class="code-label" title=""></div>
<pre class="prefixed command environment-local language-bash"><code><ol><li data-prefix="&#x24;"><span class="token function">ssh</span> root@server_ip
</li></ol>
</code></pre>
<div class="code-label" title=""></div>
<pre class="prefixed command environment-second language-bash"><code><ol><li data-prefix="&#x24;"><span class="token builtin class-name">echo</span> <span class="token string">&quot;Secondary server&quot;</span>
</li></ol>
</code></pre>
<div class="code-label" title=""></div>
<pre class="prefixed command environment-third language-bash"><code><ol><li data-prefix="&#x24;"><span class="token builtin class-name">echo</span> <span class="token string">&quot;Tertiary server&quot;</span>
</li></ol>
</code></pre>
<div class="code-label" title=""></div>
<pre class="prefixed command environment-fourth language-bash"><code><ol><li data-prefix="&#x24;"><span class="token builtin class-name">echo</span> <span class="token string">&quot;Quaternary server&quot;</span>
</li></ol>
</code></pre>
<div class="code-label" title=""></div>
<pre class="prefixed command environment-fifth language-bash"><code><ol><li data-prefix="&#x24;"><span class="token builtin class-name">echo</span> <span class="token string">&quot;Quinary server&quot;</span>
</li></ol>
</code></pre>
Expand Down Expand Up @@ -225,6 +236,7 @@ <h2 id="step-4-layout"><a class="hash-anchor" href="#step-4-layout" aria-hidden=
<summary>Content can be hidden using <code>details</code>.</summary>
<p>Inside the details block you can use any block or inline syntax.</p>
<p>You could hide the solution to a problem:</p>
<div class="code-label" title=""></div>
<pre class="language-javascript"><code><span class="token comment">// Write a message to console</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&apos;Hello, world!&apos;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
Expand Down
12 changes: 5 additions & 7 deletions modifiers/fence_label.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2022 DigitalOcean
Copyright 2024 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,7 +28,8 @@ const safeObject = require('../util/safe_object');
*/

/**
* Add support for label markup at the start of a fence, translating to a label div before the fence.
* Add support for label markup at the start of a fence, translating to a label div before the fence
* if there is a toolbar rendered or a label defined.
*
* Markup must be at the start of the fence, though may be preceded by other metadata markup using square brackets.
*
Expand Down Expand Up @@ -65,11 +66,8 @@ module.exports = (md, options) => {
const match = token.content.match(/^((?:\[.+\]\n)*?)\[label (.+)\]\n/);
const name = (match && (match[2] || '').trim()) || null;

// If no name, just return original
if (!name) return original(tokens, idx, opts, env, self);

// Remove the label line
token.content = token.content.replace(match[0], match[1]);
if (match && name) token.content = token.content.replace(match[0], match[1]);

// Get the rendered content
const content = original(tokens, idx, opts, env, self);
Expand All @@ -78,7 +76,7 @@ module.exports = (md, options) => {
const className = optsObj.className || 'code-label';

// Inject label and return
return `<div class="${md.utils.escapeHtml(className)}" title="${md.utils.escapeHtml(name)}">${md.utils.escapeHtml(name)}</div>
return `<div class="${md.utils.escapeHtml(className)}" title="${md.utils.escapeHtml(name || '')}">${md.utils.escapeHtml(name || '')}</div>
${content}`;
};

Expand Down
6 changes: 4 additions & 2 deletions modifiers/fence_label.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ world
});

it('handles a code block with no label', () => {
expect(md.render('```\nhello\nworld\n```')).toBe(`<pre><code>hello
expect(md.render('```\nhello\nworld\n```')).toBe(`<div class="code-label" title=""></div>
<pre><code>hello
world
</code></pre>
`);
});

it('handles a code block with an empty label', () => {
expect(md.render('```\n[label ]\nhello\nworld\n```')).toBe(`<pre><code>[label ]
expect(md.render('```\n[label ]\nhello\nworld\n```')).toBe(`<div class="code-label" title=""></div>
<pre><code>[label ]
hello
world
</code></pre>
Expand Down
29 changes: 19 additions & 10 deletions modifiers/prismjs.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 DigitalOcean
Copyright 2024 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -133,49 +133,57 @@ describe('HTML preservation', () => {
.use(require('./prismjs'));

it('handles a token at the start of the code block', () => {
expect(mdHtml.render('```js\nreturn;\n```')).toBe(`<pre class="language-javascript"><code class="language-js"><span class="token keyword">return</span><span class="token punctuation">;</span>
expect(mdHtml.render('```js\nreturn;\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-javascript"><code class="language-js"><span class="token keyword">return</span><span class="token punctuation">;</span>
</code></pre>
`);
});

it('handles plain-text at the start of the code block', () => {
expect(mdHtml.render('```js\nconsole.log;\n```')).toBe(`<pre class="language-javascript"><code class="language-js">console<span class="token punctuation">.</span>log<span class="token punctuation">;</span>
expect(mdHtml.render('```js\nconsole.log;\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-javascript"><code class="language-js">console<span class="token punctuation">.</span>log<span class="token punctuation">;</span>
</code></pre>
`);
});

it('handles nested tokens in the code block', () => {
expect(mdHtml.render('```nginx\nserver {}\n```')).toBe(`<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
expect(mdHtml.render('```nginx\nserver {}\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre>
`);
});

it('handles nested markup languages in the code block', () => {
expect(mdHtml.render('```php\na <?php b\n```')).toBe(`<pre class="language-php"><code class="language-php">a <span class="token php language-php"><span class="token delimiter important">&lt;?php</span> b
expect(mdHtml.render('```php\na <?php b\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-php"><code class="language-php">a <span class="token php language-php"><span class="token delimiter important">&lt;?php</span> b
</span></code></pre>
`);
});

it('handles HTML inside a token in the code block', () => {
expect(mdHtml.render('```js\nreturn \'hello <^>world<^>\';\n```')).toBe(`<pre class="language-javascript"><code class="language-js"><span class="token keyword">return</span> <span class="token string">&apos;hello <mark>world</mark>&apos;</span><span class="token punctuation">;</span>
expect(mdHtml.render('```js\nreturn \'hello <^>world<^>\';\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-javascript"><code class="language-js"><span class="token keyword">return</span> <span class="token string">&apos;hello <mark>world</mark>&apos;</span><span class="token punctuation">;</span>
</code></pre>
`);
});

it('handles HTML inside nested tokens in the code block', () => {
expect(mdHtml.render('```nginx\nserver { listen 80 <^>default_server<^>; }\n```')).toBe(`<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span> <span class="token directive"><span class="token keyword">listen</span> <span class="token number">80</span> <mark>default_server</mark></span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
expect(mdHtml.render('```nginx\nserver { listen 80 <^>default_server<^>; }\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span> <span class="token directive"><span class="token keyword">listen</span> <span class="token number">80</span> <mark>default_server</mark></span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
</code></pre>
`);
});

it('handles HTML spanning tokens in the code block', () => {
expect(mdHtml.render('```nginx\nserver { li<^>sten 80 default_server<^>; }\n```')).toBe(`<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span> <span class="token directive"><span class="token keyword">li</span><mark><span class="token keyword">sten</span> <span class="token number">80</span> default_server</mark></span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
expect(mdHtml.render('```nginx\nserver { li<^>sten 80 default_server<^>; }\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-nginx"><code class="language-nginx"><span class="token directive"><span class="token keyword">server</span></span> <span class="token punctuation">{</span> <span class="token directive"><span class="token keyword">li</span><mark><span class="token keyword">sten</span> <span class="token number">80</span> default_server</mark></span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
</code></pre>
`);
});

it('handles HTML spanning multi-line tokens in the code block', () => {
expect(mdHtml.render('```go\n<^>data := `<^>\n <^>test<^>\n<^>`<^>\n```')).toBe(`<pre class="language-go"><code class="language-go"><mark>data <span class="token operator">:=</span> <span class="token string">\`</span></mark><span class="token string">
expect(mdHtml.render('```go\n<^>data := `<^>\n <^>test<^>\n<^>`<^>\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-go"><code class="language-go"><mark>data <span class="token operator">:=</span> <span class="token string">\`</span></mark><span class="token string">
<mark>test</mark>
<mark>\`</mark></span>
</code></pre>
Expand All @@ -190,7 +198,8 @@ describe('HTML preservation', () => {
});

it('handles HTML wrapping each line', () => {
expect(mdHtml.render('```javascript,line_numbers\nconst test = \'hello\';\nconst other = \'world\';\nconsole.log(test, other);\n```')).toBe(`<pre class="language-javascript"><code class="prefixed line_numbers language-javascript"><ol><li data-prefix="1"><span class="token keyword">const</span> test <span class="token operator">=</span> <span class="token string">&apos;hello&apos;</span><span class="token punctuation">;</span>
expect(mdHtml.render('```javascript,line_numbers\nconst test = \'hello\';\nconst other = \'world\';\nconsole.log(test, other);\n```')).toBe(`<div class="code-label" title=""></div>
<pre class="language-javascript"><code class="prefixed line_numbers language-javascript"><ol><li data-prefix="1"><span class="token keyword">const</span> test <span class="token operator">=</span> <span class="token string">&apos;hello&apos;</span><span class="token punctuation">;</span>
</li><li data-prefix="2"><span class="token keyword">const</span> other <span class="token operator">=</span> <span class="token string">&apos;world&apos;</span><span class="token punctuation">;</span>
</li><li data-prefix="3">console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>test<span class="token punctuation">,</span> other<span class="token punctuation">)</span><span class="token punctuation">;</span>
</li></ol>
Expand Down
24 changes: 13 additions & 11 deletions styles/_code_label.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 DigitalOcean
Copyright 2024 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License") !default;
you may not use this file except in compliance with the License.
Expand All @@ -19,20 +19,22 @@ limitations under the License.
$code-label-class: "code-label" !default;

// Code labels
.#{$code-label-class} {
background-color: $gray7;
border-radius: 16px 16px 0 0;
color: $gray3;
display: block;
.#{$code-label-class}:has(+ .code-toolbar),
.#{$code-label-class}:not([title=""]) {
background-color: $code-highlight-dark;
border-radius: 1em 1em 0 0;
color: $white;
display: flex;
padding: 0.75em 1.5em;
justify-content: space-between;
align-items: center;
font-size: 16px;
margin: 1em 0 0;
padding: 12px;
position: relative;
text-align: center;
min-height: 3.25em;
z-index: 2;

+ pre {
border-radius: 0 0 16px 16px;
border-radius: 0 0 1em 1em;
margin: 0 0 1em;
}

Expand All @@ -41,7 +43,7 @@ $code-label-class: "code-label" !default;
margin: 0 0 1em;

pre {
border-radius: 0 0 16px 16px;
border-radius: 0 0 1em 1em;
}
}
}
5 changes: 3 additions & 2 deletions styles/_code_prism.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ pre {

> .toolbar {
position: absolute;
right: calc(1.5em - 0.5em);
top: calc(1.5em - 0.375em);
right: 1.5em;
top: -2.4em;
z-index: 3;

> .toolbar-item {
display: inline-block;
Expand Down
10 changes: 9 additions & 1 deletion styles/digitalocean/_code_environments.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2023 DigitalOcean
Copyright 2024 DigitalOcean
Licensed under the Apache License, Version 2.0 (the "License") !default;
you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,14 @@ limitations under the License.
@import "../theme";
@import "../mixins";

$code-label-class: "code-label" !default;

.#{$code-label-class}:has(+ .code-toolbar > pre[class*="environment-"]),
.#{$code-label-class}:has(+ pre[class*="environment-"]) {
background: $gray9;
color: $gray4;
}

// DigitalOcean-usage-specific code environment styling
pre {
// Environments use light-mode base
Expand Down

0 comments on commit 8acbe30

Please sign in to comment.