@sherby/code-block
Version:
A web component that utilizes Prism.js and LitElement to display nicely formatted code.
111 lines (91 loc) • 3.27 kB
JavaScript
import { css, html, LitElement } from 'lit';
import 'prismjs/prism.js';
import { getFileOfAlias } from './get-file-of-alias';
import { getLanguageName } from './get-language-name';
export class CodeBlock extends LitElement {
static get properties() {
return {
defaultLanguage: { type: String },
language: { type: String },
languageFileTemplate: { type: String },
theme: { type: String },
themeFileTemplate: { type: String },
};
}
constructor() {
super();
this.defaultLanguage ??= 'markdown';
this.languageFileTemplate ??= `/node_modules/prismjs/components/prism-{LANGUAGE}.min.js`;
this.themeFileTemplate ??= `/node_modules/prismjs/themes/prism-{THEME}.css`;
}
async __loadLanguage() {
const language = this.language || this.defaultLanguage;
const languageFile = this.languageFileTemplate.replace('{LANGUAGE}', getFileOfAlias(language));
await import(languageFile);
}
async firstUpdated() {
await this.__loadLanguage();
const nodes = this.shadowRoot.querySelector('#code').assignedNodes();
let code = '';
for (let index = 0, len = nodes.length; index < len; ++index) {
const values = nodes[index].nodeValue.split('\n').map((x) => x.trimEnd());
// Remove empty lines at the beginning and at the end
if (!values[values.length - 1].trim()) {
values.pop();
}
if (!values[0].trim()) {
values.shift();
}
const numberOfSpacesOfFirstLine = values[0].search(/\S|$/);
const spacesRegex = new RegExp(` {${numberOfSpacesOfFirstLine}}`);
values.forEach((value) => {
code += value.replace(spacesRegex, '') + '\n';
});
}
// Set to our styled block
const language = this.language || this.defaultLanguage;
this.shadowRoot.querySelector('#output').innerHTML = Prism.highlight(code, Prism.languages[language], language);
}
static get styles() {
return css`
:host {
--code-block-language-color: #4ab9f8;
}
#hide {
display: none ;
}
pre {
position: relative;
}
pre:not([data-language-name='']) {
padding-top: 2em;
}
pre:not([data-language-name='']):before {
content: attr(data-language-name);
color: var(--code-block-language-color);
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-weight: 700;
letter-spacing: 1px;
position: absolute;
right: 1em;
top: 0.45em;
z-index: 1;
}
`;
}
render() {
const isDefaultTheme = !this.theme;
const searchValue = isDefaultTheme ? '-{THEME}' : '{THEME}';
const themeFile = this.themeFileTemplate.replace(searchValue, this.theme || '');
const language = this.language || this.defaultLanguage;
const languageName = this.language ? getLanguageName(language) : '';
return html`
<link rel="stylesheet" href="${themeFile}" />
<pre class="language-${language}" data-language-name="${languageName}"><code id="output"></code></pre>
<div id="hide">
<slot id="code"></slot>
</div>
`;
}
}