Add tree-home block; merge birthday/anniversary timeline
- New "Upcoming family events" block for the tree home page, rendering the same card + timeline visualisation as the newsletter email but adapted for web context: avatars resolve to media-file URLs (no CID), the silhouette placeholder reuses BockenTheme's .person-card .photo-placeholder rules so the Nord-mixed shades and dark-mode handling stay in sync with the full-diagram plugin, and per-viewer relationship labels surface when the signed-in user is linked to an Individual on the tree. - Default window 30 days, configurable via the standard block config UI. Wide-screen wrapper caps at 760 px with a small right-side breathing margin. - Block renders via AJAX and caches its HTML for 5 minutes per (tree, window, viewer, locale), so the tree home page paints instantly and repeat visits skip the heavy event/query + relationship-BFS work. - Living-kin section is now a single date-sorted timeline that mixes birthdays and intact-couple anniversaries. Each row's icon + label key off the fact's tag, so a mixed run shares one rail. Applies to both block and email. - Newsletter subscription menu entry removed from the header; the form is still reachable on the standard /my-account page via the registerCustomView override.
This commit is contained in:
+35
-60
@@ -689,6 +689,16 @@ $timeline_arrow_row = '<tr>'
|
||||
// by the factory's HtmlFilter::ESCAPE setting, so
|
||||
// a stray "<" can't break the email layout.
|
||||
$intro_html = Registry::markdownFactory()->markdown(trim($intro), $tree);
|
||||
// Force every Markdown-rendered <img> to fit
|
||||
// inside the intro container — many email
|
||||
// clients honour neither <style> blocks nor
|
||||
// CSS class hooks reliably, so inline width
|
||||
// constraints are the only portable fix.
|
||||
$intro_html = preg_replace(
|
||||
'/<img\b/i',
|
||||
'<img style="max-width:100%;height:auto;display:block;border-radius:6px;margin:8px 0;"',
|
||||
$intro_html,
|
||||
) ?? $intro_html;
|
||||
$intro_inner = '<div style="border-left:3px solid ' . $palette['accent'] . ';padding:6px 0 6px 16px;'
|
||||
. 'font-size:15px;line-height:1.55;font-weight:300;color:' . $palette['ink'] . ';'
|
||||
. 'font-style:italic;">' . $intro_html . '</div>';
|
||||
@@ -714,18 +724,29 @@ $timeline_arrow_row = '<tr>'
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (!$birthdays->isEmpty()) : ?>
|
||||
<?php
|
||||
// Merge living-kin events (birthdays + intact-couple
|
||||
// anniversaries) into a single date-sorted timeline.
|
||||
// The icon and label come from the fact's tag, so a
|
||||
// mixed row of BIRT and MARR facts shares one rail.
|
||||
$living = collect($birthdays);
|
||||
if ($include_anniversaries && $anniversaries !== null) {
|
||||
$living = $living->merge($anniversaries);
|
||||
}
|
||||
$living = $living->sortBy(static fn (Fact $f): int => $f->jd ?? 0)->values();
|
||||
?>
|
||||
<?php if (!$living->isEmpty()) : ?>
|
||||
<?php
|
||||
$detailed = [];
|
||||
$summary = [];
|
||||
foreach ($birthdays as $fact) {
|
||||
foreach ($living as $fact) {
|
||||
if ($is_detailed($fact)) { $detailed[] = $fact; } else { $summary[] = $fact; }
|
||||
}
|
||||
?>
|
||||
<tr><td style="padding:8px 0 0;">
|
||||
<h2 style="<?= $section_title_style ?>"><?= e(I18N::translate('Upcoming birthdays')) ?></h2>
|
||||
<h2 style="<?= $section_title_style ?>"><?= e(I18N::translate('Upcoming events')) ?></h2>
|
||||
<p style="<?= $section_kicker_style ?>">
|
||||
<?= e(I18N::translate('Living kin who will celebrate this fortnight.')) ?>
|
||||
<?= e(I18N::translate('Birthdays of living kin and anniversaries of intact couples in the next %d days.', $window_days)) ?>
|
||||
</p>
|
||||
<?php if ($detailed !== []) : ?>
|
||||
<?= $card_open ?>
|
||||
@@ -733,11 +754,13 @@ $timeline_arrow_row = '<tr>'
|
||||
<?php $prev_jd = null; ?>
|
||||
<?php foreach ($detailed as $fact) : ?>
|
||||
<?php
|
||||
$age = $upcoming_age($fact);
|
||||
$body = '<span style="display:inline-block;vertical-align:middle;margin-right:12px;">' . $event_icon('BIRT') . '</span>'
|
||||
$kind = $event_kind($fact);
|
||||
$age = $upcoming_age($fact);
|
||||
$label = $kind === 'MARR' ? $anniversary_label($age) : $birthday_label($age);
|
||||
$body = '<span style="display:inline-block;vertical-align:middle;margin-right:12px;">' . $event_icon($kind) . '</span>'
|
||||
. '<span style="vertical-align:middle;">'
|
||||
. '<span style="font-weight:600;color:' . $palette['ink'] . ';">' . $record_label($fact) . '</span>'
|
||||
. '<div style="margin-top:2px;color:' . $palette['ink2'] . ';font-weight:300;">' . e($birthday_label($age)) . '</div>'
|
||||
. '<div style="margin-top:2px;color:' . $palette['ink2'] . ';font-weight:300;">' . e($label) . '</div>'
|
||||
. '</span>';
|
||||
$show_dot = ($fact->jd ?? 0) !== $prev_jd;
|
||||
$prev_jd = $fact->jd ?? 0;
|
||||
@@ -750,66 +773,18 @@ $timeline_arrow_row = '<tr>'
|
||||
<?php endif ?>
|
||||
<?php if ($summary !== []) : ?>
|
||||
<div style="<?= $summary_kicker_style ?>">
|
||||
<?= e(I18N::translate('Other birthdays')) ?>
|
||||
<?= e(I18N::translate('Other upcoming events')) ?>
|
||||
</div>
|
||||
<ul style="<?= $summary_list_style ?>">
|
||||
<?php foreach ($summary as $fact) : ?>
|
||||
<?php $age = $upcoming_age($fact); ?>
|
||||
<li style="<?= $summary_item_style ?>">
|
||||
<?= $record_label($fact) ?>
|
||||
<span style="color:<?= $palette['ink3'] ?>;"><?= e($birthday_label($age)) ?></span>
|
||||
<span style="color:<?= $palette['mute'] ?>;"> · <?= e($event_date_display($fact)) ?></span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
</td></tr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if ($include_anniversaries && $anniversaries !== null && !$anniversaries->isEmpty()) : ?>
|
||||
<?php
|
||||
$detailed = [];
|
||||
$summary = [];
|
||||
foreach ($anniversaries as $fact) {
|
||||
if ($is_detailed($fact)) { $detailed[] = $fact; } else { $summary[] = $fact; }
|
||||
}
|
||||
?>
|
||||
<tr><td style="padding:32px 0 0;">
|
||||
<h2 style="<?= $section_title_style ?>"><?= e(I18N::translate('Upcoming marriage anniversaries')) ?></h2>
|
||||
<p style="<?= $section_kicker_style ?>">
|
||||
<?= e(I18N::translate('Marriages still intact.')) ?>
|
||||
</p>
|
||||
<?php if ($detailed !== []) : ?>
|
||||
<?= $card_open ?>
|
||||
<?= $timeline_top_cap ?>
|
||||
<?php $prev_jd = null; ?>
|
||||
<?php foreach ($detailed as $fact) : ?>
|
||||
<?php
|
||||
$age = $upcoming_age($fact);
|
||||
$body = '<span style="display:inline-block;vertical-align:middle;margin-right:12px;">' . $event_icon('MARR') . '</span>'
|
||||
. '<span style="vertical-align:middle;">'
|
||||
. '<span style="font-weight:600;color:' . $palette['ink'] . ';">' . $record_label($fact) . '</span>'
|
||||
. '<div style="margin-top:2px;color:' . $palette['ink2'] . ';font-weight:300;">' . e($anniversary_label($age)) . '</div>'
|
||||
. '</span>';
|
||||
$show_dot = ($fact->jd ?? 0) !== $prev_jd;
|
||||
$prev_jd = $fact->jd ?? 0;
|
||||
echo $event_row($fact, $body, $show_dot);
|
||||
$kind = $event_kind($fact);
|
||||
$age = $upcoming_age($fact);
|
||||
$label = $kind === 'MARR' ? $anniversary_label($age) : $birthday_label($age);
|
||||
?>
|
||||
<?php endforeach ?>
|
||||
<?= $timeline_bottom_cap ?>
|
||||
<?= $timeline_arrow_row ?>
|
||||
<?= $card_close ?>
|
||||
<?php endif ?>
|
||||
<?php if ($summary !== []) : ?>
|
||||
<div style="<?= $summary_kicker_style ?>">
|
||||
<?= e(I18N::translate('Other anniversaries')) ?>
|
||||
</div>
|
||||
<ul style="<?= $summary_list_style ?>">
|
||||
<?php foreach ($summary as $fact) : ?>
|
||||
<?php $age = $upcoming_age($fact); ?>
|
||||
<li style="<?= $summary_item_style ?>">
|
||||
<?= $record_label($fact) ?>
|
||||
<span style="color:<?= $palette['ink3'] ?>;"><?= e($anniversary_label($age)) ?></span>
|
||||
<span style="color:<?= $palette['ink3'] ?>;"><?= e($label) ?></span>
|
||||
<span style="color:<?= $palette['mute'] ?>;"> · <?= e($event_date_display($fact)) ?></span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
|
||||
Reference in New Issue
Block a user