1+ import type { Root } from "mdast" ;
2+ import type { Plugin } from "unified" ;
3+ import { visit } from "unist-util-visit" ;
4+ import type { Directives } from 'mdast-util-directive'
5+ import type { Node , Paragraph as P } from 'mdast'
6+ import { h as _h , type Properties } from 'hastscript'
7+
8+ /** Checks if a node is a directive. */
9+ function isNodeDirective ( node : Node ) : node is Directives {
10+ return (
11+ node . type === 'containerDirective' ||
12+ node . type === 'leafDirective' ||
13+ node . type === 'textDirective'
14+ )
15+ }
16+
17+ /** From Astro Starlight: Function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
18+ function h ( el : string , attrs : Properties = { } , children : any [ ] = [ ] ) : P {
19+ const { properties, tagName } = _h ( el , attrs )
20+ return {
21+ children,
22+ data : { hName : tagName , hProperties : properties } ,
23+ type : 'paragraph' ,
24+ }
25+ }
26+
27+ const DIRECTIVE_NAME = "github" ;
28+
29+ export const remarkGithubCard : Plugin < [ ] , Root > = ( ) => ( tree ) => {
30+ visit ( tree , ( node , index , parent ) => {
31+ if ( ! parent || index === undefined || ! isNodeDirective ( node ) ) return ;
32+
33+ // We only want a leaf directive named DIRECTIVE_NAME
34+ if ( node . type !== "leafDirective" || node . name !== DIRECTIVE_NAME ) return ;
35+
36+ let repoName = node . attributes ?. repo ?? node . attributes ?. user ?? null ;
37+ if ( ! repoName ) return ; // Leave the directive as-is if no repo is provided
38+
39+ repoName = repoName . endsWith ( "/" ) ? repoName . slice ( 0 , - 1 ) : repoName ; // Remove trailing slash
40+ repoName = repoName . startsWith ( "https://github.com/" )
41+ ? repoName . replace ( "https://github.com/" , "" )
42+ : repoName ; // Remove leading URL
43+
44+ const repoParts = repoName . split ( "/" ) ;
45+ const SimpleUUID = `GC-${ crypto . randomUUID ( ) } ` ;
46+ const realUrl = `https://github.com/${ repoName } ` ;
47+
48+ // If its a repo link
49+ if ( repoParts . length > 1 ) {
50+ const script = h ( "script" , { } , [
51+ {
52+ type : "text" ,
53+ value : `
54+ fetch('https://api.github.com/repos/${ repoName } ', { referrerPolicy: "no-referrer" })
55+ .then(response => response.json())
56+ .then(data => {
57+ const t = document.getElementById('${ SimpleUUID } ');
58+ t.classList.remove("gh-loading");
59+
60+ if (data.description) {
61+ t.querySelector('.gh-description').innerText = data.description.replace(/:[a-zA-Z0-9_]+:/g, '');
62+ } else {
63+ t.querySelector('.gh-description').style.display = 'none';
64+ }
65+ if (data.language) t.querySelector('.gh-language').innerText = data.language;
66+ t.querySelector('.gh-forks').innerText = Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(data.forks).replaceAll("\u202f", '');
67+ t.querySelector('.gh-stars').innerText = Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(data.stargazers_count).replaceAll("\u202f", '');
68+ const avatarEl = t.querySelector('.gh-avatar');
69+ avatarEl.style.backgroundImage = 'url(' + data.owner.avatar_url + ')';
70+
71+ if (data.license?.spdx_id) {
72+ t.querySelector('.gh-license').innerText = data.license?.spdx_id
73+ } else {
74+ t.querySelector('.gh-license').style.display = 'none';
75+ };
76+ })
77+ .catch(err => {
78+ document.getElementById('${ SimpleUUID } ').classList.add("gh-error")
79+ console.warn("[GITHUB-CARD] Error loading card for ${ repoName } | ${ SimpleUUID } .", err)
80+ })
81+ ` ,
82+ } ,
83+ ] ) ;
84+
85+ const hTitle = h ( "div" , { class : "gh-title title" } , [
86+ h ( "span" , { class : "gh-avatar" } ) ,
87+ h ( "a" , { class : "gh-text not-prose cactus-link" , href : realUrl } , [
88+ { type : "text" , value : `${ repoParts [ 0 ] } /${ repoParts [ 1 ] } ` } ,
89+ ] ) ,
90+ h ( "span" , { class : "gh-icon" } ) ,
91+ ] ) ;
92+
93+ const hChips = h ( "div" , { class : "gh-chips" } , [
94+ h ( "span" , { class : "gh-stars" } , [ { type : "text" , value : "00K" } ] ) ,
95+ h ( "span" , { class : "gh-forks" } , [ { type : "text" , value : "00K" } ] ) ,
96+ h ( "span" , { class : "gh-license" } , [ { type : "text" , value : "MIT" } ] ) ,
97+ h ( "span" , { class : "gh-language" } , [ { type : "text" , value : "" } ] ) ,
98+ ] ) ;
99+
100+ const hDescription = h ( "div" , { class : "gh-description" } , [
101+ {
102+ type : "text" ,
103+ value : "Lorem ipsum dolor sit amet, consectetur adipiscing elit." ,
104+ } ,
105+ ] ) ;
106+
107+ parent . children . splice (
108+ index ,
109+ 1 ,
110+ h ( "div" , { id : SimpleUUID , class : "github-card gh-loading" } , [
111+ hTitle ,
112+ hDescription ,
113+ hChips ,
114+ script ,
115+ ] ) ,
116+ ) ;
117+ }
118+
119+ // If its a user link
120+ else if ( repoParts . length === 1 ) {
121+ const script = h ( "script" , { } , [
122+ {
123+ type : "text" ,
124+ value : `
125+ fetch('https://api.github.com/users/${ repoName } ', { referrerPolicy: "no-referrer" })
126+ .then(response => response.json())
127+ .then(data => {
128+ const t = document.getElementById('${ SimpleUUID } ');
129+ t.classList.remove("gh-loading");
130+
131+ const avatarEl = t.querySelector('.gh-avatar');
132+ avatarEl.style.backgroundImage = 'url(' + data.avatar_url + ')';
133+ t.querySelector('.gh-followers').innerText = Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(data.followers).replaceAll("\u202f", '');
134+ t.querySelector('.gh-repositories').innerText = Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(data.public_repos).replaceAll("\u202f", '');
135+ if (data.location) t.querySelector('.gh-region').innerText = data.location;
136+
137+ })
138+ .catch(err => {
139+ const c = document.getElementById('${ SimpleUUID } ').classList.add("gh-error")
140+ console.warn("[GITHUB-CARD] Error loading card for ${ repoName } | ${ SimpleUUID } .", err)
141+ })
142+ ` ,
143+ } ,
144+ ] ) ;
145+
146+ parent . children . splice (
147+ index ,
148+ 1 ,
149+ h ( "div" , { id : SimpleUUID , class : "github-card gh-simple gh-loading" } , [
150+ h ( "div" , { class : "gh-title title" } , [
151+ h ( "span" , { class : "gh-avatar" } ) ,
152+ h ( "a" , { class : "gh-text not-prose cactus-link" , href : realUrl } , [
153+ { type : "text" , value : repoParts [ 0 ] } ,
154+ ] ) ,
155+ h ( "span" , { class : "gh-icon" } ) ,
156+ ] ) ,
157+ h ( "div" , { class : "gh-chips" } , [
158+ h ( "span" , { class : "gh-followers" } , [ { type : "text" , value : "00K" } ] ) ,
159+ h ( "span" , { class : "gh-repositories" } , [ { type : "text" , value : "00K" } ] ) ,
160+ h ( "span" , { class : "gh-region" } , [ { type : "text" , value : "" } ] ) ,
161+ ] ) ,
162+ script ,
163+ ] ) ,
164+ ) ;
165+ }
166+ } ) ;
167+ } ;
0 commit comments