expressive-code / expressive-code

A text marking & annotation engine for presenting source code on the web.

Home Page:https://expressive-code.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to add a language icon

fneiraj opened this issue · comments

Hello, first of all thank you for this incredible project :)

I wanted to see if it's possible to add a block code language icon, something like this:

image

thanks in advance!

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:

image

image

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.

replacing the badge not replacing the badge

Ohh, that's smart! Great idea!