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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<header class="navbar navbar-expand-lg navbar-dark" style="background-color: var(--header-bg);">
|
||||
<header id="site-header" class="navbar navbar-expand-lg navbar-dark" style="background-color: transparent;">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="?page={{default_page}}&lang={{current_lang}}">
|
||||
<img src="/assets/icon.svg" alt="CodePress Logo" width="32" height="32" class="me-2">
|
||||
|
||||
@@ -76,11 +76,45 @@
|
||||
|
||||
<!-- Dynamic theme colors -->
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#site-header {
|
||||
background-image: {{background_image_css}};
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#site-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: var(--header-bg);
|
||||
opacity: calc((100 - {{background_image_opacity}}) / 100);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#site-header > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
:root {
|
||||
--header-bg: {{header_color}};
|
||||
--header-font: {{header_font_color}};
|
||||
--header-height: {{header_height}}px;
|
||||
--nav-bg: {{navigation_color}};
|
||||
--nav-font: {{navigation_font_color}};
|
||||
--nav-height: {{nav_height}}px;
|
||||
--sidebar-bg: {{sidebar_background}};
|
||||
--sidebar-border: {{sidebar_border}};
|
||||
}
|
||||
@@ -88,6 +122,7 @@
|
||||
/* Header styles */
|
||||
.navbar {
|
||||
background-color: var(--header-bg) !important;
|
||||
min-height: var(--header-height);
|
||||
}
|
||||
|
||||
.navbar .navbar-brand,
|
||||
@@ -209,6 +244,7 @@
|
||||
.navigation-section {
|
||||
background-color: var(--nav-bg) !important;
|
||||
color: var(--nav-font) !important;
|
||||
min-height: var(--nav-height);
|
||||
}
|
||||
|
||||
/* Enhanced accessibility styles */
|
||||
@@ -290,7 +326,7 @@
|
||||
border-right: 1px solid var(--sidebar-border) !important;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
min-height: calc(100vh - var(--header-height) - var(--nav-height) - 42px);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@@ -310,7 +346,7 @@
|
||||
|
||||
/* Ensure full height layout */
|
||||
.main-content {
|
||||
min-height: calc(100vh - 200px);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@@ -380,9 +416,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header role="banner" id="site-header">
|
||||
{{>header}}
|
||||
</header>
|
||||
{{>header}}
|
||||
|
||||
<nav role="navigation" aria-label="Main navigation" id="site-navigation">
|
||||
{{>navigation}}
|
||||
|
||||
Reference in New Issue
Block a user