@@ -30,35 +30,30 @@ function parseCertTable(md) {
3030 }
3131 if ( start === - 1 ) return [ ] ;
3232
33- // skip header + separator
3433 const rows = [ ] ;
3534 for ( let i = start + 2 ; i < lines . length ; i ++ ) {
3635 const line = lines [ i ] . trim ( ) ;
3736 if ( ! line . startsWith ( "|" ) ) break ;
38- // split by '|' and remove first/last empty segments
37+ // split by '|' and trim
3938 const parts = line . split ( "|" ) . map ( p => p . trim ( ) ) ;
40- // parts usually: ["", "Company", "Type", ... , ""]
41- // discard any leading/trailing empty strings
42- if ( parts . length < 6 ) continue ;
43- // filter out possible empty at start
44- const cols = parts . filter ( ( c , idx ) => c !== "" || ( c === "" && idx > 0 && idx < parts . length - 1 ) ) ;
45- // safer: take last 6 non-empty-ish entries from the line
46- // but easier: take indices 1..6 in normal layout
39+ // expected parts: ["", "Company", "Type", "Status", "Valid From", "Valid To", "Download", ""]
40+ // guard: ensure at least 7 meaningful columns
41+ const meaningful = parts . filter ( ( p , idx ) => p !== "" || ( idx > 0 && idx < parts . length - 1 ) ) ;
42+ // We'll read by indexes, but check boundary
4743 const company = parts [ 1 ] || "" ;
4844 const type = parts [ 2 ] || "" ;
4945 const statusRaw = parts [ 3 ] || "" ;
5046 const validFrom = parts [ 4 ] || "" ;
5147 const validTo = parts [ 5 ] || "" ;
5248 const downloadRaw = parts [ 6 ] || "" ;
5349
54- const status = statusRaw . replace ( / \* \* / g, "" ) . trim ( ) ;
55- const downloadUrlMatch = downloadRaw . match ( / \( ( h t t p s ? : \/ \/ [ ^ \) ] + ) \) / ) ;
56- const downloadUrl = downloadUrlMatch ? decodeURIComponent ( downloadUrlMatch [ 1 ] ) : ( downloadRaw . match ( / h t t p s ? : \/ \/ / ) ? downloadRaw : "" ) ;
50+ const status = stripMd ( statusRaw ) ;
51+ const downloadUrl = extractUrlFromMd ( downloadRaw ) ;
5752
5853 rows . push ( {
59- company : company ,
54+ company : stripMd ( company ) ,
6055 type : stripMd ( type ) ,
61- status : stripMd ( status ) ,
56+ status : status ,
6257 validFrom : stripMd ( validFrom ) ,
6358 validTo : stripMd ( validTo ) ,
6459 download : downloadUrl
@@ -69,26 +64,60 @@ function parseCertTable(md) {
6964}
7065
7166function stripMd ( s ) {
67+ if ( ! s ) return "" ;
7268 return s . replace ( / \* \* / g, "" ) . replace ( / \[ | \] / g, "" ) . trim ( ) ;
7369}
7470
71+ function extractUrlFromMd ( s ) {
72+ if ( ! s ) return "" ;
73+ // match (https://...)
74+ const m = s . match ( / \( ( h t t p s ? : \/ \/ [ ^ \) ] + ) \) / ) ;
75+ if ( m && m [ 1 ] ) return decodeSafe ( m [ 1 ] ) ;
76+ // sometimes the link isn't wrapped in parentheses, try to find plain url
77+ const m2 = s . match ( / h t t p s ? : \/ \/ \S + / ) ;
78+ if ( m2 ) return decodeSafe ( m2 [ 0 ] ) ;
79+ return "" ;
80+ }
81+
82+ function decodeSafe ( u ) {
83+ try {
84+ return decodeURIComponent ( u ) ;
85+ } catch ( e ) {
86+ return u ;
87+ }
88+ }
89+
7590/* ---------- Rendering ---------- */
7691
7792function renderRecommended ( md ) {
78- const m = md . match ( / # R e c o m m e n d C e r t i f i c a t e \s + ( [ \s \S ] * ?) \n \n / ) ;
93+ // find the Recommend Certificate section
94+ // README uses:
95+ // # Recommend Certificate
96+ // **China Telecommunications Corporation V2 - ❌ Revoked**
97+ //
98+ // We'll capture the next non-empty line and strip stars.
99+ const lines = md . split ( "\n" ) ;
100+ let idx = lines . findIndex ( l => l . trim ( ) . toLowerCase ( ) . startsWith ( "# recommend certificate" ) ) ;
79101 let rec = "" ;
80- if ( m ) rec = m [ 1 ] . trim ( ) ;
81- else {
82- // fallback: look for header then bold on next line
83- const m2 = md . match ( / # R e c o m m e n d C e r t i f i c a t e \s * \n \* \* ( .+ ?) \* \* / s) ;
84- if ( m2 ) rec = m2 [ 1 ] . trim ( ) ;
102+ if ( idx !== - 1 ) {
103+ // find first non-empty line after the header
104+ for ( let i = idx + 1 ; i < lines . length ; i ++ ) {
105+ const ln = lines [ i ] . trim ( ) ;
106+ if ( ! ln ) continue ;
107+ // strip ** and md markup
108+ rec = ln . replace ( / \* \* / g, "" ) . trim ( ) ;
109+ // remove surrounding markdown quote markers or other noise
110+ rec = rec . replace ( / ^ > \s ? / , "" ) . trim ( ) ;
111+ break ;
112+ }
85113 }
86114
87115 const el = document . getElementById ( "recommended" ) ;
88116 if ( ! rec ) {
89117 el . style . display = "none" ;
90118 return ;
91119 }
120+ // show plain text (no bold)
92121 el . innerHTML = `<h3>⭐ Recommended Certificate</h3><p>${ escapeHtml ( rec ) } </p>` ;
93122}
94123
@@ -101,11 +130,11 @@ function renderCertCards(certs) {
101130 return ;
102131 }
103132
104- certs . forEach ( ( c , idx ) => {
105- const statusLower = c . status . toLowerCase ( ) ;
133+ certs . forEach ( ( c ) => {
134+ const statusLower = ( c . status || "" ) . toLowerCase ( ) ;
106135 const isRevoked = statusLower . includes ( "revok" ) || statusLower . includes ( "❌" ) ;
107- const badgeClass = isRevoked ? "revoked" : "valid " ;
108- const badgeText = c . status || ( isRevoked ? "Revoked" : "Unknown" ) ;
136+ const badgeClass = isRevoked ? "revoked" : "signed " ;
137+ const badgeText = isRevoked ? "❌ Revoked" : "✅ Signed" ;
109138
110139 const card = document . createElement ( "div" ) ;
111140 card . className = "cert-card" ;
@@ -128,7 +157,7 @@ function renderCertCards(certs) {
128157 </div>
129158 ` ;
130159
131- // click handler opens modal with details
160+ // open modal with details on click / enter
132161 card . addEventListener ( "click" , ( ) => openModal ( c ) ) ;
133162 card . addEventListener ( "keypress" , ( e ) => { if ( e . key === "Enter" ) openModal ( c ) ; } ) ;
134163
@@ -146,23 +175,16 @@ function renderUpdates(md) {
146175 return ;
147176 }
148177
149- // capture content after "# Updates" until next heading that starts with '# ' or EOF
150178 const after = md . substring ( idx + "# Updates" . length ) ;
151179 const lines = after . split ( "\n" ) ;
152180 const updates = [ ] ;
153181 for ( let i = 0 ; i < lines . length ; i ++ ) {
154182 const line = lines [ i ] . trim ( ) ;
155183 if ( ! line ) continue ;
156184 if ( line . startsWith ( "#" ) ) break ;
157- // lines that start with ** are update entries in this README
158- if ( line . startsWith ( "**" ) && line . endsWith ( "**" ) ) {
159- updates . push ( line . replace ( / \* \* / g, "" ) ) ;
160- } else if ( line . startsWith ( "**" ) ) {
161- updates . push ( line . replace ( / \* \* / g, "" ) ) ;
162- } else {
163- // sometimes they aren't bold — include them too
164- updates . push ( line ) ;
165- }
185+ // Accept lines starting with ** or plain lines
186+ const cleaned = line . replace ( / \* \* / g, "" ) . trim ( ) ;
187+ if ( cleaned ) updates . push ( cleaned ) ;
166188 }
167189
168190 if ( ! updates . length ) {
@@ -177,18 +199,26 @@ function renderUpdates(md) {
177199function openModal ( c ) {
178200 const modal = document . getElementById ( "certModal" ) ;
179201 document . getElementById ( "modalName" ) . textContent = c . company ;
180- document . getElementById ( "modalMeta" ) . textContent = `${ c . type } • Status: ${ c . status } ` ;
202+ document . getElementById ( "modalMeta" ) . textContent = `${ c . type } • Status: ${ c . status || ( c . status === "" ? "Unknown" : c . status ) } ` ;
181203 document . getElementById ( "modalDates" ) . textContent = `Valid: ${ c . validFrom } → ${ c . validTo } ` ;
182204
183205 const dl = document . getElementById ( "modalDownload" ) ;
206+ dl . innerHTML = "" ;
184207 if ( c . download ) {
185208 const a = document . createElement ( "a" ) ;
186209 a . href = c . download ;
187210 a . target = "_blank" ;
188211 a . rel = "noopener noreferrer" ;
189212 a . textContent = "Download" ;
190- dl . innerHTML = "" ;
191213 dl . appendChild ( a ) ;
214+
215+ // also show raw url (small)
216+ const small = document . createElement ( "div" ) ;
217+ small . style . marginTop = "8px" ;
218+ small . style . fontSize = "12px" ;
219+ small . style . color = "var(--muted)" ;
220+ small . textContent = c . download ;
221+ dl . appendChild ( small ) ;
192222 } else {
193223 dl . innerHTML = `<div style="color:var(--muted);">No download link found.</div>` ;
194224 }
0 commit comments