Skip to content

Commit 7303362

Browse files
Create badge.js
1 parent 321f76c commit 7303362

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

js/badge.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const els = {
2+
label: document.getElementById("b-label"),
3+
message: document.getElementById("b-message"),
4+
style: document.getElementById("b-style"),
5+
labelColor: document.getElementById("b-labelcolor"),
6+
swatches: document.getElementById("b-swatches"),
7+
preview: document.getElementById("b-preview"),
8+
md: document.getElementById("snippet-md"),
9+
html: document.getElementById("snippet-html"),
10+
};
11+
12+
const SWATCHES = [
13+
{ name: "bytes pink", value: "#EC4899" },
14+
{ name: "brand purple", value: "#7C4DFF" },
15+
{ name: "success green", value: "#2da44e" },
16+
{ name: "H# red", value: "#9c1120" },
17+
{ name: "Hacker Lang purple", value: "#8250df" },
18+
{ name: "HackerScript gray", value: "#8a8a94" },
19+
{ name: "info blue", value: "#0969da" },
20+
{ name: "warning orange", value: "#FB923C" },
21+
];
22+
23+
let messageColor = SWATCHES[0].value;
24+
25+
function buildSwatches() {
26+
els.swatches.innerHTML = SWATCHES.map(
27+
(s, i) =>
28+
`<span class="swatch ${i === 0 ? "is-active" : ""}" data-color="${s.value}" style="background:${s.value}" title="${s.name}"></span>`
29+
).join("");
30+
els.swatches.querySelectorAll(".swatch").forEach((el) => {
31+
el.addEventListener("click", () => {
32+
messageColor = el.dataset.color;
33+
els.swatches.querySelectorAll(".swatch").forEach((s) => s.classList.remove("is-active"));
34+
el.classList.add("is-active");
35+
update();
36+
});
37+
});
38+
}
39+
40+
/** Measures text width in the badge's font so segments size correctly, like shields.io. */
41+
function textWidth(text, fontSize = 11) {
42+
const canvas = textWidth._canvas || (textWidth._canvas = document.createElement("canvas"));
43+
const ctx = canvas.getContext("2d");
44+
ctx.font = `bold ${fontSize}px Verdana, Geneva, sans-serif`;
45+
return ctx.measureText(text).width;
46+
}
47+
48+
function escapeXml(s) {
49+
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
50+
}
51+
52+
function buildSvg({ label, message, style, labelColor, messageColor }) {
53+
const pad = 10;
54+
const labelW = Math.round(textWidth(label) + pad * 2);
55+
const messageW = Math.round(textWidth(message) + pad * 2);
56+
const height = 20;
57+
const totalW = labelW + messageW;
58+
const radius = style === "flat-square" ? 0 : 3;
59+
const gloss =
60+
style === "plastic"
61+
? `<linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".2"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".1"/><stop offset="1" stop-color="#000" stop-opacity=".2"/></linearGradient>`
62+
: "";
63+
64+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${height}" role="img" aria-label="${escapeXml(label)}: ${escapeXml(message)}">
65+
<defs>${gloss}</defs>
66+
<clipPath id="r"><rect width="${totalW}" height="${height}" rx="${radius}" fill="#fff"/></clipPath>
67+
<g clip-path="url(#r)">
68+
<rect width="${labelW}" height="${height}" fill="${labelColor}"/>
69+
<rect x="${labelW}" width="${messageW}" height="${height}" fill="${messageColor}"/>
70+
${gloss ? `<rect width="${totalW}" height="${height}" fill="url(#s)"/>` : ""}
71+
</g>
72+
<g fill="#fff" text-anchor="middle" font-family="Verdana, Geneva, sans-serif" font-size="11" font-weight="bold">
73+
<text x="${labelW / 2}" y="14">${escapeXml(label)}</text>
74+
<text x="${labelW + messageW / 2}" y="14">${escapeXml(message)}</text>
75+
</g>
76+
</svg>`;
77+
}
78+
79+
function update() {
80+
const label = els.label.value.trim() || "label";
81+
const message = els.message.value.trim() || "message";
82+
const style = els.style.value;
83+
const labelColor = els.labelColor.value.trim() || "#4a4a58";
84+
85+
const svg = buildSvg({ label, message, style, labelColor, messageColor });
86+
const dataUri = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`;
87+
88+
els.preview.innerHTML = `<img src="${dataUri}" alt="${label}: ${message}" width="${Math.round(
89+
(textWidth(label) + 20) + (textWidth(message) + 20)
90+
)}" height="20" />`;
91+
92+
els.md.value = `[![${label}](${dataUri})](https://bytes-repository.github.io/index.html)`;
93+
els.html.value = `<a href="https://bytes-repository.github.io/index.html"><img src="${dataUri}" alt="${label}: ${message}" /></a>`;
94+
}
95+
96+
document.querySelectorAll("[data-copy-target]").forEach((btn) => {
97+
btn.addEventListener("click", async () => {
98+
const target = document.getElementById(btn.dataset.copyTarget);
99+
try {
100+
await navigator.clipboard.writeText(target.value);
101+
const original = btn.textContent;
102+
btn.textContent = "Copied";
103+
setTimeout(() => (btn.textContent = original), 1300);
104+
} catch {
105+
target.select();
106+
}
107+
});
108+
});
109+
110+
[els.label, els.message, els.style, els.labelColor].forEach((el) => el.addEventListener("input", update));
111+
112+
buildSwatches();
113+
update();

0 commit comments

Comments
 (0)