How to add a language icon
fneiraj opened this issue · comments
Hello, thank you for being here! :)
This should be possible using a simple custom plugin. The only thing we'd need is some icon source for the language icons. Do you have anything in mind? What icons are being used in your screenshot?
I don't know what image source the example I gave uses, I found these that may be useful:
https://github.com/donovanhiland/atom-file-icons
https://github.com/file-icons/atom
As a quick and dirty solution, you can create a custom plugin like this:
export function codeLanguagePlugin() {
return {
name: 'Displays the language of the code block',
baseStyles: `
.ec-line:first-child {
position: absolute;
right: -40px;
top: 5px;
color: gray;
font-size: 12px;
}
`,
hooks: {
postprocessAnalyzedCode: (context) => {
const shellLanguages = ['ansi', 'bash', 'bat', 'batch', 'cmd', 'console', 'powershell', 'ps', 'ps1', 'psd1',
'psm1', 'sh', 'shell', 'shellscript', 'shellsession', 'zsh'];
if (shellLanguages.includes(context.codeBlock.language)) {
context.codeBlock.insertLine(0, '');
return;
}
context.codeBlock.insertLine(0, context.codeBlock.language);
},
},
}
}
Ideally it should be in the figure
tag, and it should detect the frame properly instead of checking for shell languages.
@hippotastic could you provide us some guide to improve this please?
Thank you for jumping in with a draft for a solution, @davidmles!
The only reason why I didn't manage to post a solution to this question yet is that I didn't find a readily available and easy to integrate solution that has the icons in a style that would suit this use case.
So instead of further trying to find an icon-based solution, here's one that doesn't need icons. I created an example plugin that uses the postprocessRenderedBlock
hook instead, which might be a better fit for this use case:
// @ts-check
import { setProperty } from '@expressive-code/core'
/** @returns {import('@expressive-code/core').ExpressiveCodePlugin} */
export function pluginLanguageBadge() {
return {
name: 'Language Badge',
baseStyles: ({ cssVar }) => `
[data-language]::before {
position: absolute;
z-index: 2;
right: calc(${cssVar('borderWidth')} + ${cssVar('uiPaddingInline')} / 2);
top: calc(${cssVar('borderWidth')} + 0.35rem);
padding: 0.1rem 0.5rem;
box-shadow: 0 0 1px 1px ${cssVar('codeBackground')};
content: attr(data-language);
font-size: 0.75rem;
text-transform: uppercase;
color: white;
background: rebeccapurple;
border-radius: inherit;
pointer-events: none;
}
:not(.frame.has-title) .copy {
margin-right: 3rem;
}
`,
hooks: {
postprocessRenderedBlock: ({ codeBlock, renderData }) => {
setProperty(renderData.blockAst, 'data-language', codeBlock.language)
},
},
}
}
The result looks like this:
The solution adds a data-language
attribute to the wrapper element of each code block, and the accompanying CSS code creates a badge from that. You could even provide more specific CSS to style different languages individually.
Hope that helps!
Thanks @hippotastic! I love your solution.
As a slight improvement, I would replace this part:
:not(.frame.has-title) .copy {
margin-right: 3rem;
}
with:
.frame:not(.has-title):not(.is-terminal):hover[data-language]::before {
display: none;
}
so it makes the badge dissapear when the copy icon needs to be displayed in the same place.
Ohh, that's smart! Great idea!