feat: Rename image and data files

- Add PostCard component
    - Add WysiwygEditor component
    - Use 'front-matter' to parse front matter in markdown files for metadata instead of using redundant data objects
This commit is contained in:
2025-09-27 11:48:32 -05:00
parent f22d9f08bf
commit dceef32ba2
20 changed files with 500 additions and 52 deletions

View File

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 810 KiB

After

Width:  |  Height:  |  Size: 810 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -0,0 +1,80 @@
// PostCard.jsx
import React from 'react';
import { Button } from '@progress/kendo-react-buttons';
import { Card, CardImage, CardBody } from '@progress/kendo-react-layout';
const PostCard = ({ post, onEdit }) => {
const dataPath = post.header.image;
const relativePath = `../../${dataPath}`;
const imageUrl = new URL(relativePath, import.meta.url).href;
const formatDate = (utcString) => {
if (!utcString) return '';
// 1. Create Date object. It parses the UTC string automatically.
const date = new Date(utcString);
// 2. Define options for the desired output format (local time zone)
const dateOptions = {
year: 'numeric',
month: 'short', // e.g., 'Sep'
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true, // e.g., '9:51 PM'
timeZoneName: undefined // Removes the timezone name from the output
};
// 3. Convert to local, human-readable string and clean up formatting
// The space between date and time may vary by locale, so we use string replacement for consistency.
const formattedDate = date.toLocaleDateString(undefined, dateOptions);
// This attempts to convert the format '9/20/2025, 9:51 PM' to 'Sep 20, 2025, 9:51 PM'
const parts = formattedDate.split(',');
if (parts.length > 1) {
const timePart = date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit', hour12: true });
const datePart = date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
return `${datePart}, ${timePart}`;
}
return formattedDate;
};
return (
<Card key={post.slug} style={{ width: '250px', textAlign: 'center', display: 'flex' }}>
{/* 1. Thumbnail Image */}
<CardImage
src={imageUrl}
alt={post.title}
style={{ width: '100%', height: '100px', objectFit: 'cover' }}
/>
{/* 2. Title, Date, and Button Container */}
<CardBody style={{ flexGrow: 1, padding: '10px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
{/* Title Link */}
<div style={{ marginBottom: '5px' }}>
<a className="k-link" href={post.canonical_url} target="_blank" rel="noreferrer" style={{ fontWeight: 'bold' }}>
{post.title}
</a>
</div>
{/* Date and Edit Button */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: '0.85em' }}>
<span>{formatDate(post.date)}</span>
<Button
look="flat"
themeColor="primary"
onClick={() => onEdit(post.slug)}
>
Edit
</Button>
</div>
</CardBody>
</Card>
);
};
export default PostCard;

View File

@@ -0,0 +1,44 @@
import {
RichTextEditorComponent,
Inject,
Toolbar,
HtmlEditor,
MarkdownEditor,
SourceCode,
Link, Image
} from '@syncfusion/ej2-react-richtexteditor';
import * as React from 'react';
function CampfireToggleEditor(props) {
const toolbarOptions = {
items: [
'Bold', 'Italic', 'Underline', '|',
'Formats', 'Alignments', 'OrderedList', 'UnorderedList', '|',
'CreateLink', 'Image', '|',
'SourceCode',
'Undo', 'Redo'
]
};
return (
<RichTextEditorComponent
value={props.value}
change={handleEditorChange}
editorMode={'Html'}
toolbarSettings={toolbarOptions}
>
<Inject
services={[
Toolbar,
Link,
Image,
HtmlEditor,
MarkdownEditor,
SourceCode
]}
/>
</RichTextEditorComponent>
);
};
export default CampfireToggleEditor;

View File

@@ -11,7 +11,7 @@ export default function Copyright() {
>
<p>
&copy; 2025{" | "}Derek L. Seitz{" | "}
<a href="https://dlseitz.dev" target="_blank" rel="noopener noreferrer" class="k-link">
<a href="https://dlseitz.dev" target="_blank" rel="noopener noreferrer" className="k-link">
dlseitz.dev
</a>
</p>

View File

@@ -1,13 +1,13 @@
---
title: #0 - Setting Up Camp
slug: 0-setting-up-camp
title: "#0 - Setting Up Camp"
slug: "0-setting-up-camp"
fileName:
published: true
date: 2025-08-24 05:00:00 UTC
tags: fullstack,developerjourney,BuildInPublic,introduction
canonical_url: https://campfire.dlseitz.dev/0-setting-up-camp
date: "2025-08-24 05:00:00 UTC"
tags: ["fullstack", "developerjourney", "BuildInPublic", "introduction", "Campfire-Logs"]
canonical_url: "https://campfire.dlseitz.dev/0-setting-up-camp"
header:
image: /assets/0-setting-up-camp.jpg
image: "assets/header/0-setting-up-camp.jpg"
attribution: 'Photo by <a href="https://unsplash.com/@kemaldgn?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Kemal Berkay Dogan</a> on <a href="https://unsplash.com/photos/a-campfire-with-a-cup-of-coffee-sitting-in-front-of-it-TcUN5sDZPZ8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>'
---

View File

@@ -1,11 +1,12 @@
---
title: #1 - The Great Gitea Migration
title: "#1 - The Great Gitea Migration"
slug: "1-the-great-gitea-migration"
published: true
date: 2025-08-27 20:19:51 UTC
tags: CampfireLogs,SelfHosting,Devops,gitea
canonical_url: https://campfire.dlseitz.dev/1-the-great-gitea-migration
date: "2025-08-27 20:19:51 UTC"
tags: ["CampfireLogs", "SelfHosting", "Devops", "gitea", "Campfire-Logs"]
canonical_url: "https://campfire.dlseitz.dev/1-the-great-gitea-migration"
header:
image: /assets/header/#1-the-great-gitea-migration.jpg
image: "assets/header/1-the-great-gitea-migration.jpg"
attribution: 'Photo by <a href="https://unsplash.com/@lc_photography?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Leon Contreras</a> on <a href="https://unsplash.com/photos/selective-focus-photography-of-marshmallows-on-fire-pit-YndHL7gQIJE?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>'
---

View File

@@ -1,11 +1,12 @@
---
title: #2 - Retrofitting the Privacy Policy
title: "#2 - Retrofitting the Privacy Policy"
slug: "2-refactoring-the-privacy-policy"
published: true
date: 2025-09-02 20:41:21 UTC
tags: WebDevelopment,FullStackDevelopment,Nodejs,DevLife
canonical_url: https://campfire.dlseitz.dev/2-retrofitting-the-privacy-policy
date: "2025-09-02 20:41:21 UTC"
tags: ["WebDevelopment", "FullStackDevelopment", "Nodejs", "DevLife", "Campfire-Logs"]
canonical_url: "https://campfire.dlseitz.dev/2-retrofitting-the-privacy-policy"
header:
image: /assets/header/#2-retrofitting-the-privacy-policy.png
image: "assets/header/2-retrofitting-the-privacy-policy.png"
attribution: 'Image generated with Sora. | © 2025 Derek L. Seitz'
---

View File

@@ -1,11 +1,12 @@
---
title: #3 - Data Privacy: Things to Consider
title: "#3 - Data Privacy: Things to Consider"
slug: "3-data-privacy-things-to-consider"
published: true
date: 2025-09-03 03:49:36 UTC
tags: dataprivacy,WebDevelopment,Freelancing,PrivacyPolicy
canonical_url: https://campfire.dlseitz.dev/3-data-privacy-things-to-consider
date: "2025-09-03 03:49:36 UTC"
tags: ["dataprivacy", "WebDevelopment", "Freelancing", "PrivacyPolicy", "Campfire-Logs"]
canonical_url: "https://campfire.dlseitz.dev/3-data-privacy-things-to-consider"
header:
image: /assets/header/#3-data-privacy-things-to-consider.jpg
image: "assets/header/3-data-privacy-things-to-consider.jpg"
attribution: 'Photo by <a href="https://unsplash.com/@heftiba?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Toa Heftiba</a> on <a href="https://unsplash.com/photos/group-of-people-sitting-on-front-firepit-x9I-6yoXrXE?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>'
---

View File

@@ -1,11 +1,12 @@
---
title: #4 - Refactoring A False Sense of Simplicity
title: "#4 - Refactoring A False Sense of Simplicity"
slug: "4-refactoring-a-false-sense-of-simplicity"
published: true
date: 2025-09-12 01:08:54 UTC
tags: refactoring,WebDevelopment,FrontendDevelopment,CSS
canonical_url: https://campfire.dlseitz.dev/4-refactoring-a-false-sense-of-simplicity
date: "2025-09-12 01:08:54 UTC"
tags: ["refactoring", "WebDevelopment", "FrontendDevelopment", "CSS", "Campfire-Logs"]
canonical_url: "https://campfire.dlseitz.dev/4-refactoring-a-false-sense-of-simplicity"
header:
image: '/assets/header/#4-refactoring-a-false-sense-of-simplicity.png'
image: "assets/header/4-refactoring-a-false-sense-of-simplicity.png"
attribution: 'Image generated with Sora. | © 2025 Derek L. Seitz'
---

View File

@@ -1,13 +1,14 @@
---
title: #5 - 'The Power of [Separation] Compels You!'
title: "#5 - 'The Power of [Separation] Compels You!'"
slug: "5-the-power-of-separation-compels-you"
published: true
date: 2025-09-20 21:51:29 UTC
tags: WebDevelopment,refactoring,JavaScript,apiintegration
canonical_url: https://campfire.dlseitz.dev/5-the-power-of-separation-compels-you
date: "2025-09-20 21:51:29 UTC"
tags: ["WebDevelopment", "refactoring", "JavaScript", "apiintegration", "Campfire-Logs"]
canonical_url: "https://campfire.dlseitz.dev/5-the-power-of-separation-compels-you"
header:
image: /assets/header/#5-the-power-of-separation-compels-you.webp
image: "assets/header/5-the-power-of-separation-compels-you.webp"
attribution: 'Image generated with Sora. | © 2025 Derek L. Seitz'
contentImagePath: /assets/#5/
contentImagePath: "./assets/#5/"
---
Hey there, and welcome back to **_Campfire Logs: The Art of Trial & Error_**. In my last log, [_#4 - Refactoring a False Sense of Simplicity_](https://hashnode.com/post/cmfg52ttw000002jufwdvdxkj), I introduced you to the [front end](https://www.computerscience.org/bootcamps/resources/frontend-vs-backend/#:~:text=Front%2Dend%20development%20focuses%20on%20the%20user%2Dfacing%20side%20of%20a%20website.%20Front%2Dend%20developers%20ensure%20that%20visitors%20can%20easily%20interact%20with%20and%20navigate%20sites%20by%20using%20programming%20languages%2C%20design%20skills%2C%20and%20other%20tools.%20They%20produce%20the%20drop%2Ddown%20menus%2C%20layouts%2C%20and%20designs%20for%20websites.) demo I recently [refactored](https://daedtech.com/refactoring-development-technique-not-project/#:~:text=Code%20refactoring%20is%20the%20process%20of%20restructuring%20existing%20computer%20code%20%E2%80%93%20changing%20the%20factoring%20%E2%80%93%20without%20changing%20its%20external%20behavior.) to be more modular and accessibility-friendly. Today, we are going to talk more about that same refactor, but we are going to focus on some of the enhancements I made to the existing features and the new features added for improved interactivity. We are also going to discuss how separating the data, presentation, and logic layers to the demo improved maintainability of the codebase by [decoupling](https://blog.covibe.us/the-pitfalls-of-excessive-decoupling-in-software-development-striking-the-right-balance/#:~:text=Decoupling%2C%20in%20software%20development%2C%20refers%20to%20the%20practice%20of%20breaking%20down%20a%20software%20system%20into%20smaller%2C%20independent%20components%20or%20modules.) its components.

View File

@@ -1,4 +1,4 @@
import matter from 'gray-matter';
import fm from 'front-matter';
const modules = import.meta.glob('./BlogPosts/*.md', { query: '?raw', import: 'default' });
@@ -7,10 +7,10 @@ export async function loadPosts() {
for (const path in modules) {
const fileContent = await modules[path]();
const { data } = matter(fileContent);
const { attributes } = fm(fileContent);
posts.push({
...data,
...attributes,
path,
});
}

View File

@@ -1,13 +1,13 @@
// DashboardPage.jsx
import React from 'react';
import React, { useState, useEffect } from 'react';
import { loadPosts } from '../data/blog-post-data';
import { Button } from '@progress/kendo-react-buttons';
import { Card, CardImage, CardBody, GridLayout } from '@progress/kendo-react-layout';
import PostCard from '../components/Cards/PostCard';
const Dashboard = React.forwardRef((props, ref) => {
// State to hold posts loaded asynchronously
const [blogPosts, setBlogPosts] = useState([]);
// Load posts on component mount
useEffect(() => {
const fetchPosts = async () => {
const postsData = await loadPosts();
@@ -16,7 +16,6 @@ const Dashboard = React.forwardRef((props, ref) => {
fetchPosts();
}, []);
// Separate published and draft posts (no changes here)
const publishedPosts = blogPosts.filter(post => post.published);
const draftPosts = blogPosts.filter(post => !post.published);
@@ -28,20 +27,23 @@ const Dashboard = React.forwardRef((props, ref) => {
<div style={{ textAlign: 'center' }} ref={ref}>
<h1>Dashboard</h1>
<section style={{ textAlign: 'center' }}>
<section style={{ textAlign: 'center', maxWidth: '1200px', margin: '0 auto' }}>
<h2>Published Posts</h2>
{publishedPosts.length ? (
<ul>
<GridLayout
cols={[{ width: "1fr" }, { width: "1fr" }, { width: "1fr" }]}
gap={{ rows: 30, cols: 10 }}
style={{ width: '900px' }}
>
{publishedPosts.map(post => (
<li key={post.slug}>
<a className="k-link" href={post.canonical_url} target="_blank" rel="noreferrer">
{post.title}
</a>{' '}
<span style={{ marginLeft: '30px', padding: '0 20px' }}>{post.date}</span>{' '}
<Button style={{ marginLeft: '30px', padding: '0 20px' }} onClick={() => handleEdit(post.slug)}>Edit</Button>
</li>
// The PostCard automatically becomes a grid item (cell)
<PostCard
key={post.slug}
post={post}
onEdit={handleEdit}
/>
))}
</ul>
</GridLayout>
) : (
<p>No published posts.</p>
)}