Using your github readme on website

tigawanna

Dennis kinuthia

Posted on January 15, 2024

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

💖 💪 🙅 🚩
tigawanna
Dennis kinuthia

Posted on January 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Using your github readme on website
markdown Using your github readme on website

January 15, 2024