Author: Jim Winstead (jimwins)
Committer: GitHub (web-flow)
Pusher: jimwins
Date: 2025-04-06T12:25:53-07:00

Commit: 
https://github.com/php/web-news/commit/b882172274ee4d32db0cca6838acbc3da2451fb8
Raw diff: 
https://github.com/php/web-news/commit/b882172274ee4d32db0cca6838acbc3da2451fb8.diff

Display thread tree as a tree (#33)

Changed paths:
  M  article.php
  M  lib/ThreadTree.php
  M  lib/common.php
  M  style.css


Diff:

diff --git a/article.php b/article.php
index bc5a34a..7db4dbd 100644
--- a/article.php
+++ b/article.php
@@ -371,21 +371,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) {
diff --git a/lib/ThreadTree.php b/lib/ThreadTree.php
index f9c09c3..06e4434 100644
--- a/lib/ThreadTree.php
+++ b/lib/ThreadTree.php
@@ -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>";
+        }
+    }
 }
diff --git a/lib/common.php b/lib/common.php
index 3e7577c..8c680fc 100644
--- a/lib/common.php
+++ b/lib/common.php
@@ -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)) {
@@ -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) . "...";
@@ -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);
 }
 
diff --git a/style.css b/style.css
index 7d0e23e..cdec4c5 100644
--- a/style.css
+++ b/style.css
@@ -410,6 +410,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;

Reply via email to