diff --git a/CHANGELOG.md b/CHANGELOG.md index 39d0b41..367c751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index d4ea194..dc1fb96 100644 --- a/README.md +++ b/README.md @@ -874,7 +874,7 @@ _No options are available for this plugin._ ### fence_label
-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. diff --git a/fixtures/full-output.html b/fixtures/full-output.html index c8ab505..cca01ff 100644 --- a/fixtures/full-output.html +++ b/fixtures/full-output.html @@ -129,47 +129,58 @@

}

Examples can have line numbers, and every code block has a ‘Copy’ button to copy just the code:

+
  1. const test = 'hello';
  2. const other = 'world';
  3. console.log(test, other);

Here’s output from a command with a secondary label:

+
Output
Could not connect to Redis at 127.0.0.1:6379: Connection refused

This is a non-root user command example:

+
  1. sudo apt-get update
  2. sudo apt-get install python3

This is a root command example:

+
  1. adduser sammy
  2. shutdown

This is a custom prefix command example:

+
  1. FLUSH PRIVILEGES;
  2. SELECT * FROM articles;

A custom prefix can contain a space by using \s:

+
  1. FLUSH PRIVILEGES;
  2. SELECT * FROM articles;

Indicate where commands are being run with environments:

+
  1. ssh root@server_ip
+
  1. echo "Secondary server"
+
  1. echo "Tertiary server"
+
  1. echo "Quaternary server"
+
  1. echo "Quinary server"
@@ -225,6 +236,7 @@

Content can be hidden using details.

Inside the details block you can use any block or inline syntax.

You could hide the solution to a problem:

+
// Write a message to console
 console.log('Hello, world!');
 
diff --git a/modifiers/fence_label.js b/modifiers/fence_label.js index ff3a7f1..7807dd6 100644 --- a/modifiers/fence_label.js +++ b/modifiers/fence_label.js @@ -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. @@ -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. * @@ -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); @@ -78,7 +76,7 @@ module.exports = (md, options) => { const className = optsObj.className || 'code-label'; // Inject label and return - return `
${md.utils.escapeHtml(name)}
+ return `
${md.utils.escapeHtml(name || '')}
${content}`; }; diff --git a/modifiers/fence_label.test.js b/modifiers/fence_label.test.js index 7c0d3b6..63ed44c 100644 --- a/modifiers/fence_label.test.js +++ b/modifiers/fence_label.test.js @@ -27,14 +27,16 @@ world }); it('handles a code block with no label', () => { - expect(md.render('```\nhello\nworld\n```')).toBe(`
hello
+    expect(md.render('```\nhello\nworld\n```')).toBe(`
+
hello
 world
 
`); }); it('handles a code block with an empty label', () => { - expect(md.render('```\n[label ]\nhello\nworld\n```')).toBe(`
[label  ]
+    expect(md.render('```\n[label  ]\nhello\nworld\n```')).toBe(`
+
[label  ]
 hello
 world
 
diff --git a/modifiers/prismjs.test.js b/modifiers/prismjs.test.js index 6a5b45a..fd827b7 100644 --- a/modifiers/prismjs.test.js +++ b/modifiers/prismjs.test.js @@ -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. @@ -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(`
return;
+        expect(mdHtml.render('```js\nreturn;\n```')).toBe(`
+
return;
 
`); }); it('handles plain-text at the start of the code block', () => { - expect(mdHtml.render('```js\nconsole.log;\n```')).toBe(`
console.log;
+        expect(mdHtml.render('```js\nconsole.log;\n```')).toBe(`
+
console.log;
 
`); }); it('handles nested tokens in the code block', () => { - expect(mdHtml.render('```nginx\nserver {}\n```')).toBe(`
server {}
+        expect(mdHtml.render('```nginx\nserver {}\n```')).toBe(`
+
server {}
 
`); }); it('handles nested markup languages in the code block', () => { - expect(mdHtml.render('```php\na a <?php b + expect(mdHtml.render('```php\na +
a <?php b
 
`); }); it('handles HTML inside a token in the code block', () => { - expect(mdHtml.render('```js\nreturn \'hello <^>world<^>\';\n```')).toBe(`
return 'hello world';
+        expect(mdHtml.render('```js\nreturn \'hello <^>world<^>\';\n```')).toBe(`
+
return 'hello world';
 
`); }); it('handles HTML inside nested tokens in the code block', () => { - expect(mdHtml.render('```nginx\nserver { listen 80 <^>default_server<^>; }\n```')).toBe(`
server { listen 80 default_server; }
+        expect(mdHtml.render('```nginx\nserver { listen 80 <^>default_server<^>; }\n```')).toBe(`
+
server { listen 80 default_server; }
 
`); }); it('handles HTML spanning tokens in the code block', () => { - expect(mdHtml.render('```nginx\nserver { li<^>sten 80 default_server<^>; }\n```')).toBe(`
server { listen 80 default_server; }
+        expect(mdHtml.render('```nginx\nserver { li<^>sten 80 default_server<^>; }\n```')).toBe(`
+
server { listen 80 default_server; }
 
`); }); it('handles HTML spanning multi-line tokens in the code block', () => { - expect(mdHtml.render('```go\n<^>data := `<^>\n <^>test<^>\n<^>`<^>\n```')).toBe(`
data := \`
+        expect(mdHtml.render('```go\n<^>data := `<^>\n  <^>test<^>\n<^>`<^>\n```')).toBe(`
+
data := \`
   test
 \`
 
@@ -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(`
  1. const test = 'hello'; + expect(mdHtml.render('```javascript,line_numbers\nconst test = \'hello\';\nconst other = \'world\';\nconsole.log(test, other);\n```')).toBe(`
    +
    1. const test = 'hello';
    2. const other = 'world';
    3. console.log(test, other);
    diff --git a/styles/_code_label.scss b/styles/_code_label.scss index 08846a5..02d335e 100644 --- a/styles/_code_label.scss +++ b/styles/_code_label.scss @@ -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. @@ -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; } @@ -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; } } } diff --git a/styles/_code_prism.scss b/styles/_code_prism.scss index 8fb5400..e52ab37 100644 --- a/styles/_code_prism.scss +++ b/styles/_code_prism.scss @@ -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; diff --git a/styles/digitalocean/_code_environments.scss b/styles/digitalocean/_code_environments.scss index 661803f..db9306b 100644 --- a/styles/digitalocean/_code_environments.scss +++ b/styles/digitalocean/_code_environments.scss @@ -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. @@ -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