DEV Community

Cover image for Using your github readme on website
Dennis kinuthia
Dennis kinuthia

Posted on • Edited on

Using your github readme on website

I have a page in my portfolio that's supposed to load a repository and display it's details , but besides the name , languages and topics we don't have other details to describe it in-depth .
Most projects already have readmes which render really nicely on github with GFM , I want that on my page.

We'll start by fetching and parsing the readme into html .

npm i showdown highlight.js
npm i -D @types/showdown
Enter fullscreen mode Exit fullscreen mode
markdown parser

import showdown from 'showdown';
import hljs from 'highlight.js';




export function convertMarkdownToHtml(markdown: string): string {

    showdown.extension("highlight", function () {
        function htmlunencode(text: string): string {
            return text
                .replace(/&/g, "&")
                .replace(/&lt;/g, "<")
                .replace(/&gt;/g, ">");
        }
        return [
            {
                type: "output",
                filter: function (text: string, converter: any, options: any): string {
                    var left = "<pre><code\\b[^>]*>",
                        right = "</code></pre>",
                        flags = "g";
                    var replacement = function (
                        wholeMatch: string,
                        match: string,
                        left: string,
                        right: string,
                    ): string {
                        match = htmlunencode(match);
                        var lang = (left.match(/class=\"([^ \"]+)/) || [])[1];
                        left = left.slice(0, 18) + "hljs " + left.slice(18);
                        if (lang && hljs.getLanguage(lang)) {
                            return (
                                left + hljs.highlight(match, { language: lang }).value + right
                            );
                        } else {
                            return left + hljs.highlightAuto(match).value + right;
                        }
                    };
                    return showdown.helper.replaceRecursiveRegExp(
                        text,
                        replacement,
                        left,
                        right,
                        flags,
                    );
                },
            },
        ];
    });


    let converter = new showdown.Converter({
        ghCompatibleHeaderId: true,
        simpleLineBreaks: true,
        ghMentions: true,
        extensions: ['highlight'],
        tables: true
    });

    let preContent: string = `
    <html>
      <head>
        <title></title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta charset="UTF-8">

         <link rel="stylesheet"node_modules/highlight.js/styles/atom-one-dark.css">
        <script defer  src="https://plausible.io/js/script.js"></script>
      </head>
      <body>
        <div id=''>
      `;

    let postContent: string = `
        </div>
      </body>
    </html>`;

    let html: string = preContent + converter.makeHtml(markdown) + postContent;

    return html;
}

Enter fullscreen mode Exit fullscreen mode

Then we'll add some styles

.markdown {
  @apply text-base-content leading-normal break-words p-2;
  overflow-x: scroll;
  line-height: 2;
}

.markdown > * + * {
  @apply mt-0 mb-4;
}

.markdown li + li {
  @apply mt-1;
}

.markdown li > p + p {
  @apply mt-6 mb-2;
}

.markdown strong {
  @apply font-semibold;
}

.markdown a {
  @apply text-blue-600 font-semibold;
}
.markdown a:hover {
  @apply text-blue-400 font-semibold;
}

.markdown strong a {
  @apply font-bold;
}

.markdown h1 {
  @apply leading-tight border-b text-4xl font-semibold mb-4 mt-6 pb-2;
}

.markdown h2 {
  @apply leading-tight border-b text-2xl font-semibold mb-4 mt-6 pb-2;
}

.markdown h3 {
  @apply leading-snug text-lg font-semibold mb-4 mt-6;
}

.markdown h4 {
  @apply leading-none text-base font-semibold mb-4 mt-6;
}

.markdown h5 {
  @apply leading-tight text-sm font-semibold mb-4 mt-6;
}

.markdown h6 {
  @apply leading-tight text-sm font-semibold text-base-content mb-4 mt-6;
}

.markdown blockquote {
  @apply text-base border-l-4 border-base-200 px-3  text-base-content/70;
}

.markdown code {
  @apply font-mono text-sm inline bg-base-100/60 rounded-2xl px-1 py-2 my-5;
}

.markdown pre {
  @apply bg-base-200/60 rounded-2xl p-2;
}

.markdown pre code {
  @apply block bg-transparent p-0 overflow-visible rounded-2xl my-5;
}

.markdown ul {
  @apply text-base pl-8 list-disc;
}

.markdown ol {
  @apply text-base pl-4 list-decimal;
}

.markdown kbd {
  @apply text-xs inline-block rounded border px-1 py-5 align-middle font-normal font-mono shadow;
}

.markdown table {
  @apply text-base border-base-300;
}

.markdown th {
  @apply border font-bold text-lg py-1 px-3;
}

.markdown td {
  @apply border py-1 px-3;
}

/* Override pygments style background color. */
.markdown .highlight pre {
  @apply bg-base-200/40 !important;
}

.markdown pre {
  border-radius: "10%";
  font-size: 85%;
  line-height: 1.8;
  overflow: auto;
}

code.hljs {
  padding: 3px 5px;
}
/*

Atom One Dark by Daniel Gamage
Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax

base:    #282c34
mono-1:  #abb2bf
mono-2:  #818896
mono-3:  #5c6370
hue-1:   #56b6c2
hue-2:   #61aeee
hue-3:   #c678dd
hue-4:   #98c379
hue-5:   #e06c75
hue-5-2: #be5046
hue-6:   #d19a66
hue-6-2: #e6c07b

*/
.hljs {
  color: #abb2bf;
  background: #282c34;
}
.hljs-comment,
.hljs-quote {
  color: #5c6370;
  font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
  color: #c678dd;
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
  color: #e06c75;
}
.hljs-literal {
  color: #56b6c2;
}
.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta .hljs-string {
  color: #98c379;
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
  color: #d19a66;
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
  color: #61aeee;
}
.hljs-built_in,
.hljs-title.class_,
.hljs-class .hljs-title {
  color: #e6c07b;
}
.hljs-emphasis {
  font-style: italic;
}
.hljs-strong {
  font-weight: bold;
}
.hljs-link {
  text-decoration: underline;
}
Enter fullscreen mode Exit fullscreen mode

import styles into your project (most projects, Nextjs or Vite require all stylesheets to be added at the root layout or main.tsx component )

layout.tsx

import "./globals.css";
import "../state/md/markdown.css";
Enter fullscreen mode Exit fullscreen mode

Fetch the readme from github and pass the string into the parser

import { convertMarkdownToHtml } from "@/state/md/parse";

interface GetRepoREADME {
  repo: string;
  owner: string;
}
export async function getGithubREADME({ repo, owner }: GetRepoREADME) {
  try {
    const response = await fetch(
      `https://raw.githubusercontent.com/${owner}/${repo}/main/README.md`
    );
    if (!response.ok) {
      throw new Error(response.statusText);
    }
    const text = await response.text();
    if (!text) {
      throw new Error("no parsable readme");
    }
    const output_html = convertMarkdownToHtml(text);
    return output_html;
  } catch (error) {
    console.log(" === error === ", error);
    return;
  }
}
Enter fullscreen mode Exit fullscreen mode

We can the use this in our project , am using Nextjs but you can use whatever you want.

import { getGithubREADME } from "../helpers/getOneRepomarkdown";

interface OneGithubRepoREADMEProps {
  repo: string;
  owner: string;
}

export async function OneGithubRepoREADME({owner,repo}:OneGithubRepoREADMEProps){
    const data = await getGithubREADME({owner,repo}) 

    if (!data ) {
      return null;
    }

return (
 <div id="readme" className='w-[95%] md:w-[85%]  h-full  
 bg-base-200/30 p-5 rounded-xl '>
  <h2 className="text-2xl font-bold text-start w-full capitalize">{repo} readme</h2>
   <div className="markdown" dangerouslySetInnerHTML={{ __html: data}} />
 </div>
);
}

Enter fullscreen mode Exit fullscreen mode

This code works on browser or Nodejs and even server components

You can customize the styles by picking another theme from node_modules/highlight.js/styles and replacing the existing one in the provided CSS (all that start with hljs-)

Shout out to KrauseFx for the code

One extra thing we can add is a stackblitz component to let the visitor play around with our code directly in the browser using the @stackblitz/sdk package


"use client"
import sdk from "@stackblitz/sdk";
import { useEffect } from "react";

interface stackblitzEmbedProps {
    repo: string;
    owner: string;

}

export function StackblitzEmbed({owner,repo}: stackblitzEmbedProps) {
  const selectedRepo = {
    github: `${owner}/${repo}`,
    openFile: "README.md",
  };

  useEffect(() => {
    sdk.embedGithubProject("embed", selectedRepo.github, {
      height: 1000,
      clickToLoad: true,
      // openFile: selectedRepo.openFile,
    });
  }, [selectedRepo.github]);

  /**
   * Open the project in a new window on StackBlitz
   */
  function openProject() {
    sdk.openGithubProject(selectedRepo.github, {
      // openFile: selectedRepo.openFile,
    });
  }

  return (
  <div id="stackblitz" className="w-full h-full relative">
    <button className="btn btn-sm btn-outline hover:bg-secondary absolute top-[1%] right-[2%]" 
    onClick={openProject} >
      Open in new window
    </button>
    <div id="embed" className="mt-5 p-5 w-[95%] h-full flex items-center justify-center">B</div>

  </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Image description

Javascript , I love it

Top comments (0)