-
Notifications
You must be signed in to change notification settings - Fork 0
Implement blog snippet system and new landing page #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
91300fd
d737f6a
db70c24
347fea9
a3a55ed
e5271ed
24924f5
065b9f0
8b863fd
f1a5592
4eae751
9f5f6ab
3d560e9
10242ed
f74f774
5dd1241
6b35f55
4ca5a91
565bf1e
7b74943
1c36fad
447ed49
b32ab4e
e36c150
a8f408a
ca454b8
0a1eee3
92ba828
37bb713
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,9 @@ | ||
| // @ts-check | ||
| import { defineConfig } from 'astro/config'; | ||
|
|
||
| import react from '@astrojs/react'; | ||
|
|
||
| // https://astro.build/config | ||
| export default defineConfig({}); | ||
| export default defineConfig({ | ||
| integrations: [react()] | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| interface BlogPost { | ||
| id: number; | ||
| date: string; | ||
| slug: string; | ||
| title: { | ||
| rendered: string; | ||
| }; | ||
| guid: { | ||
| rendered: string; | ||
| }; | ||
| content: { | ||
| rendered: string; | ||
| }; | ||
| excerpt: { | ||
| rendered: string; | ||
| }; | ||
| link: string; | ||
| author: number; | ||
| jetpack_featured_media_url: string; | ||
|
|
||
| // embedded data | ||
| _embedded?: { | ||
| author?: { | ||
| name: string; | ||
| }[]; | ||
| }; | ||
| } | ||
|
|
||
| export type { BlogPost }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,91 @@ | ||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||
| import type { BlogPost } from "../data/types"; | ||||||||||||||||||||||||||||||||
| import "../../src/styles/mylanding.css"; | ||||||||||||||||||||||||||||||||
| import { preview } from "astro"; | ||||||||||||||||||||||||||||||||
| export async function getStaticPaths() { | ||||||||||||||||||||||||||||||||
| // _embed query param expands related objects like authors | ||||||||||||||||||||||||||||||||
| const res = await fetch("https://akongalabs.com/wp-json/wp/v2/posts?_embed"); | ||||||||||||||||||||||||||||||||
| const posts: BlogPost[] = await res.json(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return posts.map((post, index) => ({ | ||||||||||||||||||||||||||||||||
| params: { slug: post.slug }, | ||||||||||||||||||||||||||||||||
| props: { | ||||||||||||||||||||||||||||||||
| post, | ||||||||||||||||||||||||||||||||
| prevPost: posts[index + 1] || null, | ||||||||||||||||||||||||||||||||
| nextPost: posts[index - 1] || null, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const { post, prevPost, nextPost } = Astro.props as { | ||||||||||||||||||||||||||||||||
| post: BlogPost; | ||||||||||||||||||||||||||||||||
| prevPost: BlogPost | null; | ||||||||||||||||||||||||||||||||
| nextPost: BlogPost | null; | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <html> | ||||||||||||||||||||||||||||||||
| <head> | ||||||||||||||||||||||||||||||||
| <title set:html={post.title.rendered}></title> | ||||||||||||||||||||||||||||||||
| <meta charset="utf-8" /> | ||||||||||||||||||||||||||||||||
| <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> | ||||||||||||||||||||||||||||||||
| <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1"/> | ||||||||||||||||||||||||||||||||
| <meta name="generator" content={Astro.generator} /> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| </head> | ||||||||||||||||||||||||||||||||
| <body> | ||||||||||||||||||||||||||||||||
| <article class="blog-post"> | ||||||||||||||||||||||||||||||||
| <div class="blog-body"> | ||||||||||||||||||||||||||||||||
| <div class="first_links_snips"> | ||||||||||||||||||||||||||||||||
| <a href="/newLanding">Akong'a Labs</a> | ||||||||||||||||||||||||||||||||
| <a href="/blogSnippets">Blogs</a> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| <div class="blog-content"> | ||||||||||||||||||||||||||||||||
| <h1 set:html={post.title.rendered} /> | ||||||||||||||||||||||||||||||||
| <img src={post.jetpack_featured_media_url} /> | ||||||||||||||||||||||||||||||||
| <p class="author-date"> | ||||||||||||||||||||||||||||||||
| <span class="author">{post._embedded?.author?.[0]?.name}</span> | ||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Render a safe title and harden featured image (a11y + perf)
- <h1 set:html={post.title.rendered} />
- <img src={post.jetpack_featured_media_url} />
+ <h1>{titleText}</h1>
+ {
+ post.jetpack_featured_media_url && (
+ <img
+ src={post.jetpack_featured_media_url}
+ alt={titleText}
+ loading="lazy"
+ decoding="async"
+ />
+ )
+ }Consider adding width/height to reduce CLS if dimensions are known. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| <span class="date"> | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| new Date(post.date).toLocaleDateString("en-US", { | ||||||||||||||||||||||||||||||||
| year: "numeric", | ||||||||||||||||||||||||||||||||
| month: "long", | ||||||||||||||||||||||||||||||||
| day: "numeric", | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <div set:html={post.content.rendered} /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| <script async data-uid="4e5ba9cc5c" src="https://akongalabs.kit.com/4e5ba9cc5c/index.js"></script> | ||||||||||||||||||||||||||||||||
| <hr /> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| (prevPost || nextPost) && ( | ||||||||||||||||||||||||||||||||
| <nav class="blog-navigation"> | ||||||||||||||||||||||||||||||||
| <div class="nav-links"> | ||||||||||||||||||||||||||||||||
| {prevPost && ( | ||||||||||||||||||||||||||||||||
| <a href={`/${prevPost.slug}`}> | ||||||||||||||||||||||||||||||||
| Previous: <span set:html={prevPost.title.rendered} /> | ||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {nextPost && ( | ||||||||||||||||||||||||||||||||
| <a href={`/${nextPost.slug}`}> | ||||||||||||||||||||||||||||||||
| Next: <span set:html={nextPost.title.rendered} /> | ||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </nav> | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <div class="last_links"> | ||||||||||||||||||||||||||||||||
| <p><a href="https://x.com/adrianmurage">𝕏</a></p> | ||||||||||||||||||||||||||||||||
| <p><a href="https://github.com/AkongaLabs">GitHub</a></p> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </article> | ||||||||||||||||||||||||||||||||
| </body> | ||||||||||||||||||||||||||||||||
| </html> | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| --- | ||
| import "../../src/styles/mylanding.css"; | ||
| import type { BlogPost } from "../data/types"; | ||
|
|
||
| const res = await fetch("https://akongalabs.com/wp-json/wp/v2/posts"); | ||
| const posts: BlogPost[] = await res.json(); | ||
| --- | ||
|
Comment on lines
+1
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sanitize HTML, avoid unnecessary set:html, and improve heading semantics
Apply these diffs: ---
-import "../../src/styles/mylanding.css";
+import "../styles/mylanding.css";
import type { BlogPost } from "../data/types";
+import sanitizeHtml from "sanitize-html";
-const res = await fetch("https://akongalabs.com/wp-json/wp/v2/posts");
-const posts: BlogPost[] = await res.json();
+const res = await fetch("https://akongalabs.com/wp-json/wp/v2/posts?per_page=10");
+let posts: BlogPost[] = [];
+if (res.ok) {
+ posts = (await res.json()) as BlogPost[];
+} else {
+ console.warn("Failed to fetch posts:", res.status, res.statusText);
+}
---- <p class="bluecolor" set:html={formattedDate}></p>
- <h1 class="blog-title" set:html={post.title.rendered}></h1>
- <div class="para_space" set:html={cleanedExcerpt}></div>
+ <p class="bluecolor">{formattedDate}</p>
+ <h2 class="blog-title">
+ {post.title.rendered.replace(/<[^>]*>/g, "")}
+ </h2>
+ <div
+ class="para_space"
+ set:html={sanitizeHtml(cleanedExcerpt, {
+ allowedTags: ["p", "em", "strong", "a", "code", "pre", "ul", "ol", "li", "span", "br"],
+ allowedAttributes: { a: ["href", "rel", "target"] },
+ allowedSchemes: ["http", "https", "mailto"],
+ })}
+ ></div>Also applies to: 34-38 🤖 Prompt for AI Agents |
||
|
|
||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> | ||
| <meta name="viewport" content="width=device-width" /> | ||
| <title>Blog snippets</title> | ||
| </head> | ||
| <body> | ||
| <div class="blogs_container"> | ||
| <div class="first_links"> | ||
| <a href="/newLanding">Akong'a Labs</a> | ||
| <a href="/blogSnippets">Blogs</a> | ||
| </div> | ||
|
|
||
| <div> | ||
| {posts.map((post) => { | ||
| const formattedDate = new Date(post.date).toLocaleDateString("en-US", { | ||
| year: "numeric", | ||
| month: "long", | ||
| day: "numeric", | ||
| }); | ||
| const cleanedExcerpt = post.excerpt.rendered.replace(/\[…\]/g, "..."); | ||
|
|
||
| return ( | ||
| <div class="asnip"> | ||
| <p class="bluecolor" set:html={formattedDate}></p> | ||
| <h1 class="blog-title"> | ||
| <a href={`/${post.slug}`} set:html={post.title.rendered}></a> | ||
| </h1> | ||
| <div class="para_space" set:html={cleanedExcerpt}></div> | ||
| <a class="readmore" href={`/${post.slug}`}>Read more →</a> | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
|
|
||
| <p class="insider">Get insider posts straight in your inbox</p> | ||
|
|
||
| <script async data-uid="4e5ba9cc5c" src="https://akongalabs.kit.com/4e5ba9cc5c/index.js"></script> | ||
| <div class="last_links"> | ||
| <p><a href="https://x.com/adrianmurage">𝕏</a></p> | ||
| <p><a href="https://github.com/AkongaLabs">GitHub</a></p> | ||
| </div> | ||
| </div> | ||
| </body> | ||
| </html> | ||
| <!-- <script async data-uid="4e5ba9cc5c" src="https://akongalabs.kit.com/4e5ba9cc5c/index.js"></script> --> | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,53 @@ | ||||||||||||
| --- | ||||||||||||
| // import "../../public/styles/mylanding.css"; | ||||||||||||
| import "../../src/styles/mylanding.css" | ||||||||||||
| const isDev = import.meta.env.DEV; | ||||||||||||
| const baseUrl = isDev ? "http://localhost:3001" : ""; | ||||||||||||
|
Comment on lines
+4
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove unused baseUrl variable. The Apply this diff to remove the unused variable: ---
import '../../public/styles/mylanding.css'
-const isDev = import.meta.env.DEV;
-const baseUrl = isDev ? "http://localhost:3001" : "";
---📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| --- | ||||||||||||
|
|
||||||||||||
| <html lang="en"> | ||||||||||||
| <head> | ||||||||||||
| <meta charset="utf-8" /> | ||||||||||||
| <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> | ||||||||||||
| <meta | ||||||||||||
| name="viewport" | ||||||||||||
| content="width=device-width, initial-scale=1, minimum-scale=1" | ||||||||||||
| /> | ||||||||||||
| <meta name="generator" content={Astro.generator} /> | ||||||||||||
| <title>Akong’a Labs</title> | ||||||||||||
| </head> | ||||||||||||
| <body> | ||||||||||||
| <div class="container"> | ||||||||||||
| <div class="content"> | ||||||||||||
| <div class="first_links"> | ||||||||||||
| <a href="/newLanding">Akong'a Labs</a> | ||||||||||||
| <a href="/blogSnippets">Blogs</a> | ||||||||||||
| </div> | ||||||||||||
| <div class="about"> | ||||||||||||
| <h2 class="title">Akong’a — The number “1” in the Pokot language.</h2> | ||||||||||||
| <p> | ||||||||||||
| Akonga Labs is the first of a new kind of company, in Kenya, and | ||||||||||||
| Africa. A <s>startup</s> <strong>stayup</strong> that builds a sustainable business first. | ||||||||||||
| </p> | ||||||||||||
| <p> | ||||||||||||
| Prioritizing building <strong>needed products</strong> in <strong>existing</strong> | ||||||||||||
| and <strong>validated markets.</strong> Prioritizing <strong>profit</strong> over “growth at all cost”. | ||||||||||||
| </p> | ||||||||||||
| </div> | ||||||||||||
|
|
||||||||||||
| <div class="last_links"> | ||||||||||||
| <p><a href="https://x.com/adrianmurage">𝕏</a></p> | ||||||||||||
| <p><a href="https://github.com/AkongaLabs">GitHub</a></p> | ||||||||||||
| </div> | ||||||||||||
| </div> | ||||||||||||
| <div class="image_container"> | ||||||||||||
| <img | ||||||||||||
| class="header_image" | ||||||||||||
| src="/images/header-background.webp" | ||||||||||||
| alt="header image" | ||||||||||||
| /> | ||||||||||||
| <div class="image_text">Akong'a Labs</div> | ||||||||||||
| </div> | ||||||||||||
| </div> | ||||||||||||
| </body> | ||||||||||||
| </html> | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sanitize injected HTML and avoid set:html where plain text will do (XSS hardening)
Title, content and other WP-rendered fields can contain HTML. Injecting them directly with set:html is a high-impact XSS risk if upstream sanitization ever regresses or plugins add unsafe markup.
Apply these diffs:
Also applies to: 14-15, 29-29, 42-42