Jake Vanderwerf
4 hours ago 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7
inc/blocks/FeedBlock.php
@@ -1,343 +1,630 @@
<?php
namespace JVBase\blocks;
use JVBase\managers\CacheManager;
use JVBase\managers\Cache;
use JVBase\registrar\Registrar;
use JVBase\base\Site;
use JVBase\forms\TaxonomySelector;
use JVBase\ui\CRUDSkeleton;
use WP_Block;
if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
   exit;
}
class FeedBlock
{
    protected CacheManager $cache;
    protected array $config;
    protected string $path = JVB_DIR.'/build/feed';
   protected Cache $cache;
   protected array $config;
   protected string $path = JVB_DIR.'/build/feed';
    public function __construct()
    {
        $this->cache = CacheManager::for('feed',WEEK_IN_SECONDS);
        add_action('init', [$this, 'registerBlock']);
    }
   protected array $content = [];
   protected array $taxonomies = [];
   protected bool $isGallery = false;
    public function registerBlock()
    {
        register_block_type($this->path, [
            'render_callback' => [$this, 'render']
        ]);
    }
   public function __construct()
   {
      // Initialize cache with connections
      $this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
      if (JVB_TESTING) {
         $this->cache->flush();
      }
    protected function buildParams(array $attributes):array
    {
      add_action('init', [$this, 'registerBlock']);
   }
        if (!jvbCheck('inheritQuery', $attributes)) {
            return [
                'title'     => $attributes['title'],
                'content'   => $attributes['contentTypes'],
                'taxonomies'=> $this->getTaxonomies($attributes['contentTypes'])
            ];
        }
   public function registerBlock()
   {
      register_block_type($this->path, [
         'render_callback' => [$this, 'render']
      ]);
   }
        if (isJVBUserType()) {
            return $this->buildProfileConfig();
        } elseif (isJVBContentTax()) {
            return $this->buildShopConfig();
        } elseif (is_tax()) {
            return $this->buildTaxonomyConfig();
        }
   public function render(array $attributes): string
   {
      if (is_post_type_archive(BASE.'directory')) {
         return '';
      }
      $this->determineContent($attributes);
      if (empty($this->content)) {
         return '';
      }
      $this->determineTaxonomies();
      $classes = '';
        return [];
    }
    protected function buildProfileConfig():array
    {
        $obj = get_queried_object();
        $content = jvbGetUserContentTypes($obj->ID);
        return [
            'is_gallery'    => true,
            'content'       => $content,
            'context'       => jvbNoBase($obj->post_type),
            'taxonomies'    => $this->getTaxonomies($content)
        ];
    }
    protected function buildShopConfig():array
    {
        $type = jvbNoBase(get_queried_object()->taxonomy);
        $content = jvbContentTaxContent($type);
        $context = [
            'content'   => $content,
            'taxonomies' => $this->getTaxonomies($content),
            'context'   => $type,
        ];
        unset($context['taxonomies'][array_search($type, $context['taxonomies'])]);
        return $context;
    }
    protected function buildTaxonomyConfig():array
    {
        $type = jvbNoBase(get_queried_object()->taxonomy);
        $content = jvbContentTaxContent($type);
        return [
            'content'   => $content,
            'taxonomies' => $this->getTaxonomies($content),
            'context'   => $type
        ];
    }
    protected function getTaxonomies(array $content):array
    {
        global $jvb_taxonomy_for;
        $taxonomies = [];
        foreach ($jvb_taxonomy_for as $taxonomy => $for) {
            if (array_intersect($for, $content)) {
                $taxonomies[] = $taxonomy;
            }
        }
        return $taxonomies;
    }
    public function render(array $attributes, string $content, WP_Block $block)
    {
        $this->config = $this->buildParams($attributes);
      return $this->cache->remember(
         $this->config,
         function() {
            return $this->renderBlock();
         }
      return sprintf(
         '<section class="feed-block%s" data-content="%s"%s>
         %s%s%s%s%s
         <footer>%s</footer>
         </section>',
         $classes,
         $this->getContent(),
         $this->isGallery ? ' data-gallery' : '',
         $this->renderFiltersAndControls(),
         $this->renderGrid(),
         $this->renderTemplates(),
         $this->renderLoader(),
         TaxonomySelector::outputSelectorModal(),
         $this->renderActions()
      );
    }
   }
    protected function renderBlock():string
    {
        $work = isJVBUserType() ? ' id="work"' : '';
   protected function determineContent(array $attrs):void
   {
      if (array_key_exists('inheritQuery', $attrs) && $attrs['inheritQuery'] === true) {
         if (is_post_type_archive()) {
            $obj = get_queried_object();
            $this->content = [jvbNoBase($obj->name)];
            return;
         } elseif (!empty(Registrar::getProfileTypes()) && is_singular(Registrar::getProfileTypes())) {
            global $post;
            $author = $post->post_author;
            $role = jvbUserRole($author);
            $registrar = Registrar::getInstance($role);
            if (!$registrar) {
               return;
            }
            $this->content = $registrar->getCreatable();
            return;
         } elseif (is_tax()) {
            $obj = get_queried_object();
            $registrar = Registrar::getInstance($obj->taxonomy);
            if (!$registrar) {
               return;
            }
            if ($registrar->hasFeature('is_content')) {
               //example: tattoo shop, etc TODO
               return;
            }
            $this->content = array_map(function ($item) { return jvbNoBase($item); }, $registrar->registrar->for);
            return;
         }
      }
      // not inheriting, getting from config
      $this->content = $attrs['contentTypes']??[];
   }
      protected function getContent():string
      {
         return implode(',', $this->content);
      }
      protected function determineTaxonomies():void
      {
         $taxonomies = [];
         $ignore = [];
         foreach ($this->content as $content) {
        ob_start();
        ?>
        <section<?= $work ?> class="feed-block"
                             data-source="<?= get_queried_object_id(); ?>"
            <?= (array_key_exists('context', $this->config)) ? ' data-context="'.$this->config['context'].'"' : '' ?>
            <?= (array_key_exists('is_gallery', $this->config)) ? ' data-gallery="true"' : ''?>>
            <?php
            $this->renderFilters();
            $this->renderGrid();
            $this->renderLoader();
            $this->renderTemplates();
            ?>
        </section>
        <?php
        return ob_get_clean();
    }
            $registrar = Registrar::getInstance($content);
            if (!$registrar) continue;
            $theTax = $registrar->registrar->taxonomies;
            foreach ($theTax as $tax) {
               if (!in_array($tax, $ignore) && !in_array($tax, $taxonomies)) {
                  $taxReg = Registrar::getInstance($tax);
                  if ($taxReg->hasFeature('show_feed')) {
                     $taxonomies[] = $tax;
                  } else {
                     $ignore[] = $tax;
                  }
               }
            }
         }
         $this->taxonomies = array_unique($taxonomies);
      }
    protected function renderFilters():void
    {
        if (empty($this->config)) {
            return;
        }
        $many = count($this->config['content']) > 1;
        global $jvb_everything;
        global $jvb_taxonomy_for;
        ?>
        <form class="feed-filters" data-save="feed">
            <details>
            <summary class="row btw">
                <span class="label">SHOWING: </span>
                <?php
                $labels = [];
                foreach ($this->config['content'] as $i => $type) :
                    $checked = $i === 0 ? ' checked' : '';
                    $label = $jvb_everything[$type]['plural'];
                    ?>
                    <input type="radio"
                           id="filter-<?= esc_attr($type) ?>"
                           class="btn"
                           name="content"
                           value="<?= esc_attr($type) ?>"
                        <?= $checked ?>>
                    <label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>" class="row">
                        <?= jvbIcon($type, ['title'=> $label]) ?>
                        <span class="screen-reader-text"><?= $label ?></span>
                    </label>
                <?php
                $labels['filter-'.$type] = $label;
                endforeach;
                ?>
                <ul class="filter-label">
                <?php
                $i = 0;
                foreach ($labels as $id =>$label) {
                    $active = $i === 0 ? ' class="active"' : '';
                    ?>
                    <li id="<?=$id?>"<?=$active?>>
                        <?=$label?>
                    </li>
                    <?php
                    $i++;
                }
                ?>
                </ul>
   protected function renderFiltersAndControls():string
   {
      return sprintf(
         '<details class="all-filters col top left" data-ignore open>
         <summary>Filters %s</summary>
         %s%s%s%s%s
         </details>
         <button data-action="clear-filters" data-ignore hidden>%s<span>Clear Filters</span></span></button>',
         $this->renderContentLabels(),
         $this->renderSearch(),
         $this->renderContent(),
         $this->renderFilters(),
         $this->renderOrderControls(),
         $this->renderViewControls(),
         jvbIcon('x')
      );
   }
      protected function renderSearch():string
      {
         return sprintf(
            '<div class="search row left nowrap">
            <span class="label">Search:</span>
            %s
            </div>',
            jvbSearch()
         );
      }
      protected function renderContentLabels():string
      {
         $inside = '';
         if (count($this->content) === 1) {
            return '';
         }
         foreach ($this->content as $i => $type) {
            $active = $i === 0 ? ' class="active"' : '';
            $inside .= sprintf(
               '<li id="filter-%s"%s>%s</li>',
               $type,
               $active,
               Registrar::getInstance($type)->getPlural()??''
            );
         }
         return sprintf(
            '<ul class="filter-label">%s</ul>',
            $inside
         );
      }
      protected function renderContent():string
      {
         $favourites = '';
         if (Site::has('favourites')) {
            $favourites = sprintf(
               '<input type="checkbox" id="favourites" class="btn" name="favourites" value="on" data-filter="favourites">
               <label for="favourites" title="Show Favourites">%s%s<span class="screen-reader-text">Show Favourites Only</span></label>',
               jvbIcon('heart'),
               jvbIcon('heart', ['style' => 'fill'])
            );
         }
         if (count($this->content) === 1) {
            return empty($favourites)
               ? sprintf(
                  '<input type="hidden" name="content" value="%s">',
                  implode(',', $this->content)
               )
               : sprintf(
               '<div class="content row right">
                  <input type="hidden" name="content" value="%s">
                  %s
                  </div>',
               implode(',', $this->content),
               $favourites
            );
         }
         $i = 0;
         $content = implode('', array_map(function($type) use (&$i) {
            $i++;
            $registrar = Registrar::getInstance($type);
            return sprintf(
               '<input type="radio"
               id="filter-%s"
               class="btn"
               name="content"
               data-filter="content"
               value="%s"%s>
               <label for="filter-%s" title="Show %s">%s<span class="screen-reader-text">%s</span></label>',
               $type,
               $type,
               $i === 0 ? ' checked' : '',
               $type,
               $registrar->getPlural(),
               jvbIcon($registrar->getIcon()),
               $registrar->getPlural()
            );
         }, $this->content));
                <?php if (is_user_logged_in()) : ?>
                    <input type="checkbox" id="favourites" class="btn" name="favourites" value="on">
                    <label for="favourites" title="Show Favourites" class="row">
                        <?= jvbIcon('heart', ['title'    =>'Favourites']) ?>
                        <span class="screen-reader-text">Show Favourites Only</span>
                    </label>
                <?php endif; ?>
                <?php if ($many) {
                    echo '</summary>';
                } ?>
         return sprintf(
            '<div class="content row left nowrap"><span class="label">Showing:</span>
               %s%s
            </div>',
            $content,
            $favourites
         );
      }
                <div class="filters">
                    <div class="filter-group">
                        <span class="label">FILTER BY:</span>
      protected function renderFilters():string
      {
         if (empty ($this->taxonomies)) {
            return '';
         }
         $inside = implode('', array_filter(array_map(function($tax) {
            $registrar = Registrar::getInstance($tax);
            if (!$registrar) return '';
                        <?php
                        foreach ($jvb_taxonomy_for as $tax => $items) :
                            $hidden = !in_array($tax, $this->config['taxonomies']) ? ' hidden' : '';
                            if (in_array($tax, $this->config['taxonomies'])) {
                                $tax = new TaxonomySelector(
                                    'feed-'.$tax,
                                    $tax,
                                    [
                              'update' => '.selected-items-section .selected-items',
                                        'types' => $items,
                              'hidden'=> $hidden
                                    ]
                                );
                                echo  $tax->render();
                            }
            $current = BASE.$this->content[0];
            $contentFor = $registrar->registrar->for;
            $hidden = in_array($current, $contentFor) ? '' : ' hidden';
                        endforeach; ?>
                    </div>
                    <div class="selected-items-section">
                        <div class="selected-items row"></div>
                        <div class="filter-actions row">
                            <?= jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY') ?>
                            <button type="button" class="clear-filters row">
                                <?= jvbIcon('close', ['title'    => 'Clear']) ?>
                                Clear All Filters
                            </button>
                        </div>
                    </div>
                </div>
            $selector = new TaxonomySelector(
               'feed-'.$tax,
               $tax,
               [
                  'icon'   => $registrar->getIcon(),
                  'update'=> '.selected-items-section .selected-items',
                  'types'  => $contentFor,
                  'autocomplete' => false,
                  'hidden' => $hidden,
                  'output' => 'minimal',
                  'search' => true
               ]
            );
            return $selector->render();
                <div class="filter-group">
                    <div class="order-by">
                        <span class="label">ORDER BY:</span>
                        <input type="radio" id="order-title" class="btn" name="orderby" value="title" data-for="artist,shop" hidden>
                        <label for="order-title" title="Order by Name" class="row">
                            <?= jvbIcon('alphabetical') ?>
                            <span class="label">Name</span>
                        </label>
         }, $this->taxonomies)));
         return sprintf(
            '<div class="taxonomies row left">
            <div class="row top nowrap">
            <span class="label">Filter By:</span>
            <div class="row left">%s</div>
            </div>
            <div class="selected-items-section">
               <div class="selected-items row left"></div>
               <div class="filter-actions row">
                  %s
                  <button type="button" class="clear-filters" hidden>
                     %s
                     <span>Clear All Filters</span>
                  </button>
               </div>
            </div>
         </div>',
            $inside,
            str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])),
            jvbIcon('x')
         );
      }
                        <input type="radio" id="order-date" class="btn" name="orderby" value="date" checked>
                        <label for="order-date" title="Order by Date" class="row">
                            <?= jvbIcon('calendar', ['title'=>'Date']) ?>
                            <span class="label">Date</span>
                        </label>
      protected function renderOrderControls():string
      {
         $orderby = [
            [
               'slug'   => 'title',
               'icon'   => 'alphabetical',
               'label'  => 'Name'
            ],
            [
               'slug'   => 'date',
               'icon'   => 'calendar',
               'label'  => 'Date Created',
            ],
            [
               'slug'   => 'date_modified',
               'icon'   => 'clock-clockwise',
               'label'  => 'Date Modified'
            ]
         ];
         $custom = $this->getCustomOrdering();
         $orderby = $orderby + $custom;
         $orderby[] = [
            'slug'   => 'random',
            'icon'   => 'shuffle',
            'label'  => 'Randomly'
         ];
         $custom = implode(',', array_map(function($ord) {
            return $ord['slug'];
         }, $custom));
                        <input type="radio" id="order-random" class="btn" name="orderby" value="random">
                        <label for="order-random" title="Random Order" class="row">
                            <?= jvbIcon('random') ?>
                            <span class="label">Random</span>
                        </label>
                    </div>
         $i = 0;
         $orderby = sprintf(
            '<div class="orderby row left">
            <span class="label">Order by:</span>%s
            </div>',
            implode('', array_map(function ($by) use (&$i){
               $checked = $i === 0 ? ' checked' : '';
               $i++;
               return sprintf(
                  '<input type="radio" id="order-%s" class="btn" name="orderby" value="%s" data-filter="orderby"%s%s>
                     <label for="order-%s" title="Order %s">%s<span class="label">%s</span></label>',
                  $by['slug'],
                  $by['slug'],
                  $checked,
                  empty($by['for']??[]) ? '' : ' data-for="'.implode($by['for']).'"',
                  $by['slug'],
                  $by['slug'] === 'random' ? $by['label'] : 'by '.$by['label'],
                  jvbIcon($by['icon']),
                  $by['label']
               );
            }, $orderby))
         );
                    <div class="order-direction radio-group-label" data-for-order="date,title">
                        <span class="label">ORDER:</span>
                        <input type="radio" id="order-desc" class="btn" name="order" value="desc" checked>
                        <label for="order-desc" title="Newest First" class="row">
                            <?= jvbIcon('desc') ?>
                        </label>
         $order = [
            [
               'slug'   => 'desc',
               'icon'   => 'sort-descending',
               'label'  => 'Descending (A-Z, 1-10)'
            ],
            [
               'slug'   => 'asc',
               'icon'   => 'sort-ascending',
               'label'  => 'Ascending (Z-A, 10-1)'
            ]
         ];
                        <input type="radio" id="order-asc" class="btn" name="order" value="asc">
                        <label for="order-asc" title="Oldest First" class="row">
                            <?= jvbIcon('asc') ?>
                        </label>
                    </div>
                </div>
            </details>
        </form>
        <?php
    }
         $i = 0;
         $order = sprintf(
            '<div class="order-direction row left" data-for-order="date,date_modified,title%s">
            <span class="label">Order:</span>
            %s
            </div>',
            $custom === '' ? '' : ','.$custom,
            implode('', array_map(function ($ord) use (&$i) {
               $checked = $i=== 0 ? ' checked' : '';
               $i++;
               return sprintf(
                  '<input type="radio" id="order-%s" class="btn" name="order" value="%s" data-filter="order"%s>
                  <label for="order-%s" title="Sort %s">
                     %s
                     <span class="label">%s</span>
                  </label>',
                  $ord['slug'],
                  $ord['slug'],
                  $checked,
                  $ord['slug'],
                  $ord['label'],
                  jvbIcon($ord['icon']),
                  $ord['label']
               );
            }, $order))
         );
    protected function renderGrid():void
    {
        ?>
        <div class="item-grid"></div>
        <?php
    }
         return sprintf(
            '<div class="ordering row left nowrap">%s%s</div>',
            $orderby,
            $order
         );
      }
         protected function getCustomOrdering():array
         {
            $custom = [];
            foreach ($this->content as $content) {
               $registrar = Registrar::getInstance($content);
               if (!$registrar || empty($registrar->config('feed')->getCustomOrder())) {
                  continue;
               }
               $custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
            }
            return $custom;
         }
      protected function renderViewControls():string
      {
         $views = [
            'grid'   => ['slug' => 'grid', 'icon' => 'squares-four', 'label' => 'Grid View'],
            'list'   => ['slug' => 'list', 'icon' => 'rows', 'label' => 'List View']
         ];
    protected function renderLoader():void
    {
        ?>
        <button type="button" class="load-more">
            <?= jvbIcon('elbow-left-down', ['title'    =>'More']) ?>
            Show Me More
            <?= jvbIcon('elbow-right-down', ['title'=> 'More']) ?>
        </button>
         $i = 0;
         return sprintf(
            '<div class="view row left nowrap"><span class="label">Switch View:</span>%s</div>',
            implode('', array_map(function ($view) use (&$i) {
               $checked = $i === 0 ? ' checked' : '';
               $i++;
               return sprintf(
                  '<input type="radio"
                  data-view="%s" value="%s" class="btn" name="view" id="view-%s"%s>
                  <label for="view-%s" title="%s">
                  %s<span class="label">%s</span>
</label>',
                  $view['slug'],
                  $view['slug'],
                  $view['slug'],
                  $checked,
                  $view['slug'],
                  $view['label'],
                  jvbIcon($view['icon']),
                  $view['label'],
               );
            }, $views))
         );
      }
        <?= jvbLoadingScreen() ?>
        <?php
        if (array_key_exists('is_gallery', $this->config)) {
            jvbRenderGallery();
        }
    }
   protected function renderGrid():string
   {
      $placeholders = '';
      $total = count($this->content) - 1;
      $icons = [];
      $icon = apply_filters('jvbFeedPlaceholder', '');
    protected function renderTemplates():void
    {
        echo '<template class="feed-item">
        <details class="item feed" data-umami-event="view_feed">
            <summary class="row btw">
                <span class="handle">DETAILS</span>
                <button class="favourite" title="Add to favourites" onclick="toggleFavourite(this)">
                    '.jvbIcon('heart')
               .jvbIcon('heart', ['style'=>'fill']).'
                </button>
                <div class="feed-images">
                    <a>
                        <img width="300px" height="300px" loading="lazy" decoding="async">
                    </a>
                </div>
            </summary>
      for ($i=1; $i<=36; $i++) {
         if (empty($icon)) {
            $rand = $total === 0 ? $total : rand(0, $total);
            $content = $this->content[$rand];
            if (!in_array($content, $icons)) {
               $icons[$content] = Registrar::getInstance($content)->getIcon();
            }
            $icon = jvbIcon($icons[$content]);
         }
            <div class="item-info">
                <h3><a></a></h3>
                <div class="item">
                    <span class="label"></span>
                    <a></a>
                    <p></p>
                </div>
                <div class="item-list">
                    <span class="label"></span>
                        <ul>
                            <li>
                                <a></a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </details>
    </template>';
         $placeholders .= sprintf(
            '<div class="placeholder">%s</div>',
            $icon
         );
      }
        echo '<template class="emptyState">
            <div class="feed-empty-state">
                <h3>NOTHING HERE...</h3>
      return sprintf(
         '<div class="item-grid">%s</div>',
         $placeholders
      );
   }
   protected function renderLoader():string
   {
      return sprintf(
         '<button type="button" class="load-more">%s<span>Show Me More</span>%s</button>
         %s%s',
         jvbIcon('arrow-elbow-left-down'),
         jvbIcon('arrow-elbow-right-down'),
         jvbLoadingScreen(),
         $this->isGallery ? jvbRenderGallery(false) : '',
      );
   }
   protected function renderTemplates():string
   {
      $templates = [];
      foreach ($this->content as $content) {
         $templates[] =  $this->getDefaultTemplate($content);
      }
      $templates[] = sprintf(
         '<template class="feedTerm"><button class="remove-term">%s<span></span>%s</button></template>',
         jvbIcon(jvbDefaultIcon()),
         jvbIcon('x')
      );
      $defaultEmptyState = sprintf(
         '<div class="empty-state">
                <h3>%sNOTHING HERE%s</h3>
                <p>Try tweaking those filters a bit.</p>
                <p>Edmonton\'s got talent - let\'s find it.</p>
            </div>
        </template>';
            </div>',
         jvbIcon(jvbDefaultIcon()),
         jvbIcon(jvbDefaultIcon()),
      );
      $emptyState = apply_filters('jvbFeedEmptyState', $defaultEmptyState, $this->content);
      $templates[] = sprintf(
         '<template class="emptyState">%s</template>',
         $emptyState
      );
        echo '<template class="placeholderTemplate"><div class="placeholder"></div></template>';
    }
      $placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon()));
      $templates[] = sprintf(
         '<template class="placeholderTemplate"><div class="placeholder">%s</div></template>',
         $placeholder
      );
      return implode('', $templates);
   }
   protected function renderActions():string
   {
      return sprintf(
         '<button data-action="refresh" data-ignore>%s<span>Hard Refresh</span></span></button>',
         jvbIcon('arrows-clockwise')
      );
   }
   public static function getFavouritesButton(string $content):string
   {
      $registrar = Registrar::getInstance($content);
      if (!$registrar || !Site::has('favourites') || !$registrar->hasFeature('favouritable')) {
         return '';
      }
      return '<button class="favourite" type="button" title="Add to favourites" data-action="favourite">
         '.jvbIcon('heart')
         .jvbIcon('heart', ['style'=>'fill']).'
      </button>';
   }
   public static function getUpvotesButton(string $content):string
   {
      $registrar = Registrar::getInstance($content);
      if (!Site::has('karma') || !$registrar || !$registrar->hasFeature('karma')){
         return '';
      }
      return '<div class="karma row">
         <button type="button" class="vote" data-action="upvote">
            '.jvbIcon('arrow-fat-up')
            .jvbIcon('arrow-fat-up', ['style'=>'fill']).
         '</button>
         <button type="button" class="vote" data-action="downvote">
            '.jvbIcon('arrow-fat-down')
            .jvbIcon('arrow-fat-down', ['style'=>'fill']).
         '</button>
         <span class="score"></span>
      </div>';
   }
   protected function getDefaultTemplate(string $content): string
   {
      $template = apply_filters('jvbFeedItem', '', $content);
      if (empty($template)) {
         $config = Registrar::getInstance($content)->getConfig('feed');
         $allFields = Registrar::getFieldsFor($content);
         $images = $config['images']??['post_thumbnail'];
         $fields = $config['fields']??['post_title','post_date','post_excerpt'];
         $fields = array_filter($fields, function($field) use($images) {
            return !in_array($field, $images);
         });
         $fields = array_filter($allFields, function($field) use($fields) {
            return in_array($field, $fields);
         }, ARRAY_FILTER_USE_KEY);
         $template = sprintf(
            '<div class="feed item col %s">%s%s',
            $content,
            self::getFavouritesButton($content),
            self::getUpvotesButton($content)
         );
         //Add all defined images, but allow for filtering
         $imageTemplate = '<a>';
         foreach ($images as $image) {
            $imageTemplate .= sprintf(
               '<img data-field="%s" width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">',
               $image
            );
         }
         $imageTemplate .= '</a>';
         $template .= sprintf(
            '<div class="images">%s</div>',
            apply_filters('jvbFeedImages', $imageTemplate, $content, $images)
         );
         //Output default fields, but allow for filtering
         $template .= sprintf(
            '<details>
      <summary>%s</summary>',
            apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content)
         );
         $fieldsTemplate = '';
         foreach ($fields as $fieldName => $config) {
            $fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($config['type'], $fieldName), $content, $fieldName, $config['type']);
         }
         $template .= sprintf(
            '<div class="item-info">%s</div>',
            apply_filters('jvbFeedItemFields', $fieldsTemplate, $content, $fields)
         );
         $template .= '</details></div>';
      }
      return sprintf(
         '<template class="feedItem%s">%s</template>',
         ucfirst($content),
         $template
      );
   }
   protected function defaultFieldTemplate(string $fieldType, string $fieldName):string
   {
      $data = ' data-field="'.$fieldName.'"';
      switch ($fieldName) {
         case 'post_title':
            return '<h3'.$data.'></h3>';
         case 'post_date':
         case 'post_modified':
            return '<time'.$data.'></time>';
      }
      return match($fieldType) {
         'date','datetime','time' => '<time'.$data.'></time>',
         'upload' => '<img'.$data.' width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">',
         'taxonomy'  => '<ul'.$data.'><li><a><i></i></a></li></ul>',
         default  =>  '<p'.$data.'></p>',
      };
   }
}