From dceef32ba2cfdda8a9dc76d9744eb8c4d8c9428f Mon Sep 17 00:00:00 2001 From: dereklseitz Date: Sat, 27 Sep 2025 11:48:32 -0500 Subject: [PATCH] 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 --- package-lock.json | 315 +++++++++++++++++- package.json | 6 + ...ting-up-camp.jpg => 0-setting-up-camp.jpg} | Bin ...on.jpg => 1-the-great-gitea-migration.jpg} | Bin ... => 2-retrofitting-the-privacy-policy.png} | Bin ... => 3-data-privacy-things-to-consider.jpg} | Bin ...factoring-a-false-sense-of-simplicity.png} | Bin ...-the-power-of-separation-compels-you.webp} | Bin src/components/Cards/Card.jsx | 0 src/components/Cards/PostCard.jsx | 80 +++++ src/components/Editor/WysiwygEditor.jsx | 44 +++ src/components/UI/Copyright.jsx | 2 +- src/data/BlogPosts/0-setting-up-camp.md | 12 +- .../BlogPosts/1-the-great-gitea-migration.md | 11 +- .../2-retrofitting-the-privacy-policy.md | 11 +- .../3-data-privacy-things-to-consider.md | 11 +- ...refactoring-a-false-sense-of-simplicity.md | 11 +- .../5-the-power-of-separation-compels-you.md | 13 +- src/data/blog-post-data.js | 6 +- src/pages/Dashboard.jsx | 30 +- 20 files changed, 500 insertions(+), 52 deletions(-) rename src/assets/header/{#0-setting-up-camp.jpg => 0-setting-up-camp.jpg} (100%) rename src/assets/header/{#1-the-great-gitea-migration.jpg => 1-the-great-gitea-migration.jpg} (100%) rename src/assets/header/{#2-retrofitting-the-privacy-policy.png => 2-retrofitting-the-privacy-policy.png} (100%) rename src/assets/header/{#3-data-privacy-things-to-consider.jpg => 3-data-privacy-things-to-consider.jpg} (100%) rename src/assets/header/{#4-refactoring-a-false-sense-of-simplicity.png => 4-refactoring-a-false-sense-of-simplicity.png} (100%) rename src/assets/header/{#5-the-power-of-separation-compels-you.webp => 5-the-power-of-separation-compels-you.webp} (100%) delete mode 100644 src/components/Cards/Card.jsx create mode 100644 src/components/Cards/PostCard.jsx diff --git a/package-lock.json b/package-lock.json index 5132b5c..f7b501c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,8 +28,14 @@ "@progress/kendo-react-tooltip": "^12.0.1", "@progress/kendo-svg-icons": "^4.5.0", "@progress/kendo-theme-default": "^12.0.1", + "@syncfusion/ej2-react-base": "^31.1.17", + "@syncfusion/ej2-react-layouts": "^31.1.17", + "@syncfusion/ej2-react-richtexteditor": "^31.1.21", "campfire-logs-dashboard": "file:./campfire-logs-dashboard", + "front-matter": "^4.0.2", "gray-matter": "^4.0.3", + "js-yaml": "^4.1.0", + "marked": "^16.3.0", "react": "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^7.9.1" @@ -2015,6 +2021,270 @@ "win32" ] }, + "node_modules/@syncfusion/ej2-base": { + "version": "31.1.20", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-31.1.20.tgz", + "integrity": "sha512-sFAvaZSfxQnccxLMUgLX1iEmEjxyykSU7bBO6Z+c89+k38+5fGD4LhhRWgsf2kBU88AqYy95yS8oUAlga2TKFQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-icons": "~31.1.17" + }, + "bin": { + "syncfusion-license": "bin/syncfusion-license.js" + } + }, + "node_modules/@syncfusion/ej2-buttons": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-buttons/-/ej2-buttons-31.1.21.tgz", + "integrity": "sha512-NcBMK19Yn7//bwbREsdvqy3v1237AzulrphSmeS9SrKMd7L5H6apeqFe1BAkvAs/K+JPpt6A3Dv6Fhl3SkVcUg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20" + } + }, + "node_modules/@syncfusion/ej2-calendars": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-calendars/-/ej2-calendars-31.1.21.tgz", + "integrity": "sha512-UZNF+plh6GnA8K9jG/drj2LI30lVdQmvQVB14G9Chdy4tqRHjcnr6C3VDrIIBjg0QSeBwzCk/BcXhrARxVpRBA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.21", + "@syncfusion/ej2-inputs": "~31.1.21", + "@syncfusion/ej2-lists": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.20" + } + }, + "node_modules/@syncfusion/ej2-compression": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-compression/-/ej2-compression-31.1.17.tgz", + "integrity": "sha512-Gl+IDD9GEiTc/lIsMPNGVNDqQojL7q2qehVuFGjaXxE9xKzOiU805+uuhmbJg3kldHFNsdAMDJeKPy4ubwf8bg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-file-utils": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-data": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-data/-/ej2-data-31.1.17.tgz", + "integrity": "sha512-jytTgUnRqGoXfzOcvRJ0p+KAPFOD53He3+y3AX8zrej/oPcMtt5iHNc44AvJ7XhpYUHLVvcSFYTd0395q/VPLA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-dropdowns": { + "version": "31.1.20", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-dropdowns/-/ej2-dropdowns-31.1.20.tgz", + "integrity": "sha512-YuNrt8sWiTZcm2AWHYS6PK9qzQCcc9AsuVQbtvJBrMEiWYKLAPUJGM3Ig/gIwpAFBAyq6TKC739J9DzhdgW+wg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-data": "~31.1.17", + "@syncfusion/ej2-inputs": "~31.1.20", + "@syncfusion/ej2-lists": "~31.1.17", + "@syncfusion/ej2-navigations": "~31.1.20", + "@syncfusion/ej2-notifications": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.20" + } + }, + "node_modules/@syncfusion/ej2-excel-export": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-excel-export/-/ej2-excel-export-31.1.17.tgz", + "integrity": "sha512-m6GyoLkp1miq/GDYTr471x2+2ZKHKfGvXRcAK2d9xNOG+kBKFxhbt8AkZxbnhFZXeaAfpKkv5kZ0uCpbsPPwgw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17", + "@syncfusion/ej2-compression": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-file-utils": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-file-utils/-/ej2-file-utils-31.1.17.tgz", + "integrity": "sha512-CzCB3KSIKRJo9VG7uP5pqPyH8shR0pBM2UDRP9BH2Gvl8YjLmV6QoPWa5aMCkFX0t0BHn1rlfh9sk+rq6LmD8Q==", + "license": "SEE LICENSE IN license" + }, + "node_modules/@syncfusion/ej2-filemanager": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-filemanager/-/ej2-filemanager-31.1.21.tgz", + "integrity": "sha512-va82snsJTQbDDphfEWqSOJUt1H80nL11NFb/ut4GKa4k/5Z0Vdj3wHSzMQ+1rDsP/lytls9IvBa8WhEHAN+m8w==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.21", + "@syncfusion/ej2-data": "~31.1.17", + "@syncfusion/ej2-grids": "~31.1.21", + "@syncfusion/ej2-inputs": "~31.1.21", + "@syncfusion/ej2-layouts": "~31.1.17", + "@syncfusion/ej2-lists": "~31.1.17", + "@syncfusion/ej2-navigations": "~31.1.20", + "@syncfusion/ej2-popups": "~31.1.20", + "@syncfusion/ej2-splitbuttons": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-grids": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-grids/-/ej2-grids-31.1.21.tgz", + "integrity": "sha512-gdHGQQCG8wdZ6Nh26d9mRaDX5T37afWoiMvH4AJQ7tyoLI1Ynkc1kT7iv0msYcE+SDbyCXqfRhJAWxdlrBcJTw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.21", + "@syncfusion/ej2-calendars": "~31.1.21", + "@syncfusion/ej2-compression": "~31.1.17", + "@syncfusion/ej2-data": "~31.1.17", + "@syncfusion/ej2-dropdowns": "~31.1.20", + "@syncfusion/ej2-excel-export": "~31.1.17", + "@syncfusion/ej2-file-utils": "~31.1.17", + "@syncfusion/ej2-inputs": "~31.1.21", + "@syncfusion/ej2-lists": "~31.1.17", + "@syncfusion/ej2-navigations": "~31.1.20", + "@syncfusion/ej2-notifications": "~31.1.17", + "@syncfusion/ej2-pdf-export": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.20", + "@syncfusion/ej2-splitbuttons": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-icons": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-icons/-/ej2-icons-31.1.17.tgz", + "integrity": "sha512-5IkOJyoUL7MoOjRE+hf2jC/dCjS+Q1Z34oz49mSvK1vKc2kcYR5Wa7o49uwjAgmJUSZK8O2cGE0M3HHSBFIKSw==", + "license": "SEE LICENSE IN license" + }, + "node_modules/@syncfusion/ej2-inputs": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-31.1.21.tgz", + "integrity": "sha512-kjIGhN6NYAOTOV18niCpx/oijuAlDTszNJQolQQmi+wfeVkkb6YDHSyL0da8zhPbNYs4gITvbMwQlMwPoalKHw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.21", + "@syncfusion/ej2-popups": "~31.1.20", + "@syncfusion/ej2-splitbuttons": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-layouts": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-layouts/-/ej2-layouts-31.1.17.tgz", + "integrity": "sha512-M03yuIn5bdDlJ+Zk8i2E2z8b6iU11fPOTENUVW8RjADJvuWOeCO854eAF6BZxhPl7hAEk0QzVfd0EykmV2M74A==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-lists": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-lists/-/ej2-lists-31.1.17.tgz", + "integrity": "sha512-EyU/yUflRQ2hsGvOiguCdqdAeyUmojwllVgnx89MZyJ4zuCSU+OhbETJZ7BB2+8uhleq1cJ3wTY2PtHpBrPErw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17", + "@syncfusion/ej2-buttons": "~31.1.17", + "@syncfusion/ej2-data": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-navigations": { + "version": "31.1.20", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-navigations/-/ej2-navigations-31.1.20.tgz", + "integrity": "sha512-KzJT4vHbMF9ooq8o41xwYUJX70Dr96mMEaRYJusRv/RJ+1zM/UvHhgX8n15TIFTXexH1y+KwkFvYaGchcokBgA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.17", + "@syncfusion/ej2-data": "~31.1.17", + "@syncfusion/ej2-inputs": "~31.1.20", + "@syncfusion/ej2-lists": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.20" + } + }, + "node_modules/@syncfusion/ej2-notifications": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-notifications/-/ej2-notifications-31.1.17.tgz", + "integrity": "sha512-1D3qVwrwXq6Umwz/SRTMqY0eBLvh57uswk+8CYVirPMVef9CK/pC70KOoVFoy3xkBg6iFaxud4bZwVDCCRMHHQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17", + "@syncfusion/ej2-buttons": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-pdf-export": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-pdf-export/-/ej2-pdf-export-31.1.17.tgz", + "integrity": "sha512-GbUMp2e8NpiZ/FwjDhPrUcXwPcXI5N8m2eENnqQYV7WRJCnHjjd9IEVTpZJeGRZNVcMPcauMng0Vxz2La7FBlA==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-compression": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-popups": { + "version": "31.1.20", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-popups/-/ej2-popups-31.1.20.tgz", + "integrity": "sha512-GRPZWX2ZRNS73GpG8NbhC12eBNNqdMR80nhJrun1ZBVfvPTvd6y5/qvJLWg6/kYN02tM+smgyxfxfwOH4HGi3w==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-react-base": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-base/-/ej2-react-base-31.1.17.tgz", + "integrity": "sha512-PvR2kvfMEjRE2kOcBSboMMEVkLNsDs9VV5tSfXzYR4aQ5gOvbQcVRQljxjj6lTTG7z3P5OAP6HxYJ0BMLe+E4g==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-react-layouts": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-layouts/-/ej2-react-layouts-31.1.17.tgz", + "integrity": "sha512-lOgadIKeUJQljWigjJsbRTGcfBin4PB4UjeHhnssVkOlNAw4xfnk865Fa94YCMPeqx/BhnqDyQKzCJTcS01R/A==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17", + "@syncfusion/ej2-layouts": "31.1.17", + "@syncfusion/ej2-react-base": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-react-richtexteditor": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-richtexteditor/-/ej2-react-richtexteditor-31.1.21.tgz", + "integrity": "sha512-Dq/vqJLQTAn0p+A4EFcTQ1XbbLxCW18qHIp6WVR2VJrfZeMRqQepwyRP6JyUFiEh7lzjEb0VfFp/a8SH24yIUQ==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-react-base": "~31.1.17", + "@syncfusion/ej2-richtexteditor": "31.1.21" + } + }, + "node_modules/@syncfusion/ej2-richtexteditor": { + "version": "31.1.21", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-richtexteditor/-/ej2-richtexteditor-31.1.21.tgz", + "integrity": "sha512-QVIo0R8KqHr3cd2KSzfDWmmC/EvhHHzf8palZdA7J8gC+QO6EC8uQrthFbT3rbWF0tffNOa+Q01vPwcKot+beg==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.20", + "@syncfusion/ej2-buttons": "~31.1.21", + "@syncfusion/ej2-dropdowns": "~31.1.20", + "@syncfusion/ej2-filemanager": "~31.1.21", + "@syncfusion/ej2-inputs": "~31.1.21", + "@syncfusion/ej2-navigations": "~31.1.20", + "@syncfusion/ej2-popups": "~31.1.20", + "@syncfusion/ej2-splitbuttons": "~31.1.17" + } + }, + "node_modules/@syncfusion/ej2-splitbuttons": { + "version": "31.1.17", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-splitbuttons/-/ej2-splitbuttons-31.1.17.tgz", + "integrity": "sha512-8XA9t2wVtSonGnpblqS3FgEoS8sYy6GOh3p99gDKyPI55UYwUcvuHsBaTA/+w4tGwZ5wiNQMKXWaanHXIGvovw==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~31.1.17", + "@syncfusion/ej2-popups": "~31.1.17" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2186,7 +2456,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/asynckit": { @@ -2965,6 +3234,37 @@ "node": ">= 6" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3320,7 +3620,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3551,6 +3850,18 @@ "yallist": "^3.0.2" } }, + "node_modules/marked": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", + "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", diff --git a/package.json b/package.json index 8961b16..3697a4e 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,14 @@ "@progress/kendo-react-tooltip": "^12.0.1", "@progress/kendo-svg-icons": "^4.5.0", "@progress/kendo-theme-default": "^12.0.1", + "@syncfusion/ej2-react-base": "^31.1.17", + "@syncfusion/ej2-react-layouts": "^31.1.17", + "@syncfusion/ej2-react-richtexteditor": "^31.1.21", "campfire-logs-dashboard": "file:./campfire-logs-dashboard", + "front-matter": "^4.0.2", "gray-matter": "^4.0.3", + "js-yaml": "^4.1.0", + "marked": "^16.3.0", "react": "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^7.9.1" diff --git a/src/assets/header/#0-setting-up-camp.jpg b/src/assets/header/0-setting-up-camp.jpg similarity index 100% rename from src/assets/header/#0-setting-up-camp.jpg rename to src/assets/header/0-setting-up-camp.jpg diff --git a/src/assets/header/#1-the-great-gitea-migration.jpg b/src/assets/header/1-the-great-gitea-migration.jpg similarity index 100% rename from src/assets/header/#1-the-great-gitea-migration.jpg rename to src/assets/header/1-the-great-gitea-migration.jpg diff --git a/src/assets/header/#2-retrofitting-the-privacy-policy.png b/src/assets/header/2-retrofitting-the-privacy-policy.png similarity index 100% rename from src/assets/header/#2-retrofitting-the-privacy-policy.png rename to src/assets/header/2-retrofitting-the-privacy-policy.png diff --git a/src/assets/header/#3-data-privacy-things-to-consider.jpg b/src/assets/header/3-data-privacy-things-to-consider.jpg similarity index 100% rename from src/assets/header/#3-data-privacy-things-to-consider.jpg rename to src/assets/header/3-data-privacy-things-to-consider.jpg diff --git a/src/assets/header/#4-refactoring-a-false-sense-of-simplicity.png b/src/assets/header/4-refactoring-a-false-sense-of-simplicity.png similarity index 100% rename from src/assets/header/#4-refactoring-a-false-sense-of-simplicity.png rename to src/assets/header/4-refactoring-a-false-sense-of-simplicity.png diff --git a/src/assets/header/#5-the-power-of-separation-compels-you.webp b/src/assets/header/5-the-power-of-separation-compels-you.webp similarity index 100% rename from src/assets/header/#5-the-power-of-separation-compels-you.webp rename to src/assets/header/5-the-power-of-separation-compels-you.webp diff --git a/src/components/Cards/Card.jsx b/src/components/Cards/Card.jsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Cards/PostCard.jsx b/src/components/Cards/PostCard.jsx new file mode 100644 index 0000000..017cea2 --- /dev/null +++ b/src/components/Cards/PostCard.jsx @@ -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 ( + + + {/* 1. Thumbnail Image */} + + + {/* 2. Title, Date, and Button Container */} + + + {/* Title Link */} +
+ + {post.title} + +
+ + {/* Date and Edit Button */} +
+ {formatDate(post.date)} + +
+ +
+
+ ); +}; + +export default PostCard; \ No newline at end of file diff --git a/src/components/Editor/WysiwygEditor.jsx b/src/components/Editor/WysiwygEditor.jsx index e69de29..69903d7 100644 --- a/src/components/Editor/WysiwygEditor.jsx +++ b/src/components/Editor/WysiwygEditor.jsx @@ -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 ( + + + + ); +}; + +export default CampfireToggleEditor; \ No newline at end of file diff --git a/src/components/UI/Copyright.jsx b/src/components/UI/Copyright.jsx index cdca40a..22f8eef 100644 --- a/src/components/UI/Copyright.jsx +++ b/src/components/UI/Copyright.jsx @@ -11,7 +11,7 @@ export default function Copyright() { >

© 2025{" | "}Derek L. Seitz{" | "} - + dlseitz.dev

diff --git a/src/data/BlogPosts/0-setting-up-camp.md b/src/data/BlogPosts/0-setting-up-camp.md index 33aedb3..3e78f1a 100644 --- a/src/data/BlogPosts/0-setting-up-camp.md +++ b/src/data/BlogPosts/0-setting-up-camp.md @@ -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 Kemal Berkay Dogan on Unsplash' --- diff --git a/src/data/BlogPosts/1-the-great-gitea-migration.md b/src/data/BlogPosts/1-the-great-gitea-migration.md index 9816516..6fff322 100644 --- a/src/data/BlogPosts/1-the-great-gitea-migration.md +++ b/src/data/BlogPosts/1-the-great-gitea-migration.md @@ -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 Leon Contreras on Unsplash' --- diff --git a/src/data/BlogPosts/2-retrofitting-the-privacy-policy.md b/src/data/BlogPosts/2-retrofitting-the-privacy-policy.md index 6083bdf..97fe263 100644 --- a/src/data/BlogPosts/2-retrofitting-the-privacy-policy.md +++ b/src/data/BlogPosts/2-retrofitting-the-privacy-policy.md @@ -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' --- diff --git a/src/data/BlogPosts/3-data-privacy-things-to-consider.md b/src/data/BlogPosts/3-data-privacy-things-to-consider.md index 288b7e6..723e48b 100644 --- a/src/data/BlogPosts/3-data-privacy-things-to-consider.md +++ b/src/data/BlogPosts/3-data-privacy-things-to-consider.md @@ -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 Toa Heftiba on Unsplash' --- diff --git a/src/data/BlogPosts/4-refactoring-a-false-sense-of-simplicity.md b/src/data/BlogPosts/4-refactoring-a-false-sense-of-simplicity.md index 6df1da9..5a29caf 100644 --- a/src/data/BlogPosts/4-refactoring-a-false-sense-of-simplicity.md +++ b/src/data/BlogPosts/4-refactoring-a-false-sense-of-simplicity.md @@ -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' --- diff --git a/src/data/BlogPosts/5-the-power-of-separation-compels-you.md b/src/data/BlogPosts/5-the-power-of-separation-compels-you.md index b31a2ad..038e80e 100644 --- a/src/data/BlogPosts/5-the-power-of-separation-compels-you.md +++ b/src/data/BlogPosts/5-the-power-of-separation-compels-you.md @@ -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. diff --git a/src/data/blog-post-data.js b/src/data/blog-post-data.js index 4ef40df..bab8dcd 100644 --- a/src/data/blog-post-data.js +++ b/src/data/blog-post-data.js @@ -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, }); } diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index bc68088..1a6d410 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -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) => {

Dashboard

-
+

Published Posts

{publishedPosts.length ? ( -
    + {publishedPosts.map(post => ( -
  • - - {post.title} - {' '} - {post.date}{' '} - -
  • + // The PostCard automatically becomes a grid item (cell) + ))} -
+ ) : (

No published posts.

)}