Media browser: recursive scan entire content/ tree, /-media/ URL prefix, editor change detection fix

- handleMediaList() now scans content/ recursively for all media files
- URLs use /-media/ prefix mapping directly to content/ (no special cases)
- index.php: added /-media/ route, kept /-assets/ for backward compat
- editor-toolbar.js: fixed editor.on('change') placement (was inside switchMode)
- content-edit.php and content-new.php: back-btn unsaved-changes detection
- Removed unused __editorCleanup global
This commit is contained in:
2026-06-24 17:00:59 +02:00
parent d97c67c6a9
commit dc0d370e65
50 changed files with 2796 additions and 140 deletions

View File

@@ -45,7 +45,7 @@ class CodePressCMS {
$this->currentLanguage = $this->getCurrentLanguage();
$this->translations = $this->loadTranslations($this->currentLanguage);
// Initialize plugin manager (files already loaded in engine/core/index.php)
// Initialize plugin manager (files already loaded in cms/core/index.php)
$this->pluginManager = new PluginManager(__DIR__ . '/../../../plugins');
$api = new CMSAPI($this);
$this->pluginManager->setAPI($api);
@@ -182,7 +182,10 @@ class CodePressCMS {
$result = [];
foreach ($items as $item) {
if ($item[0] === '.') continue;
if ($item[0] === '.' || $item[0] === '-') continue;
// Skip assets directory (old name, kept for safety)
if ($item === 'assets' && is_dir($dir . '/' . $item)) continue;
// Skip language-specific content that doesn't match current language
$availableLangs = array_keys($this->getAvailableLanguages());
@@ -640,6 +643,12 @@ class CodePressCMS {
* @return string Formatted display name
*/
private function formatDisplayName($filename) {
// Preserve leading dash before processing
$hasLeadingDash = $filename[0] === '-';
if ($hasLeadingDash) {
$filename = substr($filename, 1);
}
// Remove language prefixes dynamically based on available languages
$availableLangs = array_keys($this->getAvailableLanguages());
$langPattern = '/^(' . implode('|', $availableLangs) . ')\.(.+)$/';
@@ -656,11 +665,12 @@ class CodePressCMS {
'ict' => 'ICT',
];
if (isset($specialCases[strtolower($filename)])) {
return $specialCases[strtolower($filename)];
return ($hasLeadingDash ? '- ' : '') . $specialCases[strtolower($filename)];
}
// Replace hyphens and underscores with spaces, then title case
$name = str_replace(['-', '_'], ' ', $filename);
$name = trim($name);
$name = ucwords(strtolower($name));
// Post-process special cases in compound names
@@ -668,7 +678,7 @@ class CodePressCMS {
$name = str_ireplace(ucfirst($lower), $correct, $name);
}
return $name;
return ($hasLeadingDash ? '- ' : '') . $name;
}
/**
@@ -983,8 +993,12 @@ private function getGuidePage() {
// Get homepage title
$homepageTitle = $this->getHomepageTitle();
// Get sidebar content from plugins
$sidebarContent = $this->pluginManager->getSidebarContent();
// Get sidebar content from plugins (filtered per-page if specified in frontmatter)
$allowedPlugins = null;
if (!empty($page['metadata']['plugins'])) {
$allowedPlugins = array_map('trim', explode(',', $page['metadata']['plugins']));
}
$sidebarContent = $this->pluginManager->getSidebarContent($allowedPlugins);
// Get layout from page metadata
$layout = $page['layout'] ?? 'sidebar-content';
@@ -994,7 +1008,7 @@ private function getGuidePage() {
'site_title' => $this->config['site_title'],
'page_title' => htmlspecialchars($page['title']),
'content' => $page['content'],
'content' => $this->processContent($page['content']),
'sidebar_content' => $sidebarContent,
'layout' => $layout,
'page_metadata' => $page['metadata'] ?? [],
@@ -1017,10 +1031,14 @@ private function getGuidePage() {
// Theme colors
'header_color' => $this->config['theme']['header_color'] ?? '#0d6efd',
'header_font_color' => $this->config['theme']['header_font_color'] ?? '#ffffff',
'header_height' => $this->config['theme']['header_height'] ?? '56',
'navigation_color' => $this->config['theme']['navigation_color'] ?? '#f8f9fa',
'navigation_font_color' => $this->config['theme']['navigation_font_color'] ?? '#000000',
'nav_height' => $this->config['theme']['nav_height'] ?? '42',
'sidebar_background' => $this->config['theme']['sidebar_background'] ?? '#f8f9fa',
'sidebar_border' => $this->config['theme']['sidebar_border'] ?? '#dee2e6',
'background_image_css' => $this->getBackgroundImageCss(),
'background_image_opacity' => $this->getBackgroundImageOpacity(),
// Language
'current_lang' => $this->currentLanguage,
'current_lang_upper' => strtoupper($this->currentLanguage),
@@ -1286,4 +1304,27 @@ private function getGuidePage() {
}
return false;
}
private function getBackgroundImageCss(): string
{
$bg = $this->config['theme']['background_image'] ?? '';
if (empty($bg)) {
return 'none';
}
if (str_starts_with($bg, 'http')) {
return 'url(' . $bg . ')';
}
return 'url(/themes/' . $bg . ')';
}
private function getBackgroundImageOpacity(): int
{
$opacity = intval($this->config['theme']['background_image_opacity'] ?? 100);
return max(0, min(100, $opacity));
}
private function processContent(string $content): string
{
return str_replace('-/assets/', '/-assets/', $content);
}
}

View File

@@ -17,6 +17,17 @@ if (file_exists($configJsonPath)) {
$config['templates_dir'] = $projectRoot . $config['templates_dir'];
}
// Load active theme
$activeTheme = $config['active_theme'] ?? 'default';
$themeDir = __DIR__ . '/../../themes/' . $activeTheme;
$themeFile = $themeDir . '/theme.json';
if (file_exists($themeFile)) {
$themeConfig = json_decode(file_get_contents($themeFile), true);
$config['theme'] = $themeConfig;
} else {
$config['theme'] = [];
}
return $config;
}
}

View File

@@ -16,7 +16,6 @@ class PluginManager
{
$this->api = $api;
// Inject API into all plugins that have setAPI method
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, 'setAPI')) {
$plugin->setAPI($api);
@@ -43,7 +42,6 @@ class PluginManager
if (class_exists($className)) {
$this->plugins[$pluginName] = new $className();
// Inject API if already available
if ($this->api && method_exists($this->plugins[$pluginName], 'setAPI')) {
$this->plugins[$pluginName]->setAPI($this->api);
}
@@ -62,14 +60,49 @@ class PluginManager
return $this->plugins;
}
public function getSidebarContent(): string
public function isPluginViewable(object $plugin): bool
{
if (method_exists($plugin, 'getConfig')) {
$config = $plugin->getConfig();
return !isset($config['viewable']) || $config['viewable'] !== false;
}
return true;
}
public function getSidebarContent(?array $allowedPlugins = null): string
{
$sidebarContent = '';
foreach ($this->plugins as $plugin) {
if (method_exists($plugin, 'getSidebarContent')) {
$sidebarContent .= $plugin->getSidebarContent();
foreach ($this->plugins as $pluginName => $plugin) {
if (!$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
continue;
}
// Filter by allowed plugins for this page
if ($allowedPlugins !== null && !in_array($pluginName, $allowedPlugins, true)) {
continue;
}
$content = $plugin->getSidebarContent();
if (trim($content) === '') {
continue;
}
$title = 'Plugin';
if (method_exists($plugin, 'getConfig')) {
$config = $plugin->getConfig();
$title = $config['title'] ?? 'Plugin';
}
$sidebarContent .= '
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">' . htmlspecialchars($title) . '</h5>
</div>
<div class="card-body">
' . $content . '
</div>
</div>';
}
return $sidebarContent;