Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 5b40e9a

Browse files
authored
Update certManager.js
1 parent 8149439 commit 5b40e9a

1 file changed

Lines changed: 67 additions & 37 deletions

File tree

web/certManager.js

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/\((https?:\/\/[^\)]+)\)/);
56-
const downloadUrl = downloadUrlMatch ? decodeURIComponent(downloadUrlMatch[1]) : (downloadRaw.match(/https?:\/\//) ? 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

7166
function 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(/\((https?:\/\/[^\)]+)\)/);
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(/https?:\/\/\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

7792
function renderRecommended(md) {
78-
const m = md.match(/# Recommend Certificate\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(/# Recommend Certificate\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) {
177199
function 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

Comments
 (0)