322 lines
9.1 KiB
PHP
322 lines
9.1 KiB
PHP
<?php
|
|
/**
|
|
* News Preview Plugin
|
|
*
|
|
* Displays a preview of the latest news items from the /nyheter/ section.
|
|
* Can be included on any page as a reusable component.
|
|
*
|
|
* Usage: Add to metadata.ini:
|
|
* plugins = "news-preview"
|
|
*
|
|
* Then in your PHP content file:
|
|
* <?= news_preview() ?>
|
|
* <?= news_preview(3, 'medium') ?>
|
|
* <?= news_preview(5, 'large') ?>
|
|
*
|
|
* Available sizes:
|
|
* - 'small': Horizontal scrolling row of compact cards (cover, title, date)
|
|
* - 'medium': Responsive wrapping grid (cover, title, date)
|
|
* - 'large': Responsive wrapping grid with summary (cover, title, date, summary)
|
|
*/
|
|
|
|
function newsPreviewT(string $key): string {
|
|
$ctx = $GLOBALS['ctx'] ?? null;
|
|
$translations = $ctx?->get('translations', []) ?? [];
|
|
$fallbacks = [
|
|
'heading' => 'Siste nytt',
|
|
'read_more' => 'Les mer',
|
|
'see_all' => 'Se alle nyheter',
|
|
];
|
|
return $translations["news_preview.$key"] ?? $fallbacks[$key] ?? $key;
|
|
}
|
|
|
|
function newsPreviewBuildItems(int $count): array {
|
|
$ctx = $GLOBALS['ctx'] ?? null;
|
|
if (!$ctx) return [];
|
|
|
|
$newsDir = $ctx->contentDir . '/nyheter';
|
|
if (!is_dir($newsDir)) return [];
|
|
|
|
$langPrefix = $ctx->get('langPrefix', '');
|
|
$subdirs = getSubdirectories($newsDir);
|
|
|
|
$items = [];
|
|
foreach ($subdirs as $dir) {
|
|
$itemPath = "$newsDir/$dir";
|
|
$metadata = loadMetadata($itemPath);
|
|
$title = $metadata['title'] ?? extractTitle($itemPath) ?? $dir;
|
|
|
|
$rawDate = $metadata['date'] ?? extractRawDateFromFolder($dir);
|
|
$date = $rawDate ? Hooks::apply(Hook::PROCESS_CONTENT, $rawDate, 'date_format') : null;
|
|
|
|
$cover = findCoverImage($itemPath);
|
|
$slug = $metadata['slug'] ?? $dir;
|
|
|
|
$items[] = [
|
|
'title' => $title,
|
|
'url' => $langPrefix . '/nyheter/' . urlencode($slug) . '/',
|
|
'date' => $date,
|
|
'rawDate' => $rawDate,
|
|
'summary' => $metadata['summary'] ?? extractMetaDescription($itemPath, $metadata),
|
|
'cover' => $cover ? $langPrefix . '/nyheter/' . urlencode($dir) . '/' . $cover : null,
|
|
];
|
|
}
|
|
|
|
usort($items, fn($a, $b) => strcmp($b['rawDate'] ?? '', $a['rawDate'] ?? ''));
|
|
return array_slice($items, 0, $count);
|
|
}
|
|
|
|
function newsPreviewRenderSmall(array $items, string $langPrefix): string {
|
|
$heading = htmlspecialchars(newsPreviewT('heading'));
|
|
$seeAll = htmlspecialchars(newsPreviewT('see_all'));
|
|
$seeAllUrl = htmlspecialchars($langPrefix . '/nyheter/');
|
|
|
|
$cards = '';
|
|
foreach ($items as $item) {
|
|
$title = htmlspecialchars($item['title']);
|
|
$url = htmlspecialchars($item['url']);
|
|
$date = $item['date'] ? '<p class="news-preview-date">' . htmlspecialchars($item['date']) . '</p>' : '';
|
|
$cover = $item['cover']
|
|
? '<a href="' . $url . '"><img src="' . htmlspecialchars($item['cover']) . '" alt="' . $title . '"></a>'
|
|
: '';
|
|
|
|
$cards .= <<<HTML
|
|
<article class="news-preview-card">
|
|
{$cover}
|
|
<div class="news-preview-card-body">
|
|
<h3><a href="{$url}">{$title}</a></h3>
|
|
{$date}
|
|
</div>
|
|
</article>
|
|
HTML;
|
|
}
|
|
|
|
return <<<HTML
|
|
<section class="news-preview news-preview-small">
|
|
<div class="news-preview-header">
|
|
<h1>{$heading}</h1>
|
|
<a href="{$seeAllUrl}" class="button">{$seeAll}</a>
|
|
</div>
|
|
<div class="news-preview-scroll">
|
|
{$cards}
|
|
</div>
|
|
</section>
|
|
HTML;
|
|
}
|
|
|
|
function newsPreviewRenderMedium(array $items, string $langPrefix): string {
|
|
$heading = htmlspecialchars(newsPreviewT('heading'));
|
|
$seeAll = htmlspecialchars(newsPreviewT('see_all'));
|
|
$seeAllUrl = htmlspecialchars($langPrefix . '/nyheter/');
|
|
|
|
$cards = '';
|
|
foreach ($items as $item) {
|
|
$title = htmlspecialchars($item['title']);
|
|
$url = htmlspecialchars($item['url']);
|
|
$date = $item['date'] ? '<p class="news-preview-date">' . htmlspecialchars($item['date']) . '</p>' : '';
|
|
$cover = $item['cover']
|
|
? '<a href="' . $url . '"><img src="' . htmlspecialchars($item['cover']) . '" alt="' . $title . '"></a>'
|
|
: '';
|
|
|
|
$cards .= <<<HTML
|
|
<article class="news-preview-card">
|
|
{$cover}
|
|
<h3><a href="{$url}">{$title}</a></h3>
|
|
{$date}
|
|
</article>
|
|
HTML;
|
|
}
|
|
|
|
return <<<HTML
|
|
<section class="news-preview news-preview-medium">
|
|
<div class="news-preview-header">
|
|
<h1>{$heading}</h1>
|
|
<a href="{$seeAllUrl}" class="button">{$seeAll}</a>
|
|
</div>
|
|
<div class="news-preview-grid">
|
|
{$cards}
|
|
</div>
|
|
</section>
|
|
HTML;
|
|
}
|
|
|
|
function newsPreviewRenderLarge(array $items, string $langPrefix): string {
|
|
$heading = htmlspecialchars(newsPreviewT('heading'));
|
|
$readMore = htmlspecialchars(newsPreviewT('read_more'));
|
|
$seeAll = htmlspecialchars(newsPreviewT('see_all'));
|
|
$seeAllUrl = htmlspecialchars($langPrefix . '/nyheter/');
|
|
|
|
$cards = '';
|
|
foreach ($items as $item) {
|
|
$title = htmlspecialchars($item['title']);
|
|
$url = htmlspecialchars($item['url']);
|
|
$date = $item['date'] ? '<p class="news-preview-date">' . htmlspecialchars($item['date']) . '</p>' : '';
|
|
$cover = $item['cover']
|
|
? '<a href="' . $url . '"><img src="' . htmlspecialchars($item['cover']) . '" alt="' . $title . '"></a>'
|
|
: '';
|
|
$summary = $item['summary']
|
|
? '<p class="news-preview-summary">' . htmlspecialchars($item['summary']) . '</p>'
|
|
: '';
|
|
|
|
$cards .= <<<HTML
|
|
<article class="news-preview-card">
|
|
{$cover}
|
|
<h3><a href="{$url}">{$title}</a></h3>
|
|
{$date}
|
|
{$summary}
|
|
<a href="{$url}" class="button">{$readMore}</a>
|
|
</article>
|
|
HTML;
|
|
}
|
|
|
|
return <<<HTML
|
|
<section class="news-preview news-preview-large">
|
|
<div class="news-preview-header">
|
|
<h1>{$heading}</h1>
|
|
<a href="{$seeAllUrl}" class="button">{$seeAll}</a>
|
|
</div>
|
|
<div class="news-preview-grid">
|
|
{$cards}
|
|
</div>
|
|
</section>
|
|
HTML;
|
|
}
|
|
|
|
function newsPreviewGetStyles(): string {
|
|
static $included = false;
|
|
if ($included) return '';
|
|
$included = true;
|
|
|
|
return <<<'STYLES'
|
|
<style>
|
|
/* News Preview Plugin */
|
|
.news-preview {
|
|
margin-top: 2rem;
|
|
|
|
.news-preview-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: 0 1rem;
|
|
|
|
h1 { margin-top: .5em }
|
|
.button { margin-top: 1.3em }
|
|
}
|
|
|
|
.news-preview-grid,
|
|
.news-preview-scroll {
|
|
margin-top: 1.5rem;
|
|
}
|
|
}
|
|
|
|
.news-preview-card {
|
|
background: white;
|
|
overflow: hidden;
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 12rem;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
h3 {
|
|
margin-top: .6rem;
|
|
padding: 0 .8rem;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.news-preview-date {
|
|
padding: 0 .8rem .5rem;
|
|
font-size: .85rem;
|
|
opacity: .7;
|
|
}
|
|
|
|
.news-preview-summary {
|
|
padding: 0 .8rem;
|
|
font-size: .95rem;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.button {
|
|
margin: .8rem .8rem 1rem;
|
|
}
|
|
}
|
|
|
|
/* Small: horizontal scroll */
|
|
.news-preview-small {
|
|
.news-preview-scroll {
|
|
display: flex;
|
|
gap: 1rem;
|
|
overflow-x: auto;
|
|
scroll-snap-type: x mandatory;
|
|
-webkit-overflow-scrolling: touch;
|
|
padding-bottom: .5rem;
|
|
}
|
|
|
|
.news-preview-card {
|
|
flex: 0 0 16rem;
|
|
scroll-snap-align: start;
|
|
|
|
img { height: 9rem }
|
|
h3 { font-size: 1rem }
|
|
}
|
|
}
|
|
|
|
/* Medium: wrapping grid, no summary */
|
|
.news-preview-medium {
|
|
.news-preview-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1.2rem;
|
|
}
|
|
|
|
.news-preview-card {
|
|
flex: 1 1 clamp(14rem, (30rem - 100%) * 999, 100%);
|
|
padding-bottom: .8rem;
|
|
}
|
|
}
|
|
|
|
/* Large: wrapping grid with summary */
|
|
.news-preview-large {
|
|
.news-preview-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1.2rem;
|
|
}
|
|
|
|
.news-preview-card {
|
|
flex: 1 1 clamp(14rem, (30rem - 100%) * 999, 100%);
|
|
padding-bottom: .2rem;
|
|
}
|
|
}
|
|
</style>
|
|
STYLES;
|
|
}
|
|
|
|
/**
|
|
* Render a news preview section.
|
|
*
|
|
* @param int $count Number of news items to show (default: 3)
|
|
* @param string $size Display size: 'small', 'medium', or 'large' (default: 'medium')
|
|
* @return string The complete HTML for the news preview section
|
|
*/
|
|
function news_preview(int $count = 3, string $size = 'medium'): string {
|
|
$ctx = $GLOBALS['ctx'] ?? null;
|
|
if (!$ctx) return '';
|
|
|
|
$items = newsPreviewBuildItems($count);
|
|
if (empty($items)) return '';
|
|
|
|
$langPrefix = $ctx->get('langPrefix', '');
|
|
|
|
$html = newsPreviewGetStyles();
|
|
$html .= match ($size) {
|
|
'small' => newsPreviewRenderSmall($items, $langPrefix),
|
|
'large' => newsPreviewRenderLarge($items, $langPrefix),
|
|
default => newsPreviewRenderMedium($items, $langPrefix),
|
|
};
|
|
|
|
return $html;
|
|
}
|