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 (
+
© 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) => {No published posts.
)}