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
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(/</g, "<")
.replace(/>/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;
}
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;
}
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";
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;
}
}
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>
);
}
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>
)
}
Top comments (0)