Add news preview plugin with three display sizes
The plugin displays latest news items from /nyheter/ section with three size options: - small: horizontal scrolling cards - medium: responsive grid - large: responsive grid with summaries Usage: Add to metadata.ini and call with <?= news_preview() ?> or <?= news_preview(5, 'large') ?>
This commit is contained in:
parent
7205a02b75
commit
1766b370dd
1 changed files with 322 additions and 0 deletions
322
custom/plugins/page/news-preview.php
Normal file
322
custom/plugins/page/news-preview.php
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
<?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;
|
||||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue