Skip to content

Commit b882172

Browse files
authored
Display thread tree as a tree (#33)
1 parent 0479a8f commit b882172

File tree

4 files changed

+196
-35
lines changed

4 files changed

+196
-35
lines changed

article.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -371,21 +371,7 @@
371371
<h2>
372372
Thread (<?= sprintf("%d message%s", $count = $threads->count(), $count > 1 ? 's' : '') ?>)
373373
</h2>
374-
<div class="responsive-table">
375-
<table class="standard">
376-
<thead>
377-
<tr>
378-
<th>#</th>
379-
<th>Subject</th>
380-
<th>Author</th>
381-
<th>Date</th>
382-
</tr>
383-
</thead>
384-
<tbody>
385-
<?php $threads->printRows($group, 'utf8'); ?>
386-
</tbody>
387-
</table>
388-
</div>
374+
<?php $threads->printFullThread($group, $article, charset: 'utf8'); ?>
389375
</blockquote>
390376
<?php
391377
} catch (\Throwable $t) {

lib/ThreadTree.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,101 @@ public function printRows($group, $charset = 'utf8')
9494
$this->printArticleAndChildren($root, $group, $charset, 1);
9595
}
9696
}
97+
98+
public function printFullThread(
99+
$group,
100+
$includingArticleNumber,
101+
$charset = null
102+
) {
103+
echo "<div class=\"list-tree\"><ul>";
104+
$this->printThread(
105+
group: $group,
106+
messageId: $this->root,
107+
activeArticleNumber: $includingArticleNumber,
108+
charset: $charset,
109+
);
110+
111+
foreach ($this->extraRootChildren as $childMessageId) {
112+
$this->printThread(
113+
group: $group,
114+
activeArticleNumber: $includingArticleNumber,
115+
messageId: $childMessageId,
116+
charset: $charset,
117+
);
118+
}
119+
120+
echo "</ul></div>";
121+
}
122+
123+
public function printThread(
124+
$group,
125+
$messageId = null,
126+
$activeArticleNumber = null,
127+
$depth = 0,
128+
$subject = "",
129+
$charset = 'utf8'
130+
) {
131+
if ($depth > 40) {
132+
echo "<li>Too deep!</li>";
133+
return;
134+
}
135+
136+
if (array_key_exists($messageId, $this->articleNumbers)) {
137+
$articleNumber = $this->articleNumbers[$messageId];
138+
139+
# for debugging that we've actually handled all articles
140+
#unset($this->articleNumbers[$messageId]);
141+
142+
$details = $this->articles[$articleNumber];
143+
144+
echo '<li>';
145+
146+
$details = $this->articles[$articleNumber];
147+
148+
if ($articleNumber != $activeArticleNumber) {
149+
echo "<a href=\"/$group/$articleNumber\">";
150+
} else {
151+
echo "<b>";
152+
}
153+
echo
154+
'<span class="author">',
155+
format_author($details['author'], $charset, nameOnly: true),
156+
'</span>',
157+
'<span class="date">',
158+
'<time datetime="', format_date($details['date'], 'c'), '">',
159+
format_date($details['date']),
160+
'</time>',
161+
'</span>';
162+
163+
$newSubject = format_subject($details['subject'], $charset, trimRe: true);
164+
if ($messageId != $this->root && $newSubject != $subject) {
165+
echo '<span class="subject">';
166+
echo format_subject($details['subject'], $charset);
167+
echo '</span>';
168+
}
169+
170+
if ($articleNumber != $activeArticleNumber) {
171+
echo "</a>";
172+
} else {
173+
echo "</b>";
174+
}
175+
176+
if (array_key_exists($messageId, $this->tree)) {
177+
echo '<ul>';
178+
foreach ($this->tree[$messageId] as $childMessageId) {
179+
$this->printThread(
180+
group: $group,
181+
activeArticleNumber: $activeArticleNumber,
182+
messageId: $childMessageId,
183+
subject: $newSubject,
184+
charset: $charset,
185+
depth: $depth + 1,
186+
);
187+
}
188+
echo '</ul>';
189+
}
190+
191+
echo "</li>";
192+
}
193+
}
97194
}

lib/common.php

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -238,36 +238,39 @@ function spam_protect($txt)
238238

239239

240240
# this turns some common forms of email addresses into mailto: links
241-
function format_author($a, $charset = 'iso-8859-1')
241+
function format_author($a, $charset = 'iso-8859-1', $nameOnly = false)
242242
{
243243
$a = recode_header($a, $charset);
244244
if (preg_match("/^\s*(.+)\s+\\(\"?(.+?)\"?\\)\s*$/", $a, $ar)) {
245-
return "<a href=\"mailto:" .
246-
htmlspecialchars(urlencode(spam_protect($ar[1])), ENT_QUOTES, "UTF-8") .
247-
"\" class=\"email fn n\">" .
248-
str_replace(" ", "&nbsp;", htmlspecialchars($ar[2], ENT_QUOTES, "UTF-8")) . "</a>";
245+
$email= spam_protect($ar[1]);
246+
$name = $ar[2];
249247
}
250-
if (preg_match("/^\s*\"?(.+?)\"?\s*<(.+)>\s*$/", $a, $ar)) {
248+
elseif (preg_match("/^\s*\"?(.+?)\"?\s*<(.+)>\s*$/", $a, $ar)) {
249+
$email = spam_protect($ar[2]);
250+
$name = $ar[1];
251+
}
252+
elseif (strpos("@", $a) !== false) {
253+
$email = $name = spam_protect($a);
254+
} else {
255+
$email = $name = $a;
256+
}
257+
if ($nameOnly) {
258+
return str_replace(" ", "&nbsp;", htmlspecialchars($name, ENT_QUOTES, "UTF-8"));
259+
} else {
251260
return "<a href=\"mailto:" .
252-
htmlspecialchars(urlencode(spam_protect($ar[2])), ENT_QUOTES, "UTF-8") .
261+
htmlspecialchars(urlencode($email), ENT_QUOTES, "UTF-8") .
253262
"\" class=\"email fn n\">" .
254-
str_replace(" ", "&nbsp;", htmlspecialchars($ar[1], ENT_QUOTES, "UTF-8")) . "</a>";
255-
}
256-
if (strpos("@", $a) !== false) {
257-
$a = spam_protect($a);
258-
return "<a href=\"mailto:" . htmlspecialchars(urlencode($a), ENT_QUOTES, "UTF-8") .
259-
"\" class=\"email fn n\">" . htmlspecialchars($a, ENT_QUOTES, "UTF-8") . "</a>";
263+
str_replace(" ", "&nbsp;", $name) . "</a>";
260264
}
261-
return str_replace(" ", "&nbsp;", htmlspecialchars($a, ENT_QUOTES, "UTF-8"));
262265
}
263266

264-
function format_subject($s, $charset = 'iso-8859-1')
267+
function format_subject($s, $charset = 'iso-8859-1', $trimRe = false)
265268
{
266269
global $article;
267270
$s = recode_header($s, $charset);
268271

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

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

281284

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

296-
function format_date($d)
299+
function format_date($d, $format = 'r')
297300
{
298301
$d = strtotime($d);
299-
$d = gmdate('r', $d);
302+
$d = gmdate($format, $d);
300303
return str_replace(" ", "&nbsp;", $d);
301304
}
302305

style.css

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,81 @@ form.subscription-form {
410410
gap: 1em;
411411
}
412412

413+
/* Thread tree, based on: https://www.cssscript.com/tree-view-unlimited-nesting/ */
414+
.list-tree {
415+
--tree-clr: #075985;
416+
--tree-font-size: 1rem;
417+
--tree-item-height: 1.5;
418+
--tree-offset: 0.5rem;
419+
--tree-indent: 0.5rem;
420+
--tree-thickness: 1px;
421+
--tree-style: solid;
422+
}
423+
.list-tree ul{
424+
display: grid;
425+
list-style: none;
426+
font-size: var(--tree-font-size);
427+
padding-inline-start: var(--tree-indent);
428+
max-width: 50em;
429+
}
430+
.list-tree li{
431+
line-height: var(--tree-item-height);
432+
padding-inline-start: var(--tree-offset);
433+
border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
434+
position: relative;
435+
text-indent: .5rem;
436+
437+
&:last-child {
438+
border-color: transparent; /* hide (not remove!) border on last li element*/
439+
}
440+
441+
& a, & b {
442+
display: grid;
443+
grid-template-columns: 1fr auto;
444+
align-item: start;
445+
& span.author {
446+
grid-column: 1 / 1;
447+
white-space: normal;
448+
}
449+
& span.date {
450+
grid-column: 2 / 2;
451+
white-space: nowrap;
452+
font-variant-numeric: tabular-nums;
453+
}
454+
& span.subject {
455+
grid-column: 1 / 2;
456+
white-space: normal;
457+
}
458+
}
459+
&::before{
460+
content: '';
461+
position: absolute;
462+
top: calc(var(--tree-font-size) / 2 + var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness));
463+
left: calc(var(--tree-thickness) * -1);
464+
width: calc(var(--tree-offset) + var(--tree-thickness) * 2);
465+
height: calc(var(--tree-item-height) * var(--tree-font-size) - var(--tree-font-size) / 2);
466+
border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
467+
border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr);
468+
}
469+
&::after{
470+
content: '';
471+
position: absolute;
472+
width: 6px;
473+
height: 6px;
474+
border-radius: 50%;
475+
background-color: var(--tree-clr);
476+
top: calc(var(--tree-item-height) / 2 * 1rem);
477+
left: var(--tree-offset) ;
478+
translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1);
479+
}
480+
& li li{
481+
/*
482+
change line color etc.
483+
--tree-clr: rgb(175, 208, 84);
484+
*/
485+
}
486+
}
487+
413488
@media screen and (max-width: 760px) {
414489
.welcome {
415490
display: none;

0 commit comments

Comments
 (0)