33< head >
44< meta charset ="UTF-8 ">
55< meta name ="viewport " content ="width=device-width,initial-scale=1 ">
6- < title > Code By Sushil</ title >
6+ < title > Code By Sushil | Articles </ title >
77
88< link href ="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap " rel ="stylesheet ">
9+ < link href ="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel ="stylesheet ">
10+
911< style >
1012body {margin : 0 ;font-family : 'Inter' , sans-serif;background : # 0f1720 ;color : # e6eef8 ;line-height : 1.6 ;}
1113.wrap {max-width : 1000px ;margin : 32px auto;padding : 20px ;}
1214h1 {text-align : center;margin-bottom : 32px ;}
1315.articles-grid {display : grid;grid-template-columns : repeat (auto-fill, minmax (280px , 1fr ));gap : 20px ;}
14- .article-card {background : rgba (255 , 255 , 255 , 0.02 );border-radius : 14px ;padding : 20px ;border : 1px solid rgba (255 , 255 , 255 , 0.03 );transition : 0.2s ;}
16+ .article-card {background : rgba (255 , 255 , 255 , 0.02 );border-radius : 14px ;padding : 20px ;border : 1px solid rgba (255 , 255 , 255 , 0.03 );cursor : pointer; transition : 0.2s ;}
1517.article-card : hover {transform : translateY (-4px );box-shadow : 0 12px 24px rgba (0 , 0 , 0 , 0.3 );}
1618.article-card h2 {margin-top : 0 ;color : # 7c5cff ;}
1719.article-card p {color : # 9aa4b2 ;}
1820.article-card .meta {font-size : 0.85rem ;color : # 9aa4b2 ;margin-top : 10px ;}
1921.article-card .tags {margin-top : 8px ;font-size : 0.8rem ;color : # 7c5cff ;}
20- .article-card a {text-decoration : none;color : inherit;}
22+
23+ /* Modal */
24+ # article-modal {position : fixed;top : 0 ;left : 0 ;width : 100% ;height : 100% ;background : rgba (15 , 23 , 32 , 0.95 );color : # e6eef8 ;overflow : auto;display : none;flex-direction : column;padding : 20px ;z-index : 999 ;}
25+ # article-modal .close {align-self : flex-end;font-size : 28px ;cursor : pointer;margin-bottom : 12px ;}
26+ # article-modal h1 {margin-top : 0 ;}
27+ # article-modal .article-meta {display : flex;gap : 12px ;flex-wrap : wrap;margin-bottom : 16px ;color : # 9aa4b2 ;font-size : 0.9rem ;}
28+ # article-modal img .cover {max-width : 100% ;border-radius : 12px ;margin : 16px 0 ;}
29+ .article-body h1 , h2 , h3 , h4 , h5 , h6 {color : # e6eef8 ;margin-top : 1.2rem ;margin-bottom : 0.6rem ;}
30+ .article-body p {margin : 0.6rem 0 ;}
31+ .article-body a {color : # 7c5cff ;}
32+ .article-body pre {background : # 1e293b ;padding : 12px ;border-radius : 8px ;overflow : auto;}
33+
34+ /* Navigation buttons */
35+ .nav-buttons {display : flex;justify-content : space-between;margin-top : 12px ;}
36+ .nav-buttons button {background : # 7c5cff ;color : # fff ;border : none;padding : 8px 16px ;border-radius : 8px ;cursor : pointer;transition : 0.2s ;}
37+ .nav-buttons button : hover {background : # 5a3edc ;}
2138</ style >
2239</ head >
2340< body >
@@ -26,7 +43,35 @@ <h1>Articles</h1>
2643< div class ="articles-grid " id ="articles-grid "> </ div >
2744</ div >
2845
46+ <!-- Article Modal -->
47+ < div id ="article-modal ">
48+ < span class ="close " id ="modal-close "> ×</ span >
49+ < h1 id ="modal-title "> </ h1 >
50+ < div class ="article-meta ">
51+ < span id ="modal-author "> </ span >
52+ < span id ="modal-date "> </ span >
53+ < span id ="modal-readtime "> </ span >
54+ < span id ="modal-tags "> </ span >
55+ < span id ="modal-views "> </ span >
56+ </ div >
57+ < img id ="modal-cover " class ="cover " src ="" alt ="Cover " style ="display:none; ">
58+ < div id ="article-content " class ="article-body "> </ div >
59+ < div class ="nav-buttons ">
60+ < button id ="prev-article "> ← Previous</ button >
61+ < button id ="next-article "> Next →</ button >
62+ </ div >
63+ </ div >
64+
65+ < script src ="https://cdn.jsdelivr.net/npm/marked/marked.min.js "> </ script >
66+ < script src ="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js "> </ script >
67+
2968< script >
69+ // --- GitHub Config ---
70+ const GITHUB_USER = 'codebysushil' ;
71+ const GITHUB_REPO = "codebysushil.github.io" ;
72+ const ARTICLES_PATH = 'articles' ;
73+ const BRANCH = 'main' ;
74+
3075// --- Helpers ---
3176function parseFrontMatter ( md ) {
3277 const fmRegex = / ^ - - - \s * ( [ \s \S ] * ?) \s * - - - \s * / ;
@@ -43,53 +88,103 @@ <h1>Articles</h1>
4388 return { fm, content} ;
4489}
4590function formatDate ( iso ) { try { const d = new Date ( iso ) ; return isNaN ( d ) ?iso :d . toLocaleDateString ( undefined , { year :'numeric' , month :'short' , day :'numeric' } ) ; } catch ( e ) { return iso } }
91+ function estimateReadTime ( text ) { const words = text . trim ( ) . split ( / \s + / ) . length ; return Math . max ( 1 , Math . round ( words / 200 ) ) + ' min read' }
92+ async function incAndGetViews ( key ) {
93+ try {
94+ const res = await fetch ( `https://api.countapi.xyz/hit/${ GITHUB_USER } -${ GITHUB_REPO } /${ key } ` ) ;
95+ const json = await res . json ( ) ;
96+ return json . value || 0 ;
97+ } catch ( e ) { return '—' }
98+ }
99+
100+ // --- DOM Elements ---
101+ const grid = document . getElementById ( 'articles-grid' ) ;
102+ const modal = document . getElementById ( 'article-modal' ) ;
103+ const modalClose = document . getElementById ( 'modal-close' ) ;
104+ const modalTitle = document . getElementById ( 'modal-title' ) ;
105+ const modalAuthor = document . getElementById ( 'modal-author' ) ;
106+ const modalDate = document . getElementById ( 'modal-date' ) ;
107+ const modalReadtime = document . getElementById ( 'modal-readtime' ) ;
108+ const modalTags = document . getElementById ( 'modal-tags' ) ;
109+ const modalViews = document . getElementById ( 'modal-views' ) ;
110+ const modalCover = document . getElementById ( 'modal-cover' ) ;
111+ const articleContent = document . getElementById ( 'article-content' ) ;
112+ const prevBtn = document . getElementById ( 'prev-article' ) ;
113+ const nextBtn = document . getElementById ( 'next-article' ) ;
46114
47- // --- Config ---
48- const GITHUB_USER = 'codebysushil' ; // your GitHub username
49- const GITHUB_REPO = "codebysushil.github.io" ; // your repository name
50- const ARTICLES_PATH = 'articles' ; // folder containing markdown files
51- const BRANCH = 'main' ; // default branch
115+ let articlesData = [ ] ; // store all articles
116+ let currentIndex = 0 ; // current article in modal
52117
118+ // --- Load articles dynamically ---
53119( async function ( ) {
54- const grid = document . getElementById ( 'articles-grid' ) ;
55120 try {
56- // GitHub API: list files in articles folder
57- const apiUrl = `https://api.github.com/repos/${ GITHUB_USER } /${ GITHUB_REPO } /contents/${ ARTICLES_PATH } ?ref=${ BRANCH } ` ;
58- const res = await fetch ( apiUrl ) ;
59- if ( ! res . ok ) throw new Error ( 'Failed to fetch repo files' ) ;
60- const files = await res . json ( ) ;
61- const mdFiles = files . filter ( f => f . name . endsWith ( '.md' ) ) ;
62-
121+ const apiUrl = `https://api.github.com/repos/${ GITHUB_USER } /${ GITHUB_REPO } /contents/${ ARTICLES_PATH } ?ref=${ BRANCH } ` ;
122+ const res = await fetch ( apiUrl ) ;
123+ if ( ! res . ok ) throw new Error ( 'Failed to fetch articles list' ) ;
124+ const files = await res . json ( ) ;
125+ const mdFiles = files . filter ( f => f . name . endsWith ( '.md' ) ) ;
126+
63127 for ( const file of mdFiles ) {
64128 try {
65- const rawUrl = file . download_url ; // direct raw URL
66- const resp = await fetch ( rawUrl ) ;
129+ const rawUrl = file . download_url ;
130+ const resp = await fetch ( rawUrl ) ;
67131 if ( ! resp . ok ) continue ;
68- const mdText = await resp . text ( ) ;
69- const { fm} = parseFrontMatter ( mdText ) ;
70- const title = fm . title || file . name ;
71- const description = fm . description || '' ;
72- const author = fm . author || 'Unknown' ;
73- const date = fm . date ?formatDate ( fm . date ) :'' ;
74- const tags = fm . tags || '' ;
75-
76- const card = document . createElement ( 'a' ) ;
77- card . href = `pages/article.html?file=${ file . name } ` ;
78- card . className = 'article-card' ;
132+ const mdText = await resp . text ( ) ;
133+ const { fm, content} = parseFrontMatter ( mdText ) ;
134+
135+ const article = {
136+ fileName : file . name ,
137+ title : fm . title || file . name ,
138+ description : fm . description || '' ,
139+ author : fm . author || 'Unknown' ,
140+ date : fm . date ?formatDate ( fm . date ) :'' ,
141+ tags : fm . tags || '' ,
142+ cover : fm . cover || '' ,
143+ content
144+ } ;
145+ articlesData . push ( article ) ;
146+
147+ // create card
148+ const card = document . createElement ( 'div' ) ;
149+ card . className = 'article-card' ;
79150 card . innerHTML = `
80- <h2>${ title } </h2>
81- <p>${ description } </p>
82- <div class="meta">By ${ author } | ${ date } </div>
83- <div class="tags">${ tags } </div>
151+ <h2>${ article . title } </h2>
152+ <p>${ article . description } </p>
153+ <div class="meta">By ${ article . author } | ${ article . date } </div>
154+ <div class="tags">${ article . tags } </div>
84155 ` ;
156+
157+ card . addEventListener ( 'click' , ( ) => openModal ( articlesData . indexOf ( article ) ) ) ;
85158 grid . appendChild ( card ) ;
86- } catch ( e ) { console . error ( 'Error loading markdown' , file . name , e ) ; }
159+
160+ } catch ( err ) { console . error ( 'Error loading file' , file . name , err ) ; }
87161 }
88- } catch ( e ) {
89- console . error ( 'Error fetching articles' , e ) ;
90- grid . innerHTML = '<p class="small">Unable to load articles.</p>' ;
91- }
162+ } catch ( e ) { console . error ( 'Failed to fetch articles list' , e ) ; }
92163} ) ( ) ;
164+
165+ // --- Modal functions ---
166+ async function openModal ( index ) {
167+ currentIndex = index ;
168+ const article = articlesData [ index ] ;
169+ modalTitle . textContent = article . title ;
170+ modalAuthor . textContent = 'By ' + article . author ;
171+ modalDate . textContent = article . date ;
172+ modalReadtime . textContent = estimateReadTime ( article . content ) ;
173+ modalTags . textContent = article . tags ;
174+ if ( article . cover ) { modalCover . src = article . cover ; modalCover . style . display = 'block' ; } else modalCover . style . display = 'none' ;
175+ articleContent . innerHTML = marked . parse ( article . content ) ;
176+ Prism . highlightAll ( ) ;
177+ modalViews . textContent = 'Views: ' + await incAndGetViews ( article . fileName . replace ( / \. [ ^ / . ] + $ / , "" ) ) ;
178+ modal . style . display = 'flex' ;
179+ }
180+
181+ // Next / Previous
182+ prevBtn . addEventListener ( 'click' , ( ) => openModal ( ( currentIndex - 1 + articlesData . length ) % articlesData . length ) ) ;
183+ nextBtn . addEventListener ( 'click' , ( ) => openModal ( ( currentIndex + 1 ) % articlesData . length ) ) ;
184+
185+ // Close modal
186+ modalClose . addEventListener ( 'click' , ( ) => modal . style . display = 'none' ) ;
187+ window . addEventListener ( 'click' , e => { if ( e . target == modal ) modal . style . display = 'none' } ) ;
93188</ script >
94189</ body >
95190</ html >
0 commit comments