| | |
| | | <?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>', |
| | | }; |
| | | } |
| | | |
| | | } |