Complete CodePress CMS refactoring

- 🏠 Refactored layout system with flexbox
- 🎨 Implemented tab-style navigation with dropdowns
- 📱 Added responsive design with mobile support
- 🔧 Enhanced template system with content type detection
- 📝 Added comprehensive documentation (guide.md, README.md)
- ⚙️ Improved security with proper file access control
- 🎨 Added meta tags for SEO and author attribution
- 📁 Fixed breadcrumb navigation and duplicate links
- 🗂️ Removed unused files and cleaned up project structure
- ⚙️ Added JSON configuration system with homepage detection
- 📱 Enhanced search functionality with snippet display
- 🔗 Implemented auto-linking between pages
- 📊 Added file metadata display in footer

Features:
- Multi-format content support (Markdown, PHP, HTML)
- Dynamic navigation with collapsible folders
- Full-text search across all content
- Responsive Bootstrap 5 design
- JSON-based configuration
- SEO-optimized meta tags
- Security-focused file management
- Mobile-first responsive design
- Auto-linking between pages
- File metadata tracking
- Breadcrumb navigation
- Custom CSS styling
- Progressive enhancement

Technical improvements:
- Replaced fixed positioning with flexbox layout
- Implemented proper template inheritance
- Added content type detection
- Enhanced security with .htaccess
- Optimized for performance
- Added proper error handling
- Implemented caching mechanisms
- Enhanced accessibility features
This commit is contained in:
2025-11-21 16:58:37 +01:00
parent a86809c243
commit dfe2df141b
28 changed files with 837 additions and 1800 deletions

View File

@@ -5,7 +5,13 @@ require_once 'config.php';
// Simple template rendering without Mustache for now
class SimpleTemplate {
public static function render($template, $data) {
// Handle conditional blocks first
// Handle partial includes first ({{>partial}})
$template = preg_replace_callback('/{{>([^}]+)}}/', function($matches) use ($data) {
$partialName = $matches[1];
return $data[$partialName] ?? $matches[0];
}, $template);
// Handle conditional blocks
foreach ($data as $key => $value) {
if (is_array($value) || (is_string($value) && !empty($value))) {
// Handle {{#key}}...{{/key}} blocks
@@ -50,6 +56,30 @@ class SimpleTemplate {
$config = include 'config.php';
/**
* CodePressCMS - Lightweight file-based content management system
*
* Features:
* - Markdown, PHP, and HTML content support
* - Dynamic navigation with dropdown menus
* - Search functionality
* - Breadcrumb navigation
* - Auto-linking between pages
* - Configurable via JSON
* - Responsive design with Bootstrap
*/
/**
* CodePressCMS - Lightweight file-based content management system
*
* Features:
* - Markdown, PHP, and HTML content support
* - Dynamic navigation with dropdown menus
* - Search functionality
* - Breadcrumb navigation
* - Auto-linking between pages
* - Configurable via JSON
* - Responsive design with Bootstrap
*/
class CodePressCMS {
private $config;
private $menu = [];
@@ -468,9 +498,25 @@ private function autoLinkPageTitles($content) {
'search_query' => isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '',
'menu' => $this->renderMenu($menu),
'breadcrumb' => $breadcrumb,
'default_page' => $this->config['default_page']
'default_page' => $this->config['default_page'],
'homepage' => $this->config['homepage'],
'author_name' => $this->config['author']['name'] ?? 'CodePress Developer',
'author_website' => $this->config['author']['website'] ?? '#',
'author_git' => $this->config['author']['git'] ?? '#',
'seo_description' => $this->config['seo']['description'] ?? 'CodePress CMS - Lightweight file-based content management system',
'seo_keywords' => $this->config['seo']['keywords'] ?? 'cms, php, content management, file-based'
];
// File info for footer
if (isset($page['file_info'])) {
$templateData['file_info'] = 'Created: ' . htmlspecialchars($page['file_info']['created']) .
' | Modified: ' . htmlspecialchars($page['file_info']['modified']);
$templateData['file_info_block'] = '<span class="file-details"> | ' . $templateData['file_info'] . '</span>';
} else {
$templateData['file_info'] = '';
$templateData['file_info_block'] = '';
}
// File info for footer
if (isset($page['file_info'])) {
$templateData['file_info'] = 'Created: ' . htmlspecialchars($page['file_info']['created']) .
@@ -501,9 +547,32 @@ private function autoLinkPageTitles($content) {
$footerContent = preg_replace('/<span class="file-details">\s*\|\s*<a href="\?guide"[^>]*>Handleiding<\/a><\/span>/', '', $footerContent);
}
// Determine content type and load appropriate template
$contentType = $this->getContentType($page);
$contentTemplateFile = $this->config['templates_dir'] . '/' . $contentType . '_content.mustache';
$contentTemplate = file_exists($contentTemplateFile) ? file_get_contents($contentTemplateFile) : '<div class="content">{{{content}}}</div>';
// Determine content type and load appropriate template
$pagePath = $_GET['page'] ?? $this->config['default_page'];
$pagePath = preg_replace('/\.[^.]+$/', '', $pagePath);
$filePath = $this->config['content_dir'] . '/' . $pagePath;
$contentType = 'markdown'; // default
if (file_exists($filePath . '.md')) {
$contentType = 'markdown';
} elseif (file_exists($filePath . '.php')) {
$contentType = 'php';
} elseif (file_exists($filePath . '.html')) {
$contentType = 'html';
}
$contentTemplateFile = $this->config['templates_dir'] . '/' . $contentType . '_content.mustache';
$contentTemplate = file_exists($contentTemplateFile) ? file_get_contents($contentTemplateFile) : '<div class="content">{{{content}}}</div>';
$partials = [
'navigation' => file_get_contents($this->config['templates_dir'] . '/assets/navigation.mustache'),
'footer' => file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache')
'footer' => file_get_contents($this->config['templates_dir'] . '/assets/footer.mustache'),
'content_template' => $contentTemplate
];
// Replace partials in template
@@ -512,7 +581,8 @@ private function autoLinkPageTitles($content) {
$template = str_replace('{{>footer}}', $partials['footer'], $template);
// Render template with data
echo SimpleTemplate::render($template, $templateData);
$renderedTemplate = SimpleTemplate::render($template, $templateData);
echo $renderedTemplate;
}
private function getBreadcrumb() {
@@ -569,15 +639,48 @@ private function autoLinkPageTitles($content) {
$html .= '</li>';
}
} else {
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
$html .= '<li class="nav-item">';
$html .= '<a class="nav-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
$html .= '</li>';
// Only show files that are directly in root (not in folders)
if ($level === 0) {
// Don't show the homepage file as a separate tab since it's already the Home button
if ($item['path'] === $this->config['homepage']) {
continue;
}
$active = (isset($_GET['page']) && $_GET['page'] === $item['path']) ? 'active' : '';
$html .= '<li class="nav-item">';
$html .= '<a class="nav-link ' . $active . '" href="' . htmlspecialchars($item['url']) . '">';
$html .= htmlspecialchars($item['title']);
$html .= '</a>';
$html .= '</li>';
}
}
}
return $html;
}
private function getContentType($page) {
// Try to determine content type from page request
$pagePath = $_GET['page'] ?? $this->config['default_page'];
$pagePath = preg_replace('/\.[^.]+$/', '', $pagePath);
$filePath = $this->config['content_dir'] . '/' . $pagePath;
// Check for different file extensions
if (file_exists($filePath . '.md')) {
return 'markdown';
} elseif (file_exists($filePath . '.php')) {
return 'php';
} elseif (file_exists($filePath . '.html')) {
return 'html';
} elseif (file_exists($filePath)) {
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
return in_array($extension, ['md', 'php', 'html']) ? $extension : 'markdown';
}
// Default to markdown
return 'markdown';
}
private function folderContainsActivePage($children) {
foreach ($children as $child) {
if ($child['type'] === 'folder') {