Skip to content

Commit ee77c60

Browse files
authored
Feature/blog update (#19)
* 💄update styling for blog posts * ➕sanitize uml for code highlighting * 💄code block ui * ➖sanitize-html
1 parent be0d756 commit ee77c60

4 files changed

Lines changed: 85 additions & 37 deletions

File tree

package-lock.json

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Blog/Content-Text.astro

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,83 @@ import 'prismjs/themes/prism-tomorrow.css';
55
import 'prismjs/components/prism-javascript';
66
import 'prismjs/components/prism-python';
77
8-
const { textBlock, className = "" } = Astro.props;
9-
10-
// Configure marked to use Prism for code blocks
11-
marked.use({
12-
renderer: {
13-
code(token: any) {
14-
// Debug logs
15-
console.log('Token:', token);
16-
let lang = token.lang || '';
17-
if (lang === 'js') lang = 'javascript';
18-
if (Prism.languages[lang]) {
19-
const highlighted = Prism.highlight(token.text, Prism.languages[lang], lang);
20-
console.log('Highlighted result:', highlighted);
21-
return `<pre class="language-${lang}"><code>${highlighted}</code></pre>`;
22-
}
23-
return `<pre><code>${token.text}</code></pre>`;
24-
}
8+
const { textBlock = '', className = '' } = Astro.props;
9+
10+
let __codeBlockCounter = 0;
11+
12+
const renderer = new marked.Renderer();
13+
renderer.code = (code: any, infostring?: string) => {
14+
const rawLang = (infostring || '').trim().split(/\s+/)[0] || '';
15+
let lang = rawLang === 'js' ? 'javascript' : rawLang;
16+
const safeLang = lang || 'text';
17+
18+
function escapeHtmlString(s: any) {
19+
const str = String(s ?? '');
20+
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2521
}
26-
});
2722
28-
const htmlContent = marked.parse(textBlock);
23+
// `code` can sometimes be an object depending on marked version; ensure we have a string
24+
const rawCode = typeof code === 'string' ? code : (code && typeof (code as any).text === 'string' ? (code as any).text : String(code ?? ''));
25+
26+
const highlighted = Prism.languages[lang]
27+
? Prism.highlight(rawCode, Prism.languages[lang], lang)
28+
: escapeHtmlString(rawCode);
29+
30+
const id = `code-${++__codeBlockCounter}`;
31+
32+
return `
33+
<div class="code-terminal" role="group" aria-label="Code example" style="background:#0b1220;border:1px solid #2b3844;border-radius:8px;margin:1rem 0;overflow:hidden;">
34+
<div class="code-header" style="display:flex;justify-content:space-between;align-items:center;padding:0.4rem 0.6rem;background:linear-gradient(90deg, rgba(255,255,255,0.015), rgba(255,255,255,0.01));color:#9aa6b2;font-size:0.85rem;">
35+
<span class="code-lang" style="font-weight:600;text-transform:uppercase;font-size:0.75rem;color:#7f8b95;">${safeLang}</span>
36+
<button class="copy-btn" data-target="${id}" aria-label="Copy code" style="background:transparent;border:1px solid rgba(255,255,255,0.03);color:#9aa6b2;padding:0.25rem 0.45rem;border-radius:6px;cursor:pointer;font-size:0.9rem;display:inline-flex;align-items:center;gap:0.35rem;">
37+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
38+
<path d="M16 1H4C2.89543 1 2 1.89543 2 3V17H4V3H16V1Z" fill="#9aa6b2"/>
39+
<path d="M20 5H8C6.89543 5 6 5.89543 6 7V21C6 22.1046 6.89543 23 8 23H20C21.1046 23 22 22.1046 22 21V7C22 5.89543 21.1046 5 20 5ZM20 21H8V7H20V21Z" fill="#9aa6b2"/>
40+
</svg>
41+
</button>
42+
</div>
43+
<pre class="language-${safeLang}" style="margin:0;padding:0.75rem;overflow:auto;background:transparent;color:#cbd5d8;"><code id="${id}" style="font-family: 'Cascadia Mono PL', ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;font-size:0.875rem;line-height:1.4;">${highlighted}</code></pre>
44+
</div>
45+
`;
46+
};
47+
48+
marked.use({ renderer });
49+
50+
const htmlContent = marked.parse(textBlock || '');
2951
---
30-
<div class={className} set:html={htmlContent}></div>
52+
<section class={className} set:html={htmlContent}></section>
53+
54+
<style>
55+
.code-terminal{ background:#0b1220; border:1px solid #2b3844; border-radius:8px; margin:1rem 0; overflow:hidden; }
56+
.code-header{ display:flex; justify-content:space-between; align-items:center; padding:0.4rem 0.6rem; background:linear-gradient(90deg, rgba(255,255,255,0.015), rgba(255,255,255,0.01)); color:#9aa6b2; font-size:0.85rem; }
57+
.code-lang{ font-weight:600; text-transform:uppercase; font-size:0.75rem; color:#7f8b95; }
58+
.copy-btn{ background:transparent; border:1px solid rgba(255,255,255,0.03); color:#9aa6b2; padding:0.25rem 0.45rem; border-radius:6px; cursor:pointer; font-size:0.9rem; display:inline-flex; align-items:center; gap:0.35rem; }
59+
.copy-btn:active{ transform:translateY(1px); }
60+
pre{ margin:0; padding:0.75rem; overflow:auto; background:transparent; color:#cbd5d8; }
61+
code{ font-family: 'Cascadia Mono PL', ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace; font-size:0.875rem; line-height:1.4; }
62+
@media (prefers-color-scheme: light){ .code-terminal{ border-color:#e6e6e6 } }
63+
</style>
64+
65+
<script type="module">
66+
const successSVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15l-5-5 1.41-1.41L11 14.17l6.59-6.59L19 9l-8 8z" fill="#7ee787"/></svg>`;
67+
const failSVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4 13.59L15.59 16 12 12.41 8.41 16 8 15.59 11.59 12 8 8.41 8.41 8 12 11.59 15.59 8 16 8.41 12.41 12 16 15.59z" fill="#ff7b7b"/></svg>`;
68+
69+
window.addEventListener('click', async (e) => {
70+
const btn = (e.target.closest && e.target.closest('.copy-btn')) || (e.target.matches && e.target.matches('.copy-btn') && e.target);
71+
if (!btn) return;
72+
const id = btn.getAttribute('data-target');
73+
const codeEl = document.getElementById(id);
74+
if (!codeEl) return;
75+
const text = codeEl.textContent || codeEl.innerText || '';
76+
const origHTML = btn.innerHTML;
77+
try {
78+
await navigator.clipboard.writeText(text);
79+
btn.innerHTML = successSVG;
80+
btn.setAttribute('aria-label', 'Copied');
81+
} catch (err) {
82+
btn.innerHTML = failSVG;
83+
btn.setAttribute('aria-label', 'Copy failed');
84+
}
85+
setTimeout(() => { btn.innerHTML = origHTML; btn.setAttribute('aria-label', 'Copy code'); }, 1500);
86+
});
87+
</script>

src/pages/blog/[slug].astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const prefixUrl = import.meta.env.STRAPI_API_URL || "";
2828
---
2929

3030
<Layout>
31-
<article class="mx-auto w-full md:w-8/12 p-4">
31+
<article class="mx-auto w-full md:w-8/12 p-4 border border-gray-800 rounded-md shadow-lg bg-gray-900 px-8 drop-shadow-md">
3232
<!-- Blog Title -->
3333
<h1 class="text-xl md:text-5xl font-bold text-center mb-4">{blog.Title}</h1>
3434
<!-- Blog Cover Image -->

src/styles/blog.css

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,25 @@
1313
}
1414

1515
.blog-content-block h1 {
16-
@apply text-blue-400 text-2xl sm:text-3xl md:text-4xl font-bold;
16+
@apply text-cyan-300 text-2xl sm:text-3xl md:text-3xl font-bold;
1717
}
1818
.blog-content-block h2 {
19-
@apply text-xl sm:text-2xl md:text-3xl font-semibold text-blue-600;
19+
@apply text-xl sm:text-2xl md:text-2xl font-semibold text-cyan-400;
2020
}
2121
.blog-content-block h3 {
22-
@apply text-lg sm:text-xl md:text-2xl font-semibold text-green-600;
22+
@apply text-lg sm:text-xl md:text-xl font-semibold text-green-600;
2323
}
2424
.blog-content-block h4 {
25-
@apply text-base sm:text-lg md:text-xl font-semibold text-purple-600;
25+
@apply text-base sm:text-lg md:text-lg font-semibold text-indigo-300;
2626
}
2727
.blog-content-block h5 {
28-
@apply text-sm sm:text-base md:text-lg font-semibold text-yellow-600;
28+
@apply text-sm sm:text-base md:text-base font-semibold text-yellow-600;
2929
}
3030
.blog-content-block h6 {
31-
@apply text-xs sm:text-sm md:text-base font-semibold text-gray-600;
31+
@apply text-xs sm:text-sm md:text-sm font-semibold text-gray-600;
3232
}
3333
.blog-content-block p {
34-
@apply text-base sm:text-lg md:text-xl;
34+
@apply text-base sm:text-lg md:text-lg;
3535
}
3636
.blog-content-block a {
3737
@apply text-blue-500 underline;

0 commit comments

Comments
 (0)