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

315
package-lock.json generated
View File

@@ -28,8 +28,14 @@
"@progress/kendo-react-tooltip": "^12.0.1", "@progress/kendo-react-tooltip": "^12.0.1",
"@progress/kendo-svg-icons": "^4.5.0", "@progress/kendo-svg-icons": "^4.5.0",
"@progress/kendo-theme-default": "^12.0.1", "@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", "campfire-logs-dashboard": "file:./campfire-logs-dashboard",
"front-matter": "^4.0.2",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"js-yaml": "^4.1.0",
"marked": "^16.3.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.9.1" "react-router-dom": "^7.9.1"
@@ -2015,6 +2021,270 @@
"win32" "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": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2186,7 +2456,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0" "license": "Python-2.0"
}, },
"node_modules/asynckit": { "node_modules/asynckit": {
@@ -2965,6 +3234,37 @@
"node": ">= 6" "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": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3320,7 +3620,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -3551,6 +3850,18 @@
"yallist": "^3.0.2" "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": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",

View File

@@ -30,8 +30,14 @@
"@progress/kendo-react-tooltip": "^12.0.1", "@progress/kendo-react-tooltip": "^12.0.1",
"@progress/kendo-svg-icons": "^4.5.0", "@progress/kendo-svg-icons": "^4.5.0",
"@progress/kendo-theme-default": "^12.0.1", "@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", "campfire-logs-dashboard": "file:./campfire-logs-dashboard",
"front-matter": "^4.0.2",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"js-yaml": "^4.1.0",
"marked": "^16.3.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.9.1" "react-router-dom": "^7.9.1"

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> <p>
&copy; 2025{" | "}Derek L. Seitz{" | "} &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 dlseitz.dev
</a> </a>
</p> </p>

View File

@@ -1,13 +1,13 @@
--- ---
title: #0 - Setting Up Camp title: "#0 - Setting Up Camp"
slug: 0-setting-up-camp slug: "0-setting-up-camp"
fileName: fileName:
published: true published: true
date: 2025-08-24 05:00:00 UTC date: "2025-08-24 05:00:00 UTC"
tags: fullstack,developerjourney,BuildInPublic,introduction tags: ["fullstack", "developerjourney", "BuildInPublic", "introduction", "Campfire-Logs"]
canonical_url: https://campfire.dlseitz.dev/0-setting-up-camp canonical_url: "https://campfire.dlseitz.dev/0-setting-up-camp"
header: 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>' 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 published: true
date: 2025-08-27 20:19:51 UTC date: "2025-08-27 20:19:51 UTC"
tags: CampfireLogs,SelfHosting,Devops,gitea tags: ["CampfireLogs", "SelfHosting", "Devops", "gitea", "Campfire-Logs"]
canonical_url: https://campfire.dlseitz.dev/1-the-great-gitea-migration canonical_url: "https://campfire.dlseitz.dev/1-the-great-gitea-migration"
header: 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>' 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 published: true
date: 2025-09-02 20:41:21 UTC date: "2025-09-02 20:41:21 UTC"
tags: WebDevelopment,FullStackDevelopment,Nodejs,DevLife tags: ["WebDevelopment", "FullStackDevelopment", "Nodejs", "DevLife", "Campfire-Logs"]
canonical_url: https://campfire.dlseitz.dev/2-retrofitting-the-privacy-policy canonical_url: "https://campfire.dlseitz.dev/2-retrofitting-the-privacy-policy"
header: 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' 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 published: true
date: 2025-09-03 03:49:36 UTC date: "2025-09-03 03:49:36 UTC"
tags: dataprivacy,WebDevelopment,Freelancing,PrivacyPolicy tags: ["dataprivacy", "WebDevelopment", "Freelancing", "PrivacyPolicy", "Campfire-Logs"]
canonical_url: https://campfire.dlseitz.dev/3-data-privacy-things-to-consider canonical_url: "https://campfire.dlseitz.dev/3-data-privacy-things-to-consider"
header: 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>' 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 published: true
date: 2025-09-12 01:08:54 UTC date: "2025-09-12 01:08:54 UTC"
tags: refactoring,WebDevelopment,FrontendDevelopment,CSS tags: ["refactoring", "WebDevelopment", "FrontendDevelopment", "CSS", "Campfire-Logs"]
canonical_url: https://campfire.dlseitz.dev/4-refactoring-a-false-sense-of-simplicity canonical_url: "https://campfire.dlseitz.dev/4-refactoring-a-false-sense-of-simplicity"
header: 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' 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 published: true
date: 2025-09-20 21:51:29 UTC date: "2025-09-20 21:51:29 UTC"
tags: WebDevelopment,refactoring,JavaScript,apiintegration tags: ["WebDevelopment", "refactoring", "JavaScript", "apiintegration", "Campfire-Logs"]
canonical_url: https://campfire.dlseitz.dev/5-the-power-of-separation-compels-you canonical_url: "https://campfire.dlseitz.dev/5-the-power-of-separation-compels-you"
header: 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' 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. 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' }); const modules = import.meta.glob('./BlogPosts/*.md', { query: '?raw', import: 'default' });
@@ -7,10 +7,10 @@ export async function loadPosts() {
for (const path in modules) { for (const path in modules) {
const fileContent = await modules[path](); const fileContent = await modules[path]();
const { data } = matter(fileContent); const { attributes } = fm(fileContent);
posts.push({ posts.push({
...data, ...attributes,
path, path,
}); });
} }

View File

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