Skip to content

Commit e332d71

Browse files
authored
Support more SHCB features (#2138)
* More SHCB feature support * Separate Highlight and SHCB styles and docs
1 parent 5430bf2 commit e332d71

File tree

9 files changed

+255
-86
lines changed

9 files changed

+255
-86
lines changed

.changeset/quick-rice-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cloudfour/patterns': minor
3+
---
4+
5+
Adds support for Syntax-highlighting Code Block's highlighted lines, line number and line wrap features

src/vendor/highlight/_theme.scss

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
@use '../../compiled/tokens/scss/color';
22
@use '../../compiled/tokens/scss/opacity';
3-
@use '../../mixins/a11y';
43
@use 'sass:color' as sasscolor;
54

65
/// Cloud Four theme for Highlight syntax highlighting.
@@ -78,16 +77,3 @@
7877
$saturation: +10%
7978
);
8079
}
81-
82-
/// Syntax-Highlighted Code Block Plugin Customizations
83-
84-
///
85-
/// The plugin adds some content showing the language the code is written in.
86-
/// This is helpful info, and we explored an interesting visual display,
87-
/// but in practice, there were a lot of bugs to resolve.
88-
///
89-
/// For now, we hide it visually and expose it to screen readers.
90-
///
91-
.shcb-language {
92-
@include a11y.sr-only;
93-
}

src/vendor/highlight/demo/demo.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { basename, extname } from 'path';
2+
3+
import hljs from 'highlight.js';
4+
5+
// Load samples from directory and retrieve keys once
6+
const samplesDir = require.context('!!raw-loader!./samples', false);
7+
const sampleKeys = samplesDir.keys();
8+
9+
/**
10+
* Retrieve a code sample from the samples directory.
11+
*
12+
* @param {string} [language='html'] The slug of the language to retrieve a
13+
* sample for.
14+
* @returns {string} The contents of the sample.
15+
*/
16+
const getSample = (language = 'html') => {
17+
const key = sampleKeys.find(
18+
(key) => key.includes(`${language}.`) || key.endsWith(`.${language}`)
19+
);
20+
return key && samplesDir(key).default;
21+
};
22+
23+
/**
24+
* A list of supported language slugs.
25+
*/
26+
export const availableSamples = sampleKeys.map((key) =>
27+
basename(key, extname(key))
28+
);
29+
30+
/**
31+
* Syntax highlighting demo
32+
*
33+
* @param {object} args Demo options
34+
* @param {string} [args.language='html'] The slug of the language sample to
35+
* return a demo for.
36+
* @returns {string} A highlighted HTML snippet.
37+
*/
38+
export const highlightDemo = ({ language = 'html' }) => {
39+
const sample = getSample(language);
40+
// To improve accuracy, we use the language slug to explicitly set the
41+
// language, but only if Highlight reports that it supports that language.
42+
// Otherwise, we just fall back to auto-detecting the language.
43+
const highlighted = hljs.getLanguage(language)
44+
? hljs.highlight(sample, { language })
45+
: hljs.highlightAuto(sample);
46+
return `<pre><code class="language-${language}">${highlighted.value}</code></pre>`;
47+
};

src/vendor/highlight/demo/theme.twig

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
11
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
2-
import { basename, extname } from 'path';
3-
import hljs from 'highlight.js';
4-
import themeDemo from './demo/theme.twig';
5-
import legacyDemo from './demo/legacy.twig';
6-
// Load samples directory as "raw" text
7-
const samplesDir = require.context('!!raw-loader!./demo/samples', false);
8-
// Initialize an object to store sample content in
9-
const samples = {};
10-
// For each sample, fetch its content and build an object for our template
11-
for (const key of samplesDir.keys()) {
12-
const filename = basename(key);
13-
const ext = extname(key);
14-
const name = filename.replace(ext, '');
15-
const content = samplesDir(key).default;
16-
const language = [name, ext.slice(1)].find(
17-
(val) => hljs.getLanguage(val) !== undefined
18-
);
19-
const html = language && hljs.highlight(content, { language }).value;
20-
const languageName = language && hljs.getLanguage(language).name;
21-
samples[name] = { filename, name, ext, content, html, languageName };
22-
}
23-
const themeStory = (args) =>
24-
themeDemo({ ...args, ...samples[args.language || 'html'] });
2+
import { availableSamples, highlightDemo } from './demo/demo.ts';
253

264
<Meta
275
title="Vendor/Highlight"
@@ -33,7 +11,7 @@ const themeStory = (args) =>
3311
}}
3412
argTypes={{
3513
language: {
36-
options: Object.keys(samples),
14+
options: availableSamples,
3715
type: 'string',
3816
control: {
3917
type: 'select',
@@ -49,37 +27,13 @@ Our CSS includes a [Highlight.js](https://highlightjs.org/) theme for syntax hig
4927
For performance, it's strongly recommended to apply syntax highlighting on the server:
5028

5129
- [Highlight.js Basic Usage: Node.js on the Server](https://highlightjs.readthedocs.io/en/latest/readme.html#node-js-on-the-server)
52-
- [highlight.php](https://github.com/scrivo/highlight.php)
53-
- [WordPress Plugins: Syntax-highlighting Code Block](https://wordpress.org/plugins/syntax-highlighting-code-block/)
30+
- [Highlight.php](https://github.com/scrivo/highlight.php)
31+
- [Syntax-Highlighting Code Block](/?path=/docs/vendor-syntax-highlighting-code-block--basic)
5432

5533
<Canvas>
5634
<Story name="Theme" args={{ language: 'html' }}>
57-
{themeStory.bind()}
35+
{highlightDemo.bind({})}
5836
</Story>
5937
</Canvas>
6038

6139
<ArgsTable story="Theme" />
62-
63-
## Backwards Compatibility
64-
65-
As of this writing, [highlight.php is two major versions behind Highlight.js](https://github.com/scrivo/highlight.php/issues/73). As a result, syntax highlighting may differ between platforms.
66-
67-
Here is an example of JavaScript highlighted using highlight.php version 9.8.1.10.
68-
69-
<Canvas>
70-
<Story name="PHP Sample">{legacyDemo.bind()}</Story>
71-
</Canvas>
72-
73-
## Syntax-highlighting Code Block
74-
75-
The [Syntax-highlighting Code Block plugin for WordPress](https://wordpress.org/plugins/syntax-highlighting-code-block/) enhances the core code block with [highlight.php](https://github.com/scrivo/highlight.php). It also adds a few additional features, including a code language label for accessibility.
76-
77-
Currently, our theme only supports this code language label (though it hides it visually for simplicity). In the future we may also add support for highlighted lines and line numbers.
78-
79-
<Canvas>
80-
<Story name="SHCB" args={{ language: 'html' }}>
81-
{(args) => themeStory({ ...args, shcb: true })}
82-
</Story>
83-
</Canvas>
84-
85-
<ArgsTable story="SHCB" />

src/vendor/shcb/_shcb.scss

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
@use '../../compiled/tokens/scss/color';
2+
@use '../../compiled/tokens/scss/opacity';
3+
@use '../../mixins/a11y';
4+
@use '../../mixins/ms';
5+
@use '../../mixins/spacing';
6+
7+
/// Syntax-Highlighted Code Block Plugin Customizations
8+
9+
///
10+
/// The plugin adds some content showing the language the code is written in.
11+
/// This is helpful info, and we explored an interesting visual display,
12+
/// but in practice, there were a lot of bugs to resolve.
13+
///
14+
/// For now, we hide it visually and expose it to screen readers.
15+
///
16+
.shcb-language {
17+
@include a11y.sr-only;
18+
}
19+
20+
/// Allow lines to wrap when that option is selected
21+
.shcb-wrap-lines {
22+
&,
23+
.wp-block-code &,
24+
.wp-block-jetpack-markdown &,
25+
.legacy-post & {
26+
white-space: pre-wrap;
27+
}
28+
}
29+
30+
/// This class is used by SHCB in conjunction with `shcb-loc` to highlight
31+
/// individual lines of a code block. The plugin uses table styles, but those
32+
/// don't work very well if we want the highlighted lines to "bleed" out to the
33+
/// margins.
34+
.shcb-code-table {
35+
/// Negate the default `pre` padding
36+
margin-inline: ms.step(1) * -1;
37+
38+
/// Display contents as a grid. We apply this to a few WordPress containing
39+
/// elements as well: Otherwise, it is overridden.
40+
&,
41+
.wp-block-code &,
42+
.wp-block-jetpack-markdown &,
43+
.legacy-post & {
44+
display: grid;
45+
}
46+
47+
/// When this is in a WordPress block context, we negate the margin by a
48+
/// responsive amount corresponding to the default container inline padding.
49+
.wp-block-code &,
50+
.wp-block-jetpack-markdown &,
51+
.legacy-post & {
52+
@include spacing.fluid-margin-inline-negative;
53+
}
54+
}
55+
56+
/// When line numbers are applied, reset a counter per code block.
57+
.shcb-line-numbers {
58+
counter-reset: line;
59+
}
60+
61+
/// This wraps individual lines of code when we are in a "code table."
62+
.shcb-loc {
63+
/// Insures that default `mark` styles don't override highlighting.
64+
color: inherit;
65+
66+
/// Re-applies the default `pre` padding.
67+
padding-inline: ms.step(1);
68+
69+
/// If we're in a container that applies responsive margins, apply the
70+
/// responsive padding instead.
71+
.wp-block-code &,
72+
.wp-block-jetpack-markdown &,
73+
.legacy-post & {
74+
@include spacing.fluid-padding-inline;
75+
}
76+
77+
/// If line numbers are shown, increment the counter in this element, and
78+
/// apply a flex display so those elements will be side by side.
79+
.shcb-line-numbers & {
80+
counter-increment: line;
81+
display: flex;
82+
gap: 2ch;
83+
84+
/// Output the current line number. We assume this will rarely exceed three
85+
/// digits. In the future this might be simplified using `subgrid`, but
86+
/// experiments to do so selectively at this time resulted in some odd
87+
/// behavior when mixed with `pre` white space.
88+
&::before {
89+
content: counter(line);
90+
flex: none;
91+
min-inline-size: 2ch;
92+
opacity: opacity.$muted;
93+
text-align: end;
94+
}
95+
}
96+
}
97+
98+
/// For highlighted lines, we set the `background-color` to that of a default
99+
/// `pre` element, then use filters to differentiate it visually.
100+
mark.shcb-loc {
101+
background: color.$brand-primary-darker;
102+
filter: contrast(95%) brightness(150%) saturate(133%) hue-rotate(30deg);
103+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<pre><code class="hljs language-javascript"><span class="hljs-comment">// Sweet useless condition</span>
1+
<pre{% if class %} class="{{class}}"{% endif %} aria-describedby="shcb-language-15" data-shcb-language-name="JavaScript" data-shcb-language-slug="javascript"><div><code class="hljs language-javascript {% if wrapLines %}shcb-wrap-lines{% endif %}"><span class="hljs-comment">// Sweet useless condition</span>
22
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span> !== <span class="hljs-string">'that'</span> || truth == <span class="hljs-literal">false</span>) {
33
<span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-regexp">/ab+c/</span>, <span class="hljs-string">'i'</span>);
44
boyHowdy(<span class="hljs-number">5</span>, <span class="hljs-string">'something'</span>);
@@ -18,4 +18,4 @@
1818

1919
<span class="hljs-keyword">const</span> sum = <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span> {
2020
<span class="hljs-keyword">return</span> args.reduce(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a + b, <span class="hljs-number">0</span>);
21-
};</code></pre>
21+
};</code></div><small class="shcb-language" id="shcb-language-15"><span class="shcb-language__label">Code language:</span> <span class="shcb-language__name">JavaScript</span> <span class="shcb-language__paren">(</span><span class="shcb-language__slug">javascript</span><span class="shcb-language__paren">)</span></small></pre>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<pre{% if class %} class="{{class}}"{% endif %} aria-describedby="shcb-language-16" data-shcb-language-name="JavaScript" data-shcb-language-slug="javascript"><div><code class="hljs language-javascript shcb-code-table{% if lineNumbers %} shcb-line-numbers{% endif %}{% if wrapLines %} shcb-wrap-lines{% endif %}"><span class="shcb-loc"><span><span class="hljs-comment">// Sweet useless condition</span>
2+
</span></span><span class="shcb-loc"><span><span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span> !== <span class="hljs-string">'that'</span> || truth == <span class="hljs-literal">false</span>) {
3+
</span></span><mark class="shcb-loc"><span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-regexp">/ab+c/</span>, <span class="hljs-string">'i'</span>);
4+
</span></mark><span class="shcb-loc"><span> boyHowdy(<span class="hljs-number">5</span>, <span class="hljs-string">'something'</span>);
5+
</span></span><span class="shcb-loc"><span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'hello world'</span>);
6+
</span></span><span class="shcb-loc"><span> <span class="hljs-keyword">return</span> (<span class="hljs-number">2</span> * <span class="hljs-number">4</span>) / <span class="hljs-number">3</span>;
7+
</span></span><span class="shcb-loc"><span>}
8+
</span></span><span class="shcb-loc"><span>
9+
</span></span><span class="shcb-loc"><span><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bread</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Dough</span> </span>{
10+
</span></span><span class="shcb-loc"><span> <span class="hljs-keyword">constructor</span>(slices = 12) {
11+
</span></span><mark class="shcb-loc"><span> <span class="hljs-keyword">if</span> (slices &gt; <span class="hljs-number">24</span>) {
12+
</span></mark><mark class="shcb-loc"><span> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Too much bread'</span>);
13+
</span></mark><mark class="shcb-loc"><span> }
14+
</span></mark><span class="shcb-loc"><span>
15+
</span></span><span class="shcb-loc"><span> <span class="hljs-keyword">this</span>.slices = <span class="hljs-string">`There are <span class="hljs-subst">${slices}</span> slices`</span>;
16+
</span></span><span class="shcb-loc"><span> }
17+
</span></span><span class="shcb-loc"><span>}
18+
</span></span><span class="shcb-loc"><span>
19+
</span></span><span class="shcb-loc"><span><span class="hljs-keyword">const</span> sum = <span class="hljs-function">(<span class="hljs-params">...args</span>) =&gt;</span> {
20+
</span></span><span class="shcb-loc"><span> <span class="hljs-keyword">return</span> args.reduce(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a + b, <span class="hljs-number">0</span>);
21+
</span></span><span class="shcb-loc"><span>};
22+
</span></span></code></div><small class="shcb-language" id="shcb-language-16"><span class="shcb-language__label">Code language:</span> <span class="shcb-language__name">JavaScript</span> <span class="shcb-language__paren">(</span><span class="shcb-language__slug">javascript</span><span class="shcb-language__paren">)</span></small></pre>

src/vendor/shcb/shcb.stories.mdx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
2+
import baseDemo from './demo/base.twig';
3+
import codeTableDemo from './demo/code-table.twig';
4+
const argTypes = {
5+
class: {
6+
type: 'string',
7+
description: 'CSS class(es) to append to the root element.',
8+
},
9+
wrapLines: {
10+
type: 'boolean',
11+
description: 'Allow lines to wrap',
12+
},
13+
};
14+
15+
<Meta
16+
title="Vendor/Syntax-Highlighting Code Block"
17+
argTypes={argTypes}
18+
parameters={{
19+
docs: {
20+
transformSource: (src) => src,
21+
},
22+
layout: 'fullscreen',
23+
}}
24+
decorators={[
25+
(story) => {
26+
const result = story();
27+
if (result.includes('wp-block-code')) {
28+
return `<div class="o-container o-container--pad o-container--prose"><div class="o-container__content">${result}</div></div>`;
29+
}
30+
return result;
31+
},
32+
]}
33+
/>
34+
35+
# Syntax-Highlighting Code Block
36+
37+
The [Syntax-Highlighting Code Block](https://wordpress.org/plugins/syntax-highlighting-code-block/) is a plugin for WordPress that enhances the native code block with some additional functions. It uses [highlight.php](https://github.com/scrivo/highlight.php) to apply syntax highlighting on the server instead of the client.
38+
39+
This leverages the same styles [as our Highlight.js theme](/?path=/docs/vendor-highlight--theme). But as of this writing, [Highlight.php is two major versions behind Highlight.js](https://github.com/scrivo/highlight.php/issues/73). As a result, syntax highlighting may differ between platforms.
40+
41+
## Basic Output
42+
43+
Without any options selected, the plugin applies highlighting and appends a description of the code language for assistive devices (hidden visually).
44+
45+
<Canvas>
46+
<Story name="Basic">{(args) => baseDemo(args)}</Story>
47+
</Canvas>
48+
49+
<ArgsTable story="Basic" />
50+
51+
## More Features
52+
53+
The plugin also supports line wrapping, highlighted lines and line numbering. Our theme requires [default plugin styles to be suppressed](https://github.com/westonruter/syntax-highlighting-code-block/wiki#suppress-all-plugin-styles) so it won't conflict with these styles.
54+
55+
<Canvas>
56+
<Story
57+
name="More Features"
58+
argTypes={{
59+
...argTypes,
60+
lineNumbers: {
61+
type: 'boolean',
62+
description: 'Show numbers per line',
63+
},
64+
}}
65+
args={{ lineNumbers: true, wrapLines: true }}
66+
>
67+
{(args) => codeTableDemo(args)}
68+
</Story>
69+
</Canvas>
70+
71+
<ArgsTable story="More Features" />

0 commit comments

Comments
 (0)