diff --git a/app/default/templates/base.php b/app/default/templates/base.php
index ce25bfd..ccf18d6 100644
--- a/app/default/templates/base.php
+++ b/app/default/templates/base.php
@@ -14,9 +14,6 @@
-
-
-
diff --git a/app/helpers.php b/app/helpers.php
index 489202e..a84c786 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -32,73 +32,11 @@ function extractTitle(string $filePath): ?string {
return null;
}
-// Build sorted list items for a directory
-function buildListItems(string $dir, Context $ctx, ?array $parentMetadata): array {
- $subdirs = getSubdirectories($dir);
-
- $items = array_filter(array_map(function($item) use ($dir, $ctx) {
- $itemPath = "$dir/$item";
- $metadata = loadMetadata($itemPath);
- $coverImage = findCoverImage($itemPath);
- $pdfFile = findPdfFile($itemPath);
-
- $title = $metadata['title'] ?? extractTitle($itemPath) ?? $item;
-
- $rawDate = null;
- $date = null;
- if (isset($metadata['date'])) {
- $rawDate = $metadata['date'];
- $date = Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format');
- } else {
- $rawDate = extractRawDateFromFolder($item);
- if ($rawDate) {
- $date = Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format');
- } else {
- $rawDate = date("Y-m-d", filemtime($itemPath));
- $date = Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format');
- }
- }
-
- $urlSlug = ($metadata && isset($metadata['slug'])) ? $metadata['slug'] : $item;
-
- $langPrefix = $ctx->get('langPrefix', '');
- $baseUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($urlSlug);
- $assetUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($item);
-
- return [
- 'title' => $title,
- 'url' => $baseUrl . '/',
- 'date' => $date,
- 'rawDate' => $rawDate,
- 'summary' => $metadata['summary'] ?? null,
- 'cover' => $coverImage ? "$assetUrl/$coverImage" : null,
- 'pdf' => $pdfFile ? "$assetUrl/$pdfFile" : null,
- 'redirect' => $metadata['redirect'] ?? null,
- 'dirPath' => $itemPath
- ];
- }, $subdirs));
-
- $sortOrder = strtolower($parentMetadata['order'] ?? 'descending');
- if ($sortOrder === 'ascending') {
- usort($items, fn($a, $b) => strcmp($a['date'] ?? '', $b['date'] ?? ''));
- } else {
- usort($items, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? ''));
- }
-
- return $items;
-}
-
-function extractRawDateFromFolder(string $folderName): ?string {
- if (preg_match('/^(\d{4})-(\d{2})-(\d{2})-/', $folderName, $matches)) {
- return $matches[1] . '-' . $matches[2] . '-' . $matches[3];
- }
- return null;
-}
-
function extractDateFromFolder(string $folderName): ?string {
- $raw = extractRawDateFromFolder($folderName);
- if ($raw) {
- return Hooks::apply(Hook::PROCESS_CONTENT, $raw, 'date_format');
+ if (preg_match('/^(\d{4})-(\d{2})-(\d{2})-/', $folderName, $matches)) {
+ $dateString = $matches[1] . '-' . $matches[2] . '-' . $matches[3];
+ // Let plugins format the date
+ return Hooks::apply(Hook::PROCESS_CONTENT, $dateString, 'date_format');
}
return null;
}
diff --git a/app/rendering.php b/app/rendering.php
index ef4e57a..7d61f89 100644
--- a/app/rendering.php
+++ b/app/rendering.php
@@ -48,16 +48,13 @@ function renderTemplate(Context $ctx, string $content, int $statusCode = 200): v
$navigation = $ctx->navigation;
$homeLabel = $ctx->homeLabel;
+ $pageTitle = null;
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [
'content' => $content,
'navigation' => $navigation,
'homeLabel' => $homeLabel,
- 'pageTitle' => $ctx->get('pageTitle'),
- 'metaDescription' => $ctx->get('metaDescription'),
- 'pageCssUrl' => $ctx->get('pageCssUrl'),
- 'pageCssHash' => $ctx->get('pageCssHash'),
- 'feedUrl' => $ctx->get('feedUrl')
+ 'pageTitle' => $pageTitle
], $ctx);
extract($templateVars);
diff --git a/app/router.php b/app/router.php
index d9710e0..64e706a 100644
--- a/app/router.php
+++ b/app/router.php
@@ -52,82 +52,6 @@ if (file_exists($contentAssetPath) && is_file($contentAssetPath)) {
}
}
-// Handle Atom feed requests
-if (str_ends_with($ctx->requestPath, 'feed.xml')) {
- $feedPath = preg_replace('#/?feed\.xml$#', '', $ctx->requestPath);
-
- // Temporarily set requestPath to the parent directory for resolution
- $reflection = new ReflectionProperty($ctx, 'requestPath');
- $originalPath = $ctx->requestPath;
- $reflection->setValue($ctx, $feedPath);
-
- $parsedFeed = parseRequestPath($ctx);
-
- if ($parsedFeed['type'] !== 'list') {
- $reflection->setValue($ctx, $originalPath);
- } else {
- $dir = $parsedFeed['path'];
- $metadata = loadMetadata($dir);
-
- if (!isset($metadata['feed']) || !$metadata['feed']) {
- $reflection->setValue($ctx, $originalPath);
- } else {
- $items = buildListItems($dir, $ctx, $metadata);
-
- // Render full content for each item
- foreach ($items as &$item) {
- $item['content'] = '';
- $contentFiles = findAllContentFiles($item['dirPath']);
- foreach ($contentFiles as $file) {
- $item['content'] .= renderContentFile($file, $ctx);
- }
- }
- unset($item);
-
- // Build Atom XML
- $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
- $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
- $baseUrl = "$scheme://$host";
- $langPrefix = $ctx->get('langPrefix', '');
- $listUrl = $baseUrl . $langPrefix . '/' . trim($feedPath, '/') . '/';
- $feedUrl = $baseUrl . $langPrefix . '/' . trim($feedPath, '/') . '/feed.xml';
- $feedTitle = $metadata['title'] ?? 'Feed';
- $updated = !empty($items) ? ($items[0]['rawDate'] ?? date('Y-m-d')) : date('Y-m-d');
-
- header('Content-Type: application/atom+xml; charset=utf-8');
- echo '' . "\n";
- echo '' . "\n";
- echo ' ' . htmlspecialchars($feedTitle) . '' . "\n";
- echo ' ' . "\n";
- echo ' ' . "\n";
- echo ' ' . htmlspecialchars($listUrl) . '' . "\n";
- echo ' ' . $updated . 'T00:00:00Z' . "\n";
-
- foreach ($items as $item) {
- $absoluteUrl = $baseUrl . $item['url'];
- $itemDate = ($item['rawDate'] ?? date('Y-m-d')) . 'T00:00:00Z';
-
- echo ' ' . "\n";
- echo ' ' . htmlspecialchars($item['title']) . '' . "\n";
- echo ' ' . "\n";
- echo ' ' . htmlspecialchars($absoluteUrl) . '' . "\n";
- echo ' ' . $itemDate . '' . "\n";
- if ($item['summary']) {
- echo ' ' . htmlspecialchars($item['summary']) . '' . "\n";
- }
- if ($item['content']) {
- $safeContent = str_replace(']]>', ']]]]>', $item['content']);
- echo ' ' . "\n";
- }
- echo ' ' . "\n";
- }
-
- echo '' . "\n";
- exit;
- }
- }
-}
-
// Handle frontpage
if (empty($ctx->requestPath)) {
$contentFiles = findAllContentFiles($ctx->contentDir);
@@ -197,7 +121,59 @@ switch ($parsedPath['type']) {
}
// Build list items
- $items = buildListItems($dir, $ctx, $metadata);
+ $subdirs = getSubdirectories($dir);
+
+ $items = array_filter(array_map(function($item) use ($dir, $ctx) {
+ $itemPath = "$dir/$item";
+ $metadata = loadMetadata($itemPath);
+ $coverImage = findCoverImage($itemPath);
+ $pdfFile = findPdfFile($itemPath);
+
+ $title = $metadata['title'] ?? extractTitle($itemPath) ?? $item;
+ $date = null;
+ if (isset($metadata['date'])) {
+ $date = $metadata['date'];
+ // Let plugins format date
+ $date = Hooks::apply(Hook::PROCESS_CONTENT, $date, 'date_format');
+ } else {
+ $extractedDate = extractDateFromFolder($item);
+ if ($extractedDate) {
+ $date = $extractedDate;
+ } else {
+ // Convert timestamp to ISO format and let plugins format it
+ $isoDate = date("Y-m-d", filemtime($itemPath));
+ $date = Hooks::apply(Hook::PROCESS_CONTENT, $isoDate, 'date_format');
+ }
+ }
+
+ // Use slug if available
+ $urlSlug = ($metadata && isset($metadata['slug'])) ? $metadata['slug'] : $item;
+
+ $langPrefix = $ctx->get('langPrefix', '');
+ $baseUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($urlSlug);
+
+ // Assets (cover, PDF) must use actual folder name, not translated slug
+ $assetUrl = $langPrefix . '/' . trim($ctx->requestPath, '/') . '/' . urlencode($item);
+
+ return [
+ 'title' => $title,
+ 'url' => $baseUrl . '/',
+ 'date' => $date,
+ 'summary' => $metadata['summary'] ?? null,
+ 'cover' => $coverImage ? "$assetUrl/$coverImage" : null,
+ 'pdf' => $pdfFile ? "$assetUrl/$pdfFile" : null,
+ 'redirect' => $metadata['redirect'] ?? null
+ ];
+ }, $subdirs));
+
+ // Sort by date - check metadata for order preference
+ $sortOrder = strtolower($metadata['order'] ?? 'descending');
+ if ($sortOrder === 'ascending') {
+ usort($items, fn($a, $b) => strcmp($a['date'] ?? '', $b['date'] ?? ''));
+ } else {
+ // Default: descending (newest first)
+ usort($items, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? ''));
+ }
// Prepare all variables for base template
$navigation = $ctx->navigation;
@@ -210,19 +186,6 @@ switch ($parsedPath['type']) {
$pageCssUrl = $pageCss['url'] ?? null;
$pageCssHash = $pageCss['hash'] ?? null;
- // Build feed URL if feed is enabled
- $langPrefix = $ctx->get('langPrefix', '');
- $feedUrl = (isset($metadata['feed']) && $metadata['feed'])
- ? $langPrefix . '/' . trim($ctx->requestPath, '/') . '/feed.xml'
- : null;
-
- // Store for base template (renderTemplate reads these from context)
- $ctx->set('pageTitle', $pageTitle);
- $ctx->set('metaDescription', $metaDescription);
- $ctx->set('pageCssUrl', $pageCssUrl);
- $ctx->set('pageCssHash', $pageCssHash);
- $ctx->set('feedUrl', $feedUrl);
-
// Let plugins add template variables
$templateVars = Hooks::apply(Hook::TEMPLATE_VARS, [
'navigation' => $navigation,
@@ -232,8 +195,7 @@ switch ($parsedPath['type']) {
'pageCssUrl' => $pageCssUrl,
'pageCssHash' => $pageCssHash,
'items' => $items,
- 'pageContent' => $pageContent,
- 'feedUrl' => $feedUrl
+ 'pageContent' => $pageContent
], $ctx);
extract($templateVars);
diff --git a/docs/03-reference/02-metadata.md b/docs/03-reference/02-metadata.md
index 4dd742c..7f8d806 100644
--- a/docs/03-reference/02-metadata.md
+++ b/docs/03-reference/02-metadata.md
@@ -194,20 +194,6 @@ hide_list = true
**Type:** Boolean
**Use case:** Section landing pages that should show content instead of list
-### `feed`
-
-Enable an Atom feed for this list page, served at `/{list-path}/feed.xml`.
-
-```ini
-feed = true
-```
-
-**Default:** `false` (no feed generated)
-**Values:** `true` or `false`
-**Type:** Boolean
-**Applies to:** List pages only (directories with subdirectories)
-**Effect:** Generates an Atom XML feed containing the full rendered content of each list item. Also adds an autodiscovery `` tag in the HTML ``.
-
## Language-Specific Overrides
Add language-specific sections to override fields:
@@ -341,19 +327,20 @@ Folder: content/blog/2024-12-15-my-post/
## Metadata in List Items
-When rendering list views, each item in the `$items` array has these keys:
+When rendering list views, each item receives these metadata fields:
```php
$item = [
- 'title' => 'My Post', // From metadata, heading, or folder name
- 'url' => '/blog/my-post/', // Full URL with trailing slash + lang prefix
- 'date' => '15. desember 2024', // Formatted for display (plugin-processed)
- 'rawDate' => '2024-12-15', // ISO YYYY-MM-DD (for feeds,