Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(code-block): prism prerendered highlighting #1906

Merged
merged 5 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/great-adults-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@rhds/elements": minor
---
`<rh-code-block>`: syntax highlighting via prerendered prismjs code-blocks. Use
the `highlighting="prerendered"` attribute when rendering code blocks using
server side prism, e.g. in a markdown fenced code block.

```html
<rh-code-block highlighting="prerendered">
<pre class="language-css"><code class="language-css"><span class="token selector">a</span> <span class="token punctuation">{</span>
<span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rh-color-interactive-primary-default<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></pre>
</rh-code-block>
```
2 changes: 1 addition & 1 deletion elements/rh-badge/rh-badge.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ span {
font-size: var(--rh-font-size-body-text-xs, 0.75rem);
font-weight: 700;
line-height: var(--rh-line-height-body-text, 1.5);
padding-inline: var(--rh-space-md, 8px);
padding-inline: var(--_badge-padding, var(--rh-space-md, 8px));
}

.on.dark { background-color: var(--rh-color-surface-darker, #1f1f1f); }
Expand Down
4 changes: 2 additions & 2 deletions elements/rh-code-block/demo/callout-badges.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<rh-code-block>
<script type="text/html"><p>Script tags in HTML must be escaped</p></script>
<rh-badge state="info"">1</rh-badge>
<rh-badge state="info">1</rh-badge>
<script type="text/html">
<p>other tags do not need to be escaped</p>
<marquee>Callouts can be placed </script><rh-badge state="info"">2</rh-badge><script type="text/html">inline</marquee>
<marquee>Callouts can be placed </script><rh-badge state="info">2</rh-badge><script type="text/html">inline</marquee>
</script>
<dl slot="legend">
<dt><rh-badge state="info">1</rh-badge></dt>
Expand Down
75 changes: 75 additions & 0 deletions elements/rh-code-block/demo/prerendered-prism-highlighting.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<rh-context-demo>
<rh-code-block highlighting="prerendered">
<pre class="language-html"><code class="language-html"><span class="token doctype"><span class="token punctuation">&lt;!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">html</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>utf-8<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>viewport<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>width=device-width<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">&gt;</span></span>Cards Galore!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rh-card</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Card<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nullam eleifend elit sed est egestas, a sollicitudin mauris
tincidunt. Pellentesque vel dapibus risus. Nullam aliquam
felis orci, eget cursus mi lacinia quis. Vivamus at felis sem.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rh-cta</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>footer<span class="token punctuation">"</span></span> <span class="token attr-name">priority</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>primary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Call to action<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rh-cta</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rh-card</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">&gt;</span></span></code></pre>
</rh-code-block>

<rh-code-block highlighting="prerendered">
<pre class="language-css"><code class="language-css"><span class="token selector">rh-card.avatar-card</span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> 360px<span class="token punctuation">;</span>
<span class="token selector">&amp;::part(body)</span> <span class="token punctuation">{</span>
<span class="token property">margin-block-start</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rh-space-lg<span class="token punctuation">,</span> 16px<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">&amp; p</span> <span class="token punctuation">{</span>
<span class="token property">margin-block-start</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token selector">&amp; h4</span> <span class="token punctuation">{</span>
<span class="token property">font-weight</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rh-font-weight-heading-regular<span class="token punctuation">,</span> 300<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rh-font-size-body-text-md<span class="token punctuation">,</span> 1rem<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rh-font-family-body-text<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rh-line-height-body-text<span class="token punctuation">,</span> 1.5<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
</rh-code-block>

<rh-code-block highlighting="prerendered">
<pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">extends</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> stylelint<span class="token punctuation">-</span>config<span class="token punctuation">-</span>standard
<span class="token punctuation">-</span> <span class="token string">'@stylistic/stylelint-config'</span>

<span class="token key atrule">plugins</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> ./node_modules/@rhds/tokens/plugins/stylelint.js
<span class="token punctuation">-</span> <span class="token string">'@stylistic/stylelint-plugin'</span>

<span class="token key atrule">rules</span><span class="token punctuation">:</span>
<span class="token key atrule">rhds/token-values</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
<span class="token key atrule">rhds/no-unknown-token-name</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> <span class="token boolean important">true</span>
<span class="token punctuation">-</span> <span class="token key atrule">allowed</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span> <span class="token punctuation">-</span><span class="token punctuation">-</span>rh<span class="token punctuation">-</span>icon<span class="token punctuation">-</span>size</code></pre>
</rh-code-block>
</rh-context-demo>

<script type="module">
import '@rhds/elements/lib/elements/rh-context-demo/rh-context-demo.js';
import '@rhds/elements/rh-code-block/rh-code-block.js';
</script>

<style>
rh-context-demo::part(demo) {
display: grid;
gap: var(--rh-space-sm, 6px);
}
</style>
133 changes: 71 additions & 62 deletions elements/rh-code-block/prism.css.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,82 @@
import { css } from 'lit';

export const prismStyles = css` code[class*="language-"],
pre[class*="language-"] {
color: var(--_code-color);
font-family: var(--rh-font-family-code, RedHatMono, "Red Hat Mono", "Courier New", Courier, monospace);
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: var(--rh-line-height-code, 1.5);
tab-size: 4;
hyphens: none;
}

pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: var(--_selected-text-background);
}
const styles = css`
& code[class*="language-"],
& pre[class*="language-"] {
color: var(--_code-color);
font-family: var(--rh-font-family-code, RedHatMono, "Red Hat Mono", "Courier New", Courier, monospace);
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: var(--rh-line-height-code, 1.5);
tab-size: 4;
hyphens: none;
background: transparent;
}

@media print {
code[class*="language-"],
pre[class*="language-"] {
& pre[class*="language-"]::selection,
& pre[class*="language-"] ::selection,
& code[class*="language-"]::selection,
& code[class*="language-"] ::selection {
text-shadow: none;
background: var(--_selected-text-background);
}

@media print {
& code[class*="language-"],
& pre[class*="language-"] {
text-shadow: none;
}
}
}

.token.atrule { color: var(--_at-rule-color); }
.token.attr-name { color: var(--_attr-name-color); }
.token.attr-value { color: var(--_attr-value-color); }
.token.bold { font-weight: var(--_important-color); }
.token.boolean { color: var(--_boolean-color); }
.token.builtin { color: var(--_built-in-color); }
.token.cdata { color: var(--_cdata-color); }
.token.char { color: var(--_character-color); }
.token.class-name { color: var(--_class-name-color); }
.token.comment { color: var(--_comment-color); }
.token.constant { color: var(--_constant-color); }
.token.deleted { color: var(--_deleted-color); }
.token.function { color: var(--_function-name-color); }
.token.important { color: var(--_important-color); }
.token.inserted { color: var(--_inserted-color); }
.token.keyword { color: var(--_keyword-color); }
.token.namespace { color: var(--_namespace-color); }
.token.number { color: var(--_number-color); }
.token.operator { color: var(--_operator-color); }
.token.property { color: var(--_property-color); }
.token.punctuation { color: var(--_punctuation-color); }
.token.regex { color: var(--_regex-color); }
.token.selector { color: var(--_selector-color); }
.token.string { color: var(--_string-color); }
.token.symbol { color: var(--_symbol-color); }
.token.tag { color: var(--_tag-color); }
.token.url { color: var(--_url-color); }
.token.variable { color: var(--_variable-color); }
& .token.atrule { color: var(--_at-rule-color); }
& .token.attr-name { color: var(--_attr-name-color); }
& .token.attr-value { color: var(--_attr-value-color); }
& .token.bold { font-weight: var(--_important-color); }
& .token.boolean { color: var(--_boolean-color); }
& .token.builtin { color: var(--_built-in-color); }
& .token.cdata { color: var(--_cdata-color); }
& .token.char { color: var(--_character-color); }
& .token.class-name { color: var(--_class-name-color); }
& .token.comment { color: var(--_comment-color); }
& .token.constant { color: var(--_constant-color); }
& .token.deleted { color: var(--_deleted-color); }
& .token.function { color: var(--_function-name-color); }
& .token.important { color: var(--_important-color); }
& .token.inserted { color: var(--_inserted-color); }
& .token.keyword { color: var(--_keyword-color); }
& .token.namespace { color: var(--_namespace-color); }
& .token.number { color: var(--_number-color); }
& .token.operator { color: var(--_operator-color); }
& .token.property { color: var(--_property-color); }
& .token.punctuation { color: var(--_punctuation-color); }
& .token.regex { color: var(--_regex-color); }
& .token.selector { color: var(--_selector-color); }
& .token.string { color: var(--_string-color); }
& .token.symbol { color: var(--_symbol-color); }
& .token.tag { color: var(--_tag-color); }
& .token.url { color: var(--_url-color); }
& .token.variable { color: var(--_variable-color); }

.token.italic { font-style: italic; }
& .token.italic { font-style: italic; }

.token.entity {
color: var(--_entity-color);
cursor: help;
}
& .token.entity {
color: var(--_entity-color);
cursor: help;
}

.token.prolog,
.token.doctype { color: var(--_doctype-color); }
& .token.prolog,
& .token.doctype { color: var(--_doctype-color); }

.language-css .token.string,
.style .token.string { color: var(--_operator-color); }
& .language-css .token.string,
& .style .token.string { color: var(--_operator-color); }
`;

export const prismStyles = css`#prism-output {${styles}}`;
export const preRenderedLightDomStyles = css`rh-code-block {
--_styles-applied: true;
${styles}
& > pre { opacity: 1; }
}`;
7 changes: 3 additions & 4 deletions elements/rh-code-block/prism.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { RhCodeBlock } from './rh-code-block.js';
import { Prism } from 'prism-esm';
import { Plugin as LineNumbers } from 'prism-esm/plugins/line-numbers/prism-line-numbers.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { html } from 'lit';

const prism = new Prism({ manual: true });
LineNumbers(prism);

/**
* Autoload a supported language
Expand Down Expand Up @@ -37,8 +36,8 @@ async function autoloader(language: RhCodeBlock['language']) {
export async function highlight(textContent: string, language: RhCodeBlock['language']) {
await autoloader(language);
const highlighted = prism.highlight(textContent, prism.languages[language!], language!);
return unsafeHTML(highlighted);
return html`<code class="language-${language}">${unsafeHTML(highlighted)}</code>`;
}

export { prismStyles } from './prism.css.js';
export { prismStyles, preRenderedLightDomStyles } from './prism.css.js';

26 changes: 26 additions & 0 deletions elements/rh-code-block/rh-code-block.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
--rh-code-block-callout-size: var(--rh-size-icon-02, 24px);
--_aspect-ratio: 1;
--_badge-size: var(--rh-code-block-callout-size);
--_badge-padding: 0;

display: block;
max-width: 1000px;
Expand All @@ -25,6 +26,14 @@
border: none !important;
}

:host([highlighting='prerendered']) ::slotted(pre) {
opacity: 0;
transition:
opacity
var(--rh-animation-speed)
var(--rh-animation-timing, cubic-bezier(0.465, 0.183, 0.153, 0.946));
}

.shadow-fab {
display: flex;
align-items: center;
Expand Down Expand Up @@ -63,6 +72,13 @@

#prism-output {
margin: 0;

& code {
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
}

#container {
Expand Down Expand Up @@ -143,6 +159,7 @@
opacity: 0;
pointer-events: none;
z-index: -10000;
line-height: var(--rh-line-height-code, 1.5);
}

#line-numbers {
Expand All @@ -157,6 +174,11 @@
color: var(--rh-color-text-secondary);
font-weight: var(--rh-font-weight-code-regular, 400);
border-inline-end: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle);

li {
line-height: var(--rh-line-height-code, 1.5);
display: block;
}
}

#actions {
Expand Down Expand Up @@ -330,3 +352,7 @@
--_important-color: var(--rh-color-purple-30, #b6a6e9);
--_variable-color: var(--rh-color-green-40, #87bb62);
}

:host([highlighting='client']) #content::slotted(:is(script, pre)) {
display: none;
}
Loading
Loading