Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display thread tree as a tree #33

Merged
merged 6 commits into from
Apr 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions article.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,21 +244,7 @@
<h2>
Thread (<?= sprintf("%d message%s", $count = $threads->count(), $count > 1 ? 's' : '') ?>)
</h2>
<div class="responsive-table">
<table class="standard">
<thead>
<tr>
<th>#</th>
<th>Subject</th>
<th>Author</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php $threads->printRows($group, 'utf8'); ?>
</tbody>
</table>
</div>
<?php $threads->printFullThread($group, $article, charset: 'utf8'); ?>
</blockquote>
<?php
} catch (\Throwable $t) {
Expand Down
97 changes: 97 additions & 0 deletions lib/ThreadTree.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,101 @@ public function printRows($group, $charset = 'utf8')
$this->printArticleAndChildren($root, $group, $charset, 1);
}
}

public function printFullThread(
$group,
$includingArticleNumber,
$charset = null
) {
echo "<div class=\"list-tree\"><ul>";
$this->printThread(
group: $group,
messageId: $this->root,
activeArticleNumber: $includingArticleNumber,
charset: $charset,
);

foreach ($this->extraRootChildren as $childMessageId) {
$this->printThread(
group: $group,
activeArticleNumber: $includingArticleNumber,
messageId: $childMessageId,
charset: $charset,
);
}

echo "</ul></div>";
}

public function printThread(
$group,
$messageId = null,
$activeArticleNumber = null,
$depth = 0,
$subject = "",
$charset = 'utf8'
) {
if ($depth > 40) {
echo "<li>Too deep!</li>";
return;
}

if (array_key_exists($messageId, $this->articleNumbers)) {
$articleNumber = $this->articleNumbers[$messageId];

# for debugging that we've actually handled all articles
#unset($this->articleNumbers[$messageId]);

$details = $this->articles[$articleNumber];

echo '<li>';

$details = $this->articles[$articleNumber];

if ($articleNumber != $activeArticleNumber) {
echo "<a href=\"/$group/$articleNumber\">";
} else {
echo "<b>";
}
echo
'<span class="author">',
format_author($details['author'], $charset, nameOnly: true),
'</span>',
'<span class="date">',
'<time datetime="', format_date($details['date'], 'c'), '">',
format_date($details['date']),
'</time>',
'</span>';

$newSubject = format_subject($details['subject'], $charset, trimRe: true);
if ($messageId != $this->root && $newSubject != $subject) {
echo '<span class="subject">';
echo format_subject($details['subject'], $charset);
echo '</span>';
}

if ($articleNumber != $activeArticleNumber) {
echo "</a>";
} else {
echo "</b>";
}

if (array_key_exists($messageId, $this->tree)) {
echo '<ul>';
foreach ($this->tree[$messageId] as $childMessageId) {
$this->printThread(
group: $group,
activeArticleNumber: $activeArticleNumber,
messageId: $childMessageId,
subject: $newSubject,
charset: $charset,
depth: $depth + 1,
);
}
echo '</ul>';
}

echo "</li>";
}
}
}
43 changes: 23 additions & 20 deletions lib/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,36 +238,39 @@ function spam_protect($txt)


# this turns some common forms of email addresses into mailto: links
function format_author($a, $charset = 'iso-8859-1')
function format_author($a, $charset = 'iso-8859-1', $nameOnly = false)
{
$a = recode_header($a, $charset);
if (preg_match("/^\s*(.+)\s+\\(\"?(.+?)\"?\\)\s*$/", $a, $ar)) {
return "<a href=\"mailto:" .
htmlspecialchars(urlencode(spam_protect($ar[1])), ENT_QUOTES, "UTF-8") .
"\" class=\"email fn n\">" .
str_replace(" ", "&nbsp;", htmlspecialchars($ar[2], ENT_QUOTES, "UTF-8")) . "</a>";
$email= spam_protect($ar[1]);
$name = $ar[2];
}
if (preg_match("/^\s*\"?(.+?)\"?\s*<(.+)>\s*$/", $a, $ar)) {
elseif (preg_match("/^\s*\"?(.+?)\"?\s*<(.+)>\s*$/", $a, $ar)) {
$email = spam_protect($ar[2]);
$name = $ar[1];
}
elseif (strpos("@", $a) !== false) {
$email = $name = spam_protect($a);
} else {
$email = $name = $a;
}
if ($nameOnly) {
return str_replace(" ", "&nbsp;", htmlspecialchars($name, ENT_QUOTES, "UTF-8"));
} else {
return "<a href=\"mailto:" .
htmlspecialchars(urlencode(spam_protect($ar[2])), ENT_QUOTES, "UTF-8") .
htmlspecialchars(urlencode($email), ENT_QUOTES, "UTF-8") .
"\" class=\"email fn n\">" .
str_replace(" ", "&nbsp;", htmlspecialchars($ar[1], ENT_QUOTES, "UTF-8")) . "</a>";
}
if (strpos("@", $a) !== false) {
$a = spam_protect($a);
return "<a href=\"mailto:" . htmlspecialchars(urlencode($a), ENT_QUOTES, "UTF-8") .
"\" class=\"email fn n\">" . htmlspecialchars($a, ENT_QUOTES, "UTF-8") . "</a>";
str_replace(" ", "&nbsp;", $name) . "</a>";
}
return str_replace(" ", "&nbsp;", htmlspecialchars($a, ENT_QUOTES, "UTF-8"));
}

function format_subject($s, $charset = 'iso-8859-1')
function format_subject($s, $charset = 'iso-8859-1', $trimRe = false)
{
global $article;
$s = recode_header($s, $charset);

/* Trim most of the prefixes we add for lists */
$s = preg_replace('/^(Re:\s*)?(\s*\[(DOC|PEAR|PECL|PHP|ANNOUNCE|GIT-PULLS|STANDARDS|php-standards)(-.+?)?]\s*)+/', '\1', $s);
$s = preg_replace('/^(Re:\s*)?(\s*\[(DOC|PEAR|PECL|PHP|ANNOUNCE|GIT-PULLS|STANDARDS|php-standards)(-.+?)?]\s*(Re:\s*)?)+/', $trimRe ? '' : '\1\5', $s);

// make this look better on the preview page..
if (strlen($s) > 150 && !isset($article)) {
Expand All @@ -279,11 +282,11 @@ function format_subject($s, $charset = 'iso-8859-1')
}


function format_title($s, $charset = 'iso-8859-1')
function format_title($s, $charset = 'iso-8859-1', $trimRe = false)
{
global $article;
$s = recode_header($s, $charset);
$s = preg_replace("/^(Re: *)?\[(PHP|PEAR)(-.*?)?\] /i", "\\1", $s);
$s = preg_replace("/^(Re:\s*)?\[(PHP|PEAR)(-.*?)?\]\s/i", $trimRe ? "" : "\\1", $s);
// make this look better on the preview page..
if (strlen($s) > 150 && !isset($article)) {
$s = substr($s, 0, 150) . "...";
Expand All @@ -293,10 +296,10 @@ function format_title($s, $charset = 'iso-8859-1')
return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
}

function format_date($d)
function format_date($d, $format = 'r')
{
$d = strtotime($d);
$d = gmdate('r', $d);
$d = gmdate($format, $d);
return str_replace(" ", "&nbsp;", $d);
}

Expand Down
75 changes: 75 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,81 @@ form.subscription-form {
gap: 1em;
}

/* Thread tree, based on: https://www.cssscript.com/tree-view-unlimited-nesting/ */
.list-tree {
--tree-clr: #075985;
--tree-font-size: 1rem;
--tree-item-height: 1.5;
--tree-offset: 0.5rem;
--tree-indent: 0.5rem;
--tree-thickness: 1px;
--tree-style: solid;
}
.list-tree ul{
display: grid;
list-style: none;
font-size: var(--tree-font-size);
padding-inline-start: var(--tree-indent);
max-width: 50em;
}
.list-tree li{
line-height: var(--tree-item-height);
padding-inline-start: var(--tree-offset);
border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
position: relative;
text-indent: .5rem;

&:last-child {
border-color: transparent; /* hide (not remove!) border on last li element*/
}

& a, & b {
display: grid;
grid-template-columns: 1fr auto;
align-item: start;
& span.author {
grid-column: 1 / 1;
white-space: normal;
}
& span.date {
grid-column: 2 / 2;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
& span.subject {
grid-column: 1 / 2;
white-space: normal;
}
}
&::before{
content: '';
position: absolute;
top: calc(var(--tree-font-size) / 2 + var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness));
left: calc(var(--tree-thickness) * -1);
width: calc(var(--tree-offset) + var(--tree-thickness) * 2);
height: calc(var(--tree-item-height) * var(--tree-font-size) - var(--tree-font-size) / 2);
border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr);
}
&::after{
content: '';
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--tree-clr);
top: calc(var(--tree-item-height) / 2 * 1rem);
left: var(--tree-offset) ;
translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1);
}
& li li{
/*
change line color etc.
--tree-clr: rgb(175, 208, 84);
*/
}
}

@media screen and (max-width: 760px) {
.welcome {
display: none;
Expand Down