]> git.mxchange.org Git - friendica.git/blobdiff - src/Content/Conversation.php
Merge pull request #13646 from annando/page-drop
[friendica.git] / src / Content / Conversation.php
index cdc6e6351f35fbb5b955d7f998f9af60bfc2b73e..66654aab102c5740906873d5c891fcdf003a418d 100644 (file)
@@ -38,12 +38,16 @@ use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\Item as ItemModel;
 use Friendica\Model\Post;
+use Friendica\Model\Post\Category;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Model\Verb;
+use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Object\Post as PostObject;
 use Friendica\Object\Thread;
 use Friendica\Protocol\Activity;
+use Friendica\User\Settings\Entity\UserGServer;
+use Friendica\User\Settings\Repository;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
@@ -53,6 +57,7 @@ use Psr\Log\LoggerInterface;
 
 class Conversation
 {
+       const MODE_CHANNEL       = 'channel';
        const MODE_COMMUNITY     = 'community';
        const MODE_CONTACTS      = 'contacts';
        const MODE_CONTACT_POSTS = 'contact-posts';
@@ -89,22 +94,25 @@ class Conversation
        private $mode;
        /** @var IHandleUserSessions */
        private $session;
+       /** @var Repository\UserGServer */
+       private $userGServer;
 
-       public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session)
+       public function __construct(Repository\UserGServer $userGServer, LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session)
        {
-               $this->activity = $activity;
-               $this->item     = $item;
-               $this->config   = $config;
-               $this->mode     = $mode;
-               $this->baseURL  = $baseURL;
-               $this->profiler = $profiler;
-               $this->logger   = $logger;
-               $this->l10n     = $l10n;
-               $this->args     = $args;
-               $this->pConfig  = $pConfig;
-               $this->page     = $page;
-               $this->app      = $app;
-               $this->session  = $session;
+               $this->activity    = $activity;
+               $this->item        = $item;
+               $this->config      = $config;
+               $this->mode        = $mode;
+               $this->baseURL     = $baseURL;
+               $this->profiler    = $profiler;
+               $this->logger      = $logger;
+               $this->l10n        = $l10n;
+               $this->args        = $args;
+               $this->pConfig     = $pConfig;
+               $this->page        = $page;
+               $this->app         = $app;
+               $this->session     = $session;
+               $this->userGServer = $userGServer;
        }
 
        /**
@@ -153,7 +161,8 @@ class Conversation
                                        'uid'     => 0,
                                        'id'      => $activity['author-id'],
                                        'network' => $activity['author-network'],
-                                       'url'     => $activity['author-link']
+                                       'url'     => $activity['author-link'],
+                                       'alias'   => $activity['author-alias'],
                                ];
                                $url = Contact::magicLinkByContact($author);
                                if (strpos($url, 'contact/redir/') === 0) {
@@ -193,6 +202,52 @@ class Conversation
                }
        }
 
+       /**
+        * Returns the liker phrase based on a list of likers
+        *
+        * @param string $verb   the activity verb
+        * @param array  $likers a list of likers
+        *
+        * @return string the liker phrase
+        *
+        * @throws InternalServerErrorException in case either the verb is invalid or the list of likers is empty
+        */
+       private function getLikerPhrase(string $verb, array $likers): string
+       {
+               $total = count($likers);
+
+               if ($total === 0) {
+                       throw new InternalServerErrorException(sprintf('There has to be at least one Liker for verb "%s"', $verb));
+               } else if ($total === 1) {
+                       $likerString = $likers[0];
+               } else {
+                       if ($total < $this->config->get('system', 'max_likers')) {
+                               $likerString = implode(', ', array_slice($likers, 0, -1));
+                               $likerString .= ' ' . $this->l10n->t('and') . ' ' . $likers[count($likers) - 1];
+                       } else {
+                               $likerString = implode(', ', array_slice($likers, 0, $this->config->get('system', 'max_likers') - 1));
+                               $likerString .= ' ' . $this->l10n->t('and %d other people', $total - $this->config->get('system', 'max_likers'));
+                       }
+               }
+
+               switch ($verb) {
+                       case 'like':
+                               return $this->l10n->tt('%2$s likes this.', '%2$s like this.', $total, $likerString);
+                       case 'dislike':
+                               return $this->l10n->tt('%2$s doesn\'t like this.', '%2$s don\'t like this.', $total, $likerString);
+                       case 'attendyes':
+                               return $this->l10n->tt('%2$s attends.', '%2$s attend.', $total, $likerString);
+                       case 'attendno':
+                               return $this->l10n->tt('%2$s doesn\'t attend.', '%2$s don\'t attend.', $total, $likerString);
+                       case 'attendmaybe':
+                               return $this->l10n->tt('%2$s attends maybe.', '%2$s attend maybe.', $total, $likerString);
+                       case 'announce':
+                               return $this->l10n->tt('%2$s reshared this.', '%2$s reshared this.', $total, $likerString);
+                       default:
+                               throw new InternalServerErrorException(sprintf('Unknown verb "%s"', $verb));
+               }
+       }
+
        /**
         * Format the activity text for an item/photo/video
         *
@@ -205,87 +260,48 @@ class Conversation
        public function formatActivity(array $links, string $verb, int $id): string
        {
                $this->profiler->startRecording('rendering');
-               $o        = '';
                $expanded = '';
-               $phrase   = '';
 
-               $total = count($links);
-               if ($total == 1) {
-                       $likers = $links[0];
-
-                       // Phrase if there is only one liker. In other cases it will be uses for the expanded
-                       // list which show all likers
-                       switch ($verb) {
-                               case 'like':
-                                       $phrase = $this->l10n->t('%s likes this.', $likers);
-                                       break;
-                               case 'dislike':
-                                       $phrase = $this->l10n->t('%s doesn\'t like this.', $likers);
-                                       break;
-                               case 'attendyes':
-                                       $phrase = $this->l10n->t('%s attends.', $likers);
-                                       break;
-                               case 'attendno':
-                                       $phrase = $this->l10n->t('%s doesn\'t attend.', $likers);
-                                       break;
-                               case 'attendmaybe':
-                                       $phrase = $this->l10n->t('%s attends maybe.', $likers);
-                                       break;
-                               case 'announce':
-                                       $phrase = $this->l10n->t('%s reshared this.', $likers);
-                                       break;
-                       }
-               } elseif ($total > 1) {
-                       if ($total < $this->config->get('system', 'max_likers')) {
-                               $likers = implode(', ', array_slice($links, 0, -1));
-                               $likers .= ' ' . $this->l10n->t('and') . ' ' . $links[count($links) - 1];
-                       } else {
-                               $likers = implode(', ', array_slice($links, 0, $this->config->get('system', 'max_likers') - 1));
-                               $likers .= ' ' . $this->l10n->t('and %d other people', $total - $this->config->get('system', 'max_likers'));
-                       }
+               $phrase = $this->getLikerPhrase($verb, $links);
+               $total  = count($links);
 
-                       $spanatts = "class=\"btn btn-link fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
+               if ($total > 1) {
+                       $spanatts  = "class=\"btn btn-link fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
+                       $explikers = $phrase;
 
-                       $explikers = '';
                        switch ($verb) {
                                case 'like':
-                                       $phrase    = $this->l10n->t('<button type="button" %1$s>%2$d people</button> like this', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s like this.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> likes this', '<button type="button" %2$s>%1$d people</button> like this', $total, $spanatts);
                                        break;
                                case 'dislike':
-                                       $phrase    = $this->l10n->t('<button type="button" %1$s>%2$d people</button> don\'t like this', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s don\'t like this.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t like this', '<button type="button" %2$s>%1$d peiple</button> don\'t like this', $total, $spanatts);
                                        break;
                                case 'attendyes':
-                                       $phrase    = $this->l10n->t('<button type="button" %1$s>%2$d people</button> attend', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s attend.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends', '<button type="button" %2$s>%1$d people</button> attend', $total, $spanatts);
                                        break;
                                case 'attendno':
-                                       $phrase    = $this->l10n->t('<button type="button" %1$s>%2$d people</button> don\'t attend', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s don\'t attend.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t attend', '<button type="button" %2$s>%1$d people</button> don\'t attend', $total, $spanatts);
                                        break;
                                case 'attendmaybe':
-                                       $phrase    = $this->l10n->t('<button type="button" %1$s>%2$d people</button> attend maybe', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s attend maybe.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends maybe', '<button type="button" %2$s>%1$d people</button> attend maybe', $total, $spanatts);
                                        break;
                                case 'announce':
-                                       $phrase    = $this->l10n->t('<button type="button" %1$s>%2$d people</button> reshared this', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s reshared this.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> reshared this', '<button type="button" %2$s>%1$d people</button> reshared this', $total, $spanatts);
                                        break;
                        }
 
                        $expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . '</p>';
                }
 
-               $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
+               $output = Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
                        '$phrase' => $phrase,
                        '$type'   => $verb,
                        '$id'     => $id
                ]);
-               $o .= $expanded;
+               $output .= $expanded;
 
                $this->profiler->stopRecording();
-               return $o;
+               return $output;
        }
 
        public function statusEditor(array $x = [], int $notes_cid = 0, bool $popup = false): string
@@ -357,6 +373,8 @@ class Conversation
                        '$editalic'            => $this->l10n->t('Italic'),
                        '$eduline'             => $this->l10n->t('Underline'),
                        '$edquote'             => $this->l10n->t('Quote'),
+                       '$edemojis'            => $this->l10n->t('Add emojis'),
+                       '$contentwarn'         => $this->l10n->t('Content Warning'),
                        '$edcode'              => $this->l10n->t('Code'),
                        '$edimg'               => $this->l10n->t('Image'),
                        '$edurl'               => $this->l10n->t('Link'),
@@ -427,17 +445,17 @@ class Conversation
         * The $mode parameter decides between the various renderings and also
         * figures out how to determine page owner and other contextual items
         * that are based on unique features of the calling module.
-        * @param array  $items
-        * @param string $mode
-        * @param        $update @TODO Which type?
-        * @param bool   $preview
-        * @param string $order
+        * @param array  $items   An array of Posts
+        * @param string $mode    One of self::MODE_*
+        * @param bool   $update  Asynchronous update rendering
+        * @param bool   $preview Post preview (no actual database record)
+        * @param string $order   Either "received" or "commented"
         * @param int    $uid
         * @return string
         * @throws ImagickException
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public function create(array $items, string $mode, $update, bool $preview = false, string $order = 'commented', int $uid = 0): string
+       public function render(array $items, string $mode, bool $update = false, bool $preview = false, string $order = 'commented', int $uid = 0): string
        {
                $this->profiler->startRecording('rendering');
 
@@ -448,12 +466,14 @@ class Conversation
 
                $live_update_div = '';
 
-               $blocklist = $this->getBlocklist();
+               $userGservers = $this->userGServer->listIgnoredByUser($this->session->getLocalUserId());
 
-               $previewing = (($preview) ? ' preview ' : '');
+               $ignoredGsids = array_map(function (UserGServer $userGServer) {
+                       return $userGServer->gsid;
+               }, $userGservers->getArrayCopy());
 
                if ($mode === self::MODE_NETWORK) {
-                       $items = $this->addChildren($items, false, $order, $uid, $mode);
+                       $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
                        if (!$update) {
                                /*
                                * The special div is needed for liveUpdate to kick in for this page.
@@ -475,11 +495,13 @@ class Conversation
                                        . (!empty($_GET['cmin'])      ? '&cmin='      . rawurlencode($_GET['cmin'])      : '')
                                        . (!empty($_GET['cmax'])      ? '&cmax='      . rawurlencode($_GET['cmax'])      : '')
                                        . (!empty($_GET['file'])      ? '&file='      . rawurlencode($_GET['file'])      : '')
-
+                                       . (!empty($_GET['channel'])   ? '&channel='   . rawurlencode($_GET['channel'])   : '')
+                                       . (!empty($_GET['no_sharer']) ? '&no_sharer=' . rawurlencode($_GET['no_sharer']) : '')
+                                       . (!empty($_GET['accounttype']) ? '&accounttype=' . rawurlencode($_GET['accounttype']) : '')
                                        . "'; </script>\r\n";
                        }
                } elseif ($mode === self::MODE_PROFILE) {
-                       $items = $this->addChildren($items, false, $order, $uid, $mode);
+                       $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
 
                        if (!$update) {
                                $tab = !empty($_GET['tab']) ? trim($_GET['tab']) : 'posts';
@@ -504,15 +526,26 @@ class Conversation
                                        . "; var netargs = '?f='; </script>\r\n";
                        }
                } elseif ($mode === self::MODE_DISPLAY) {
-                       $items = $this->addChildren($items, false, $order, $uid, $mode);
+                       $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
 
                        if (!$update) {
                                $live_update_div = '<div id="live-display"></div>' . "\r\n"
                                        . "<script> var profile_uid = " . ($this->session->getLocalUserId() ?: 0) . ";"
                                        . "</script>";
                        }
+               } elseif ($mode === self::MODE_CHANNEL) {
+                       $items = $this->addChildren($items, true, $order, $uid, $mode, $ignoredGsids);
+
+                       if (!$update) {
+                               $live_update_div = '<div id="live-channel"></div>' . "\r\n"
+                                       . "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8)
+                                       . '?f='
+                                       . (!empty($_GET['no_sharer']) ? '&no_sharer=' . rawurlencode($_GET['no_sharer']) : '')
+                                       . (!empty($_GET['accounttype']) ? '&accounttype=' . rawurlencode($_GET['accounttype']) : '')
+                                       . "'; </script>\r\n";
+                       }
                } elseif ($mode === self::MODE_COMMUNITY) {
-                       $items = $this->addChildren($items, true, $order, $uid, $mode);
+                       $items = $this->addChildren($items, true, $order, $uid, $mode, $ignoredGsids);
 
                        if (!$update) {
                                $live_update_div = '<div id="live-community"></div>' . "\r\n"
@@ -523,18 +556,18 @@ class Conversation
                                        . "'; </script>\r\n";
                        }
                } elseif ($mode === self::MODE_CONTACTS) {
-                       $items = $this->addChildren($items, false, $order, $uid, $mode);
+                       $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
 
                        if (!$update) {
                                $live_update_div = '<div id="live-contact"></div>' . "\r\n"
                                        . "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8)
-                                       ."?f='; </script>\r\n";
+                                       . "?f='; </script>\r\n";
                        }
                } elseif ($mode === self::MODE_SEARCH) {
                        $live_update_div = '<div id="live-search"></div>' . "\r\n";
                }
 
-               $page_dropping = $this->session->getLocalUserId() && $this->session->getLocalUserId() == $uid && $mode != self::MODE_SEARCH;
+               $page_dropping = $this->session->getLocalUserId() && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'show_page_drop', true) && ($this->session->getLocalUserId() == $uid && $mode != self::MODE_SEARCH);
 
                if (!$update) {
                        $_SESSION['return_path'] = $this->args->getQueryString();
@@ -545,28 +578,64 @@ class Conversation
 
                $items = $cb['items'];
 
-               $conv_responses = [
-                       'like'        => [],
-                       'dislike'     => [],
-                       'attendyes'   => [],
-                       'attendno'    => [],
-                       'attendmaybe' => [],
-                       'announce'    => [],
-               ];
+               $formSecurityToken = BaseModule::getFormSecurityToken('contact_action');
 
-               if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
-                       unset($conv_responses['dislike']);
+               $threads = $this->getThreadList($items, $mode, $preview, $page_dropping, $formSecurityToken);
+
+               if (in_array($mode, [self::MODE_FILED, self::MODE_SEARCH, self::MODE_CONTACT_POSTS])) {
+                       $page_template = Renderer::getMarkupTemplate('conversation.tpl');
+               } else {
+                       $page_template = Renderer::getMarkupTemplate('threaded_conversation.tpl');
                }
 
-               // array with html for each thread (parent+comments)
-               $threads   = [];
-               $threadsid = -1;
+               $o = Renderer::replaceMacros($page_template, [
+                       '$baseurl'     => $this->baseURL,
+                       '$return_path' => $this->args->getQueryString(),
+                       '$live_update' => $live_update_div,
+                       '$remove'      => $this->l10n->t('remove'),
+                       '$mode'        => $mode,
+                       '$update'      => $update,
+                       '$threads'     => $threads,
+                       '$dropping'    => ($page_dropping ? $this->l10n->t('Delete Selected Items') : false),
+               ]);
 
-               $page_template     = Renderer::getMarkupTemplate("conversation.tpl");
-               $formSecurityToken = BaseModule::getFormSecurityToken('contact_action');
+               $this->profiler->stopRecording();
+               return $o;
+       }
+
+       /**
+        * @param array  $items
+        * @param string $mode One of self::MODE_*
+        * @param bool   $preview
+        * @param bool   $pagedrop Whether to enable the user to select the thread for deletion
+        * @param string $formSecurityToken A 'contact_action' form security token
+        * @return array
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public function getThreadList(array $items, string $mode, bool $preview, bool $pagedrop, string $formSecurityToken): array
+       {
+               if (!$items) {
+                       return [];
+               }
+
+               if (in_array($mode, [self::MODE_FILED, self::MODE_SEARCH, self::MODE_CONTACT_POSTS])) {
+                       $threads = $this->getContextLessThreadList($items, $mode, $preview, $pagedrop, $formSecurityToken);
+               } else {
+                       $conv_responses = [
+                               'like'        => [],
+                               'dislike'     => [],
+                               'attendyes'   => [],
+                               'attendno'    => [],
+                               'attendmaybe' => [],
+                               'announce'    => [],
+                       ];
+
+                       if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
+                               unset($conv_responses['dislike']);
+                       }
 
-               if (!empty($items)) {
-                       if (in_array($mode, [self::MODE_COMMUNITY, self::MODE_CONTACTS, self::MODE_PROFILE])) {
+                       if (in_array($mode, [self::MODE_CHANNEL, self::MODE_COMMUNITY, self::MODE_CONTACTS, self::MODE_PROFILE])) {
                                $writable = true;
                        } else {
                                $writable = $items[0]['writable'] || ($items[0]['uid'] == 0) && in_array($items[0]['network'], Protocol::FEDERATED);
@@ -576,224 +645,51 @@ class Conversation
                                $writable = false;
                        }
 
-                       if (in_array($mode, [self::MODE_FILED, self::MODE_SEARCH, self::MODE_CONTACT_POSTS])) {
-
-                               /*
-                               * "New Item View" on network page or search page results
-                               * - just loop through the items and format them minimally for display
-                               */
-
-                               $tpl = 'search_item.tpl';
-
-                               $uriids = [];
-
-                               foreach ($items as $item) {
-                                       if (in_array($item['uri-id'], $uriids)) {
-                                               continue;
-                                       }
-
-                                       $uriids[] = $item['uri-id'];
-
-                                       if (!$this->item->isVisibleActivity($item)) {
-                                               continue;
-                                       }
-
-                                       if (in_array($item['author-id'], $blocklist)) {
-                                               continue;
-                                       }
-
-                                       $threadsid++;
-
-                                       // prevent private email from leaking.
-                                       if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
-                                               continue;
-                                       }
-
-                                       $profile_name = $item['author-name'];
-                                       if (!empty($item['author-link']) && empty($item['author-name'])) {
-                                               $profile_name = $item['author-link'];
-                                       }
-
-                                       $tags = Tag::populateFromItem($item);
-
-                                       $author       = ['uid' => 0, 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link']];
-                                       $profile_link = Contact::magicLinkByContact($author);
-
-                                       $sparkle = '';
-                                       if (strpos($profile_link, 'contact/redir/') === 0) {
-                                               $sparkle = ' sparkle';
-                                       }
-
-                                       $locate = ['location' => $item['location'], 'coord' => $item['coord'], 'html' => ''];
-                                       Hook::callAll('render_location', $locate);
-                                       $location_html = $locate['html'] ?: Strings::escapeHtml($locate['location'] ?: $locate['coord'] ?: '');
-
-                                       $this->item->localize($item);
-                                       if ($mode === self::MODE_FILED) {
-                                               $dropping = true;
-                                       } else {
-                                               $dropping = false;
-                                       }
-
-                                       $drop = [
-                                               'dropping' => $dropping,
-                                               'pagedrop' => $page_dropping,
-                                               'select'   => $this->l10n->t('Select'),
-                                               'delete'   => $this->l10n->t('Delete'),
-                                       ];
-
-                                       $likebuttons = [
-                                               'like'     => null,
-                                               'dislike'  => null,
-                                               'share'    => null,
-                                               'announce' => null,
-                                       ];
-
-                                       if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
-                                               unset($likebuttons['dislike']);
-                                       }
-
-                                       $body_html = ItemModel::prepareBody($item, true, $preview);
-
-                                       [$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
+                       // Normal View
+                       $conv = new Thread($mode, $preview, $writable);
 
-                                       if (!empty($item['title'])) {
-                                               $title = $item['title'];
-                                       } elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
-                                               $title = ucfirst($item['content-warning']);
-                                       } else {
-                                               $title = '';
-                                       }
-
-                                       if (!empty($item['featured'])) {
-                                               $pinned = $this->l10n->t('Pinned item');
-                                       } else {
-                                               $pinned = '';
-                                       }
-
-                                       $tmp_item = [
-                                               'template'             => $tpl,
-                                               'id'                   => ($preview ? 'P0' : $item['id']),
-                                               'guid'                 => ($preview ? 'Q0' : $item['guid']),
-                                               'commented'            => $item['commented'],
-                                               'received'             => $item['received'],
-                                               'created_date'         => $item['created'],
-                                               'uriid'                => $item['uri-id'],
-                                               'network'              => $item['network'],
-                                               'network_name'         => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
-                                               'network_icon'         => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
-                                               'linktitle'            => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
-                                               'profile_url'          => $profile_link,
-                                               'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
-                                               'name'                 => $profile_name,
-                                               'sparkle'              => $sparkle,
-                                               'lock'                 => false,
-                                               'thumb'                => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
-                                               'title'                => $title,
-                                               'body_html'            => $body_html,
-                                               'tags'                 => $tags['tags'],
-                                               'hashtags'             => $tags['hashtags'],
-                                               'mentions'             => $tags['mentions'],
-                                               'implicit_mentions'    => $tags['implicit_mentions'],
-                                               'txt_cats'             => $this->l10n->t('Categories:'),
-                                               'txt_folders'          => $this->l10n->t('Filed under:'),
-                                               'has_cats'             => ((count($categories)) ? 'true' : ''),
-                                               'has_folders'          => ((count($folders)) ? 'true' : ''),
-                                               'categories'           => $categories,
-                                               'folders'              => $folders,
-                                               'text'                 => strip_tags($body_html),
-                                               'localtime'            => DateTimeFormat::local($item['created'], 'r'),
-                                               'utc'                  => DateTimeFormat::utc($item['created'], 'c'),
-                                               'ago'                  => (($item['app']) ? $this->l10n->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])),
-                                               'location_html'        => $location_html,
-                                               'indent'               => '',
-                                               'owner_name'           => '',
-                                               'owner_url'            => '',
-                                               'owner_photo'          => $this->baseURL->remove($this->item->getOwnerAvatar($item)),
-                                               'plink'                => ItemModel::getPlink($item),
-                                               'edpost'               => false,
-                                               'pinned'               => $pinned,
-                                               'isstarred'            => 'unstarred',
-                                               'star'                 => false,
-                                               'drop'                 => $drop,
-                                               'vote'                 => $likebuttons,
-                                               'like_html'            => '',
-                                               'dislike_html '        => '',
-                                               'comment_html'         => '',
-                                               'conv'                 => ($preview ? '' : ['href' => 'display/' . $item['guid'], 'title' => $this->l10n->t('View in context')]),
-                                               'previewing'           => $previewing,
-                                               'wait'                 => $this->l10n->t('Please wait'),
-                                               'thread_level'         => 1,
-                                       ];
-
-                                       $arr = ['item' => $item, 'output' => $tmp_item];
-                                       Hook::callAll('display_item', $arr);
-
-                                       $threads[$threadsid]['id']      = $item['id'];
-                                       $threads[$threadsid]['network'] = $item['network'];
-                                       $threads[$threadsid]['items']   = [$arr['output']];
+                       /*
+                       * get all the topmost parents
+                       * this shouldn't be needed, as we should have only them in our array
+                       * But for now, this array respects the old style, just in case
+                       */
+                       foreach ($items as $item) {
+                               if (in_array($item['author-id'], $this->getBlocklist())) {
+                                       continue;
                                }
-                       } else {
-                               // Normal View
-                               $page_template = Renderer::getMarkupTemplate("threaded_conversation.tpl");
-
-                               $conv = new Thread($mode, $preview, $writable);
-
-                               /*
-                               * get all the topmost parents
-                               * this shouldn't be needed, as we should have only them in our array
-                               * But for now, this array respects the old style, just in case
-                               */
-                               foreach ($items as $item) {
-                                       if (in_array($item['author-id'], $blocklist)) {
-                                               continue;
-                                       }
 
-                                       // Can we put this after the visibility check?
-                                       $this->builtinActivityPuller($item, $conv_responses);
+                               // Can we put this after the visibility check?
+                               $this->builtinActivityPuller($item, $conv_responses);
 
-                                       // Only add what is visible
-                                       if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
-                                               continue;
-                                       }
+                               // Only add what is visible
+                               if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
+                                       continue;
+                               }
 
-                                       if (!$this->item->isVisibleActivity($item)) {
-                                               continue;
-                                       }
+                               if (!$this->item->isVisibleActivity($item)) {
+                                       continue;
+                               }
 
-                                       /// @todo Check if this call is needed or not
-                                       $arr = ['item' => $item];
-                                       Hook::callAll('display_item', $arr);
+                               /// @todo Check if this call is needed or not
+                               $arr = ['item' => $item];
+                               Hook::callAll('display_item', $arr);
 
-                                       $item['pagedrop'] = $page_dropping;
+                               $item['pagedrop'] = $pagedrop;
 
-                                       if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
-                                               $item_object = new PostObject($item);
-                                               $conv->addParent($item_object);
-                                       }
+                               if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
+                                       $item_object = new PostObject($item);
+                                       $conv->addParent($item_object);
                                }
+                       }
 
-                               $threads = $conv->getTemplateData($conv_responses, $formSecurityToken);
-                               if (!$threads) {
-                                       $this->logger->info('[ERROR] conversation : Failed to get template data.');
-                                       $threads = [];
-                               }
+                       $threads = $conv->getTemplateData($conv_responses, $formSecurityToken);
+                       if (!$threads) {
+                               $this->logger->info('[ERROR] conversation : Failed to get template data.');
+                               $threads = [];
                        }
                }
 
-               $o = Renderer::replaceMacros($page_template, [
-                       '$baseurl'     => $this->baseURL,
-                       '$return_path' => $this->args->getQueryString(),
-                       '$live_update' => $live_update_div,
-                       '$remove'      => $this->l10n->t('remove'),
-                       '$mode'        => $mode,
-                       '$update'      => $update,
-                       '$threads'     => $threads,
-                       '$dropping'    => ($page_dropping ? $this->l10n->t('Delete Selected Items') : false),
-               ]);
-
-               $this->profiler->stopRecording();
-               return $o;
+               return $threads;
        }
 
        private function getBlocklist(): array
@@ -847,7 +743,8 @@ class Conversation
                                $row['causer-avatar'] = $contact['thumb'];
                                $row['causer-name']   = $contact['name'];
                        } elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
-                               ($row['author-id'] == $activity['causer-id'])) {
+                               ($row['author-id'] == $activity['causer-id'])
+                       ) {
                                return $row;
                        }
                }
@@ -865,11 +762,19 @@ class Conversation
                        case ItemModel::PR_BCC:
                                $row['direction'] = ['direction' => 7, 'title' => $this->l10n->t('You had been addressed (%s).', 'bcc')];
                                break;
+                       case ItemModel::PR_AUDIENCE:
+                               $row['direction'] = ['direction' => 7, 'title' => $this->l10n->t('You had been addressed (%s).', 'audience')];
+                               break;
                        case ItemModel::PR_FOLLOWER:
                                $row['direction'] = ['direction' => 6, 'title' => $this->l10n->t('You are following %s.', $row['causer-name'] ?: $row['author-name'])];
                                break;
                        case ItemModel::PR_TAG:
-                               $row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('You subscribed to one or more tags in this post.')];
+                               $tags = Category::getArrayByURIId($row['uri-id'], $row['uid'], Category::SUBCRIPTION);
+                               if (!empty($tags)) {
+                                       $row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('You subscribed to %s.', implode(', ', $tags))];
+                               } else {
+                                       $row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('You subscribed to one or more tags in this post.')];
+                               }
                                break;
                        case ItemModel::PR_ANNOUNCEMENT:
                                if (!empty($row['causer-id']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'display_resharer')) {
@@ -880,9 +785,15 @@ class Conversation
                                }
 
                                if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
-                                       $causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']];
+                                       $causer = [
+                                               'uid'     => 0,
+                                               'id'      => $row['causer-id'],
+                                               'network' => $row['causer-network'],
+                                               'url'     => $row['causer-link'],
+                                               'alias'   => $row['causer-alias'],
+                                       ];
 
-                                       $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>');
+                                       $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="' . htmlentities(Contact::magicLinkByContact($causer)) . '">' . htmlentities($row['causer-name']) . '</a>');
                                }
                                $row['direction'] = ['direction' => 3, 'title' => (empty($row['causer-id']) ? $this->l10n->t('Reshared') : $this->l10n->t('Reshared by %s <%s>', $row['causer-name'], $row['causer-link']))];
                                break;
@@ -932,13 +843,14 @@ class Conversation
         *
         * @param array  $parents       Parent items
         * @param bool   $block_authors
-        * @param bool   $order
+        * @param string $order         Either "received" or "commented"
         * @param int    $uid
-        * @param string $mode
+        * @param string $mode          One of self::MODE_*
+        * @param array  $ignoredGsids  List of ids of servers ignored by the user
         * @return array items with parents and comments
-        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws InternalServerErrorException
         */
-       private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode): array
+       private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode, array $ignoredGsids = []): array
        {
                $this->profiler->startRecording('rendering');
                if (count($parents) > 1) {
@@ -977,20 +889,28 @@ class Conversation
                        $condition['author-hidden'] = false;
                }
 
-               if ($this->config->get('system', 'emoji_activities')) {
-                       $emojis = $this->getEmojis($uriids);
+               $emojis      = $this->getEmojis($uriids);
+               $quoteshares = $this->getQuoteShares($uriids);
+
+               if (!$this->config->get('system', 'legacy_activities')) {
                        $condition = DBA::mergeConditions($condition, ["(`gravity` != ? OR `origin`)", ItemModel::GRAVITY_ACTIVITY]);
                }
 
-               $condition = DBA::mergeConditions($condition,
-                       ["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)]);
+               $condition = DBA::mergeConditions(
+                       $condition,
+                       ["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)]
+               );
 
                $condition = DBA::mergeConditions($condition, ["(`uid` != ? OR `private` != ?)", 0, ItemModel::PRIVATE]);
 
-               $condition = DBA::mergeConditions($condition,
-                       ["`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked`
+               $condition = DBA::mergeConditions(
+                       $condition,
+                       [
+                               "`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked`
                        AND ((NOT `contact-pending` AND (`contact-rel` IN (?, ?))) OR `self` OR `contact-uid` = ?)",
-                       Contact::SHARING, Contact::FRIEND, 0]);
+                               Contact::SHARING, Contact::FRIEND, 0
+                       ]
+               );
 
                $thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]);
 
@@ -1014,6 +934,14 @@ class Conversation
                                continue;
                        }
 
+                       if (
+                               in_array($row['author-gsid'], $ignoredGsids)
+                               || in_array($row['owner-gsid'], $ignoredGsids)
+                               || in_array($row['causer-gsid'], $ignoredGsids)
+                       ) {
+                               continue;
+                       }
+
                        if (($mode != self::MODE_CONTACTS) && !$row['origin']) {
                                $row['featured'] = false;
                        }
@@ -1086,7 +1014,8 @@ class Conversation
                }
 
                foreach ($items as $key => $row) {
-                       $items[$key]['emojis'] = $emojis[$key] ?? [];
+                       $items[$key]['emojis']      = $emojis[$key] ?? [];
+                       $items[$key]['quoteshares'] = $quoteshares[$key] ?? [];
 
                        $always_display = in_array($mode, [self::MODE_CONTACTS, self::MODE_CONTACT_POSTS]);
 
@@ -1097,8 +1026,10 @@ class Conversation
                        $items[$key]['user-collapsed-author'] = !$always_display && in_array($row['author-id'], $collapses);
                        $items[$key]['user-collapsed-owner']  = !$always_display && in_array($row['owner-id'], $collapses);
 
-                       if (in_array($mode, [self::MODE_COMMUNITY, self::MODE_NETWORK]) &&
-                               (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores))) {
+                       if (
+                               in_array($mode, [self::MODE_CHANNEL, self::MODE_COMMUNITY, self::MODE_NETWORK]) &&
+                               (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores))
+                       ) {
                                unset($items[$key]);
                        }
                }
@@ -1128,19 +1059,24 @@ class Conversation
                ];
 
                $index_list = array_values($activity_emoji);
-               $verbs      = array_merge(array_keys($activity_emoji), [Activity::EMOJIREACT]);
+               $verbs      = array_merge(array_keys($activity_emoji), [Activity::EMOJIREACT, Activity::POST]);
 
-               $condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => ItemModel::GRAVITY_ACTIVITY, 'verb' => $verbs], ["NOT `deleted`"]);
+               $condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => [ItemModel::GRAVITY_ACTIVITY, ItemModel::GRAVITY_COMMENT], 'verb' => $verbs], ["NOT `deleted`"]);
                $separator = chr(255) . chr(255) . chr(255);
 
-               $sql = "SELECT `thr-parent-id`, `body`, `verb`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`author-name`, '" . $separator . "', ' ') SEPARATOR '". $separator ."' LIMIT 50) AS `title` FROM `post-view` WHERE " . array_shift($condition) . " GROUP BY `thr-parent-id`, `verb`, `body`";
+               $sql = "SELECT `thr-parent-id`, `body`, `verb`, `gravity`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`author-name`, '" . $separator . "', ' ') SEPARATOR '" . $separator . "' LIMIT 50) AS `title` FROM `post-view` WHERE " . array_shift($condition) . " GROUP BY `thr-parent-id`, `verb`, `body`, `gravity`";
 
                $emojis = [];
 
                $rows = DBA::p($sql, $condition);
                while ($row = DBA::fetch($rows)) {
-                       $row['verb'] = $row['body'] ? Activity::EMOJIREACT : $row['verb'];
-                       $emoji       = $row['body'] ?: $activity_emoji[$row['verb']];
+                       if ($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) {
+                               $row['verb'] = $row['body'] ? Activity::EMOJIREACT : $row['verb'];
+                               $emoji       = $row['body'] ?: $activity_emoji[$row['verb']];
+                       } else {
+                               $emoji = '';
+                       }
+
                        if (!isset($index_list[$emoji])) {
                                $index_list[] = $emoji;
                        }
@@ -1156,6 +1092,31 @@ class Conversation
                return $emojis;
        }
 
+       /**
+        * Fetch quote shares from the conversation
+        *
+        * @param array $uriids
+        * @return array
+        */
+       private function getQuoteShares(array $uriids): array
+       {
+               $condition = DBA::mergeConditions(['quote-uri-id' => $uriids], ["NOT `quote-uri-id` IS NULL"]);
+               $separator = chr(255) . chr(255) . chr(255);
+
+               $sql = "SELECT `quote-uri-id`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`name`, '" . $separator . "', ' ') SEPARATOR '" . $separator . "' LIMIT 50) AS `title` FROM `post-content` INNER JOIN `post` ON `post`.`uri-id` = `post-content`.`uri-id` INNER JOIN `contact` ON `post`.`author-id` = `contact`.`id` WHERE " . array_shift($condition) . " GROUP BY `quote-uri-id`";
+
+               $quotes = [];
+
+               $rows = DBA::p($sql, $condition);
+               while ($row = DBA::fetch($rows)) {
+                       $quotes[$row['quote-uri-id']]['total'] = $row['total'];
+                       $quotes[$row['quote-uri-id']]['title'] = array_unique(explode($separator, $row['title']));
+               }
+               DBA::close($rows);
+
+               return $quotes;
+       }
+
        /**
         * Plucks the children of the given parent from a given item list.
         *
@@ -1274,7 +1235,7 @@ class Conversation
                                        // Searches the post item in the children
                                        $j = 0;
                                        while ($child['children'][$j]['verb'] !== Activity::POST && $j < count($child['children'])) {
-                                               $j ++;
+                                               $j++;
                                        }
 
                                        $moved_item = $child['children'][$j];
@@ -1348,8 +1309,10 @@ class Conversation
                * items and add them as children of their top-level post.
                */
                foreach ($parents as $i => $parent) {
-                       $parents[$i]['children'] = array_merge($this->getItemChildren($item_array, $parent, true),
-                               $this->getItemChildren($item_array, $parent, false));
+                       $parents[$i]['children'] = array_merge(
+                               $this->getItemChildren($item_array, $parent, true),
+                               $this->getItemChildren($item_array, $parent, false)
+                       );
                }
 
                foreach ($parents as $i => $parent) {
@@ -1362,7 +1325,7 @@ class Conversation
                        }
                }
 
-               /// @TODO: Stop recusrsively adding all children back to the top level (!!!)
+               /// @TODO: Stop recursively adding all children back to the top level (!!!)
                /// However, this apparently ensures responses (likes, attendance) display (?!)
                foreach ($parents as $parent) {
                        if (count($parent['children'])) {
@@ -1457,4 +1420,179 @@ class Conversation
        {
                return strcmp($b['created'], $a['created']);
        }
+
+       /**
+        * "New Item View" on network page or search page results
+        * - just loop through the items and format them minimally for display
+        *
+        * @param array  $items
+        * @param string $mode              One of self::MODE_*
+        * @param bool   $preview           Whether the display is a preview
+        * @param bool   $pagedrop          Whether the user can select the threads for deletion
+        * @param string $formSecurityToken A 'contact_action' form security token
+        * @return array
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public function getContextLessThreadList(array $items, string $mode, bool $preview, bool $pagedrop, string $formSecurityToken): array
+       {
+               $threads = [];
+               $uriids = [];
+
+               foreach ($items as $item) {
+                       if (in_array($item['uri-id'], $uriids)) {
+                               continue;
+                       }
+
+                       $uriids[] = $item['uri-id'];
+
+                       if (!$this->item->isVisibleActivity($item)) {
+                               continue;
+                       }
+
+                       if (in_array($item['author-id'], $this->getBlocklist())) {
+                               continue;
+                       }
+
+                       // prevent private email from leaking.
+                       if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
+                               continue;
+                       }
+
+                       $profile_name = $item['author-name'];
+                       if (!empty($item['author-link']) && empty($item['author-name'])) {
+                               $profile_name = $item['author-link'];
+                       }
+
+                       $tags = Tag::populateFromItem($item);
+
+                       $author       = [
+                               'uid'     => 0,
+                               'id'      => $item['author-id'],
+                               'network' => $item['author-network'],
+                               'url'     => $item['author-link'],
+                               'alias'   => $item['author-alias'],
+                       ];
+                       $profile_link = Contact::magicLinkByContact($author);
+
+                       $sparkle = '';
+                       if (strpos($profile_link, 'contact/redir/') === 0) {
+                               $sparkle = ' sparkle';
+                       }
+
+                       $locate = ['location' => $item['location'], 'coord' => $item['coord'], 'html' => ''];
+                       Hook::callAll('render_location', $locate);
+                       $location_html = $locate['html'] ?: Strings::escapeHtml($locate['location'] ?: $locate['coord'] ?: '');
+
+                       $this->item->localize($item);
+                       if ($mode === self::MODE_FILED) {
+                               $dropping = true;
+                       } else {
+                               $dropping = false;
+                       }
+
+                       $drop = [
+                               'dropping' => $dropping,
+                               'pagedrop' => $pagedrop,
+                               'select'   => $this->l10n->t('Select'),
+                               'delete'   => $this->l10n->t('Delete'),
+                       ];
+
+                       $likebuttons = [
+                               'like'     => null,
+                               'dislike'  => null,
+                               'share'    => null,
+                               'announce' => null,
+                       ];
+
+                       if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
+                               unset($likebuttons['dislike']);
+                       }
+
+                       $body_html = ItemModel::prepareBody($item, true, $preview);
+
+                       [$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
+
+                       if (!empty($item['title'])) {
+                               $title = $item['title'];
+                       } elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
+                               $title = ucfirst($item['content-warning']);
+                       } else {
+                               $title = '';
+                       }
+
+                       if (!empty($item['featured'])) {
+                               $pinned = $this->l10n->t('Pinned item');
+                       } else {
+                               $pinned = '';
+                       }
+
+                       $tmp_item = [
+                               'template'             => 'search_item.tpl',
+                               'id'                   => ($preview ? 'P0' : $item['id']),
+                               'guid'                 => ($preview ? 'Q0' : $item['guid']),
+                               'commented'            => $item['commented'],
+                               'received'             => $item['received'],
+                               'created_date'         => $item['created'],
+                               'uriid'                => $item['uri-id'],
+                               'author_gsid'          => $item['author-gsid'],
+                               'network'              => $item['network'],
+                               'network_name'         => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
+                               'network_icon'         => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
+                               'linktitle'            => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
+                               'profile_url'          => $profile_link,
+                               'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
+                               'name'                 => $profile_name,
+                               'sparkle'              => $sparkle,
+                               'lock'                 => false,
+                               'thumb'                => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
+                               'title'                => $title,
+                               'body_html'            => $body_html,
+                               'tags'                 => $tags['tags'],
+                               'hashtags'             => $tags['hashtags'],
+                               'mentions'             => $tags['mentions'],
+                               'implicit_mentions'    => $tags['implicit_mentions'],
+                               'txt_cats'             => $this->l10n->t('Categories:'),
+                               'txt_folders'          => $this->l10n->t('Filed under:'),
+                               'has_cats'             => ((count($categories)) ? 'true' : ''),
+                               'has_folders'          => ((count($folders)) ? 'true' : ''),
+                               'categories'           => $categories,
+                               'folders'              => $folders,
+                               'text'                 => strip_tags($body_html),
+                               'localtime'            => DateTimeFormat::local($item['created'], 'r'),
+                               'utc'                  => DateTimeFormat::utc($item['created'], 'c'),
+                               'ago'                  => (($item['app']) ? $this->l10n->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])),
+                               'location_html'        => $location_html,
+                               'indent'               => '',
+                               'owner_name'           => '',
+                               'owner_url'            => '',
+                               'owner_photo'          => $this->baseURL->remove($this->item->getOwnerAvatar($item)),
+                               'plink'                => ItemModel::getPlink($item),
+                               'edpost'               => false,
+                               'pinned'               => $pinned,
+                               'isstarred'            => 'unstarred',
+                               'star'                 => false,
+                               'drop'                 => $drop,
+                               'vote'                 => $likebuttons,
+                               'like_html'            => '',
+                               'dislike_html '        => '',
+                               'comment_html'         => '',
+                               'conv'                 => $preview ? '' : ['href' => 'display/' . $item['guid'], 'title' => $this->l10n->t('View in context')],
+                               'previewing'           => $preview ? ' preview ' : '',
+                               'wait'                 => $this->l10n->t('Please wait'),
+                               'thread_level'         => 1,
+                       ];
+
+                       $arr = ['item' => $item, 'output' => $tmp_item];
+                       Hook::callAll('display_item', $arr);
+
+                       $threads[] = [
+                               'id'      => $item['id'],
+                               'network' => $item['network'],
+                               'items'   => [$arr['output']],
+                       ];
+               }
+
+               return $threads;
+       }
 }