From 97e7c319d656a5f05489ca996e249e7359303d4d Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 31 May 2026 22:42:33 +0000
Subject: [PATCH] =Jakevan edits done?

---
 inc/blocks/FeedBlock.php |  945 +++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 607 insertions(+), 338 deletions(-)

diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index 1f9703a..8a8f835 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -1,11 +1,13 @@
 <?php
 namespace JVBase\blocks;
 
-use JVBase\managers\CacheManager;
-use JVBase\utility\Features;
-use JVBase\utility\Checker;
+use JVBase\managers\Cache;
+use JVBase\registrar\Registrar;
+use JVBase\base\Site;
 use JVBase\forms\TaxonomySelector;
+use JVBase\ui\CRUDSkeleton;
 use WP_Block;
+use WP_Query;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -13,39 +15,28 @@
 
 class FeedBlock
 {
-	protected CacheManager $cache;
+	protected Cache $cache;
 	protected array $config;
 	protected string $path = JVB_DIR.'/build/feed';
 
+	protected array $content = [];
+	protected array $taxonomies = [];
+	protected ?string $context = null;
+	protected ?int $contextID = null;
+
+	protected bool $isGallery = false;
+
 	public function __construct()
 	{
 		// Initialize cache with connections
-		$this->cache = CacheManager::for('feed_block', WEEK_IN_SECONDS);
-
-		// Set up cache connections for all feed content types
-		$this->setupCacheConnections();
+		$this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
+		if (JVB_TESTING) {
+			$this->cache->flush();
+		}
 
 		add_action('init', [$this, 'registerBlock']);
 	}
 
-	/**
-	 * Set up cache connections for feed content
-	 */
-	protected function setupCacheConnections(): void
-	{
-		// Connect to all content types that show in feed
-		$contentTypes = Features::getTypesWithFeature('show_feed', 'content');
-		foreach ($contentTypes as $type) {
-			CacheManager::for('feed_content')->connectTo('post', $type);
-		}
-
-		// Connect to all taxonomies that show in feed
-		$taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy');
-		foreach ($taxonomies as $tax) {
-			CacheManager::for('feed_taxonomy')->connectTo('taxonomy', $tax);
-		}
-	}
-
 	public function registerBlock()
 	{
 		register_block_type($this->path, [
@@ -53,340 +44,618 @@
 		]);
 	}
 
-	protected function buildParams(array $attributes): array
+	public function render(array $attributes): string
 	{
-		if (!jvbCheck('inheritQuery', $attributes)) {
-			return [
-				'title' => $attributes['title'],
-				'content' => $attributes['contentTypes'],
-				'taxonomies' => $this->getTaxonomies($attributes['contentTypes'])
-			];
+		if (is_post_type_archive(BASE.'directory')) {
+			return '';
 		}
-		$config = [
-			'is_gallery'    => false,
-			'content'       => '',
-			'taxonomies'    => []
-		];
-		$type = get_queried_object();
-
-		if (is_post_type_archive() || is_singular()) {
-			$content = is_singular() ? jvbNoBase($type->post_type) : jvbNoBase($type->name);
-			$mainConfig = JVB_CONTENT[$content]??false;
-			if ($mainConfig && array_key_exists('feed', $mainConfig) && array_key_exists('config', $mainConfig['feed'])){
-				$config = array_merge($config, $mainConfig['feed']['config']);
-			} else {
-				$config['content'] = $content;
-				$config['icon'] = JVB_CONTENT[$content]['icon']??['logo-triangle'];
-			}
-			if (is_singular()) {
-				$config['source'] = $type->ID;
-			}
-
-			$config['taxonomies'] = $this->getTaxonomies([$content]);
-		} elseif (is_tax()) {
-			$content = jvbNoBase($type->taxonomy);
-			$mainConfig = JVB_TAXONOMY[$content]??false;
-			if ($mainConfig) {
-				$config['content'] = $mainConfig['for_content'];
-				$config['context'] = $content;  // ← ADD THIS
-				$config['taxonomies'] = $this->getTaxonomies($mainConfig['for_content']);
-				if (array_key_exists('feed', $mainConfig) && array_key_exists('config', $mainConfig['feed'])){
-					$config = array_merge($config, $mainConfig['feed']['config']);
-				}
-			}
-			$config['source'] = $type->term_id;
+		$this->determineContent($attributes);
+		if (empty($this->content)) {
+			return '';
 		}
+		$this->determineTaxonomies();
+		$classes = '';
 
-		if (!is_array($config['content'])) {
-			$config['content'] = [$config['content']];
-		}
-
-		return $config;
-	}
-
-	/**
-	 * Get taxonomies for given content types
-	 * Uses Checker instead of globals
-	 */
-	protected function getTaxonomies(array $content): array
-	{
-		$checker = Checker::getInstance();
-		$taxonomies = [];
-
-		foreach ($content as $contentType) {
-			$contentTaxonomies = $checker->getTaxonomiesForContent($contentType);
-			$contentTaxonomies = array_filter($contentTaxonomies, function($taxonomy) {
-				return array_key_exists('show_feed', JVB_TAXONOMY[$taxonomy]) && JVB_TAXONOMY[$taxonomy]['show_feed'];
-			});
-			$taxonomies = array_merge($taxonomies, $contentTaxonomies);
-		}
-
-		return array_unique($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%s%s
+			<footer>%s</footer>
+			</section>',
+			$classes,
+			$this->getContent(),
+			$this->isGallery ? ' data-gallery' : '',
+			is_null($this->context) ? '' : ' data-context="'.$this->context.'"',
+			is_null($this->contextID) ? '' : ' data-context-id="'.$this->contextID.'"',
+			$this->renderFiltersAndControls(),
+			$this->renderGrid(),
+			$this->renderTemplates(),
+			$this->renderLoader(),
+			TaxonomySelector::outputSelectorModal(),
+			$this->renderActions()
 		);
 	}
 
-	protected function renderBlock(): string
+	protected function determineContent(array $attrs):void
 	{
-		$ids = (array_key_exists('ids', $this->config) && !empty($this->config['ids'])) ? ' id="'.implode(' ',$this->config['ids']).'"' : '';
-		$classes = (array_key_exists('classes', $this->config) && !empty($this->config['classes'])) ? ' class="'.implode(' ',$this->config['classes']).'"' : '';
-		$source = (array_key_exists('source', $this->config)) ? ' data-source="'.$this->config['source'].'"' : '';
-		$context = (array_key_exists('context', $this->config)) ? ' data-context="'.$this->config['context'].'"' : '';
-		$icons = (array_key_exists('icon', $this->config)) ? ' data-icon="'.$this->config['icon'].'"' : ' data-icon="logo-triangle"';
-		$gallery = (array_key_exists('is_gallery', $this->config) && $this->config['is_gallery']) ? ' data-gallery' : '';
-		$content = (array_key_exists('content', $this->config)) ? ' data-content="'.implode(',',$this->config['content']).'"' : '';
-		ob_start();
-		?>
-		<section<?= $ids.$classes ?> class="feed-block"<?= $content.$source.$context.$gallery.$icons ?>>
-			<?php
-			$this->renderFilters();
-			$this->renderGrid();
-			$this->renderLoader();
-			$this->renderTemplates();
-			echo TaxonomySelector::outputSelectorModal();
-			?>
-		</section>
-		<?php
-		return ob_get_clean();
-	}
+		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);
 
-	protected function renderFilters(): void
-	{
-		if (empty($this->config)) {
-			return;
+				$this->context = jvbNoBase($role);
+				$this->contextID = $author;
+				$registrar = Registrar::getInstance($role);
+				if (!$registrar) {
+					return;
+				}
+				$this->content = $registrar->getCreatable();
+				return;
+			} elseif (is_tax()) {
+				$obj = get_queried_object();
+				$this->context = jvbNoBase($obj->taxonomy);
+				$this->contextID = $obj->term_id;
+				$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) {
 
-		$feedContent = $this->getFeedContent();
-		$hasMany = count($this->config['content']) > 1;
-		?>
-		<form class="feed-filters" data-save="feed-<?=$this->config['context']?>">
-			<?php if ($hasMany) {
-				//If we have multiple content, only show the content first
-				?>
-				<details class="col a-start">
-				<summary class="row btw">
-					<span class="label">SHOWING: </span>
-					<?php
-					$labels = [];
-					foreach ($this->config['content'] as $i => $type) :
-
-						$checked = $i === 0 ? ' checked' : '';
-						$label = $feedContent[$type]['plural'] ?? ucfirst($type);
-						?>
-						<input type="radio"
-							   id="filter-<?= esc_attr($type) ?>"
-							   class="btn"
-							   name="content"
-							   data-filter="content"
-							   value="<?= esc_attr($type) ?>"
-							<?= $checked ?>>
-						<label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>" class="row">
-							<?= jvbIcon($feedContent[$type]['icon']) ?>
-							<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++;
+				$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;
 						}
-						?>
-					</ul>
-			<?php } ?>
-
-
-					<?php if (Features::forSite()->has('favourites') && is_user_logged_in()) : ?>
-						<input type="checkbox" id="favourites" class="btn" name="favourites" value="on"
-							   data-filter="favourites">
-						<label for="favourites" title="Show Favourites" class="row">
-							<?= jvbIcon('heart').jvbIcon('heart', ['style' => 'fill']) ?>
-							<span class="screen-reader-text">Show Favourites Only</span>
-						</label>
-					<?php endif; ?>
-				<?php if ($hasMany) { ?>
-				</summary>
-				<?php } ?>
-
-					<div class="filters">
-						<div class="filter-group row start">
-							<span class="label">FILTER BY:</span>
-
-							<?php
-							$checker = Checker::getInstance();
-							foreach ($this->config['taxonomies'] as $tax) :
-								$taxConfig = JVB_TAXONOMY[$tax] ?? null;
-								if (!$taxConfig) continue;
-
-								$contentForTax = $checker->getContentForTaxonomy($tax);
-								$hidden = empty($contentForTax) ? ' hidden' : '';
-
-								$taxSelector = new TaxonomySelector(
-									'feed-'.$tax,
-									$tax,
-									[
-										'icon'			=> $taxConfig['icon']??'logo-triangle',
-										'update' 		=> '.selected-items-section .selected-items',
-										'types' 		=> $contentForTax,
-										'autocomplete'	=> false,
-										'hidden' 		=> $hidden,
-										'output'		=> 'minimal'
-									]
-								);
-								echo $taxSelector->render();
-							endforeach;
-							?>
-						</div>
-						<div class="selected-items-section">
-							<div class="selected-items row"></div>
-							<div class="filter-actions row">
-								<?= jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match']) ?>
-								<button type="button" class="clear-filters row">
-									<?= jvbIcon('x') ?>
-									Clear All Filters
-								</button>
-							</div>
-						</div>
-					</div>
-
-					<div class="row btw nowrap">
-						<div class="order-by filter-group row start w-full">
-							<span class="label">ORDER BY:</span>
-							<?php
-							//TODO: Get content types that can be sorted alphabetically
-							?>
-							<input type="radio" id="order-title" class="btn" name="orderby" value="title" data-for="artist,shop" data-filter="orderby" hidden>
-							<label for="order-title" title="Order by Name" class="row">
-								<?= jvbIcon('alphabetical') ?>
-								<span class="label">Name</span>
-							</label>
-
-							<input type="radio" id="order-date" class="btn" name="orderby" value="date" data-filter="orderby" checked>
-							<label for="order-date" title="Order by Date" class="row">
-								<?= jvbIcon('calendar', ['title' => 'Date']) ?>
-								<span class="label">Date</span>
-							</label>
-
-							<input type="radio" id="order-random" class="btn" name="orderby" value="random" data-filter="orderby">
-							<label for="order-random" title="Random Order" class="row">
-								<?= jvbIcon('shuffle') ?>
-								<span class="label">Random</span>
-							</label>
-						</div>
-
-						<div class="order-direction filter-group row start w-full" data-for-order="date,title">
-							<span class="label">ORDER:</span>
-							<input type="radio" id="order-desc" class="btn" name="order" value="desc" data-filter="order" checked>
-							<label for="order-desc" title="Sort Descending (A-Z, 1-10)" class="row">
-								<?= jvbIcon('sort-descending') ?>
-								<span class="label" >DESC (A-Z)</span>
-							</label>
-
-							<input type="radio" id="order-asc" class="btn" name="order" value="asc" data-filter="order">
-							<label for="order-asc" title="Sort Ascending (Z-A, 10-1)" class="row">
-								<?= jvbIcon('sort-ascending') ?>
-								<span class="label" >ASC (Z-A)</span>
-							</label>
-						</div>
-					</div>
-			<?php if ($hasMany) { ?>
-				</details>
-			<?php } ?>
-		</form>
-		<?php
-	}
-
-	protected function renderGrid(): void
-	{
-		?>
-		<div class="item-grid"></div>
-		<?php
-	}
-
-	protected function renderLoader(): void
-	{
-		?>
-		<button type="button" class="load-more">
-			<?= jvbIcon('arrow-elbow-left-down') ?>
-			Show Me More
-			<?= jvbIcon('arrow-elbow-right-down') ?>
-		</button>
-
-		<?= jvbLoadingScreen() ?>
-		<?php
-		if (array_key_exists('is_gallery', $this->config)) {
-			jvbRenderGallery();
+					}
+				}
+			}
+			$this->taxonomies = array_unique($taxonomies);
 		}
+
+	protected function renderFiltersAndControls():string
+	{
+		return sprintf(
+			'<details class="all-filters col top left" data-ignore>
+			<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
+		{
+			return sprintf(
+				'<span class="label">Showing: <span class="current">%s</span></span>',
+				$this->content[0]??''
+			);
+
+		}
+		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" data-filter="content" 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) {
+				$registrar = Registrar::getInstance($type);
+
+				$args = [
+					'post_type'	=> $registrar->getBased(),
+					'posts_per_page'	=> 1,
+					'fields'	=> 'ids',
+				];
+				if (!is_null($this->context)) {
+
+					$context = Registrar::getInstance($this->context);
+					switch ($context->getType()) {
+						case 'term':
+							$args['tax_query'] = [];
+							$args['tax_query'][] = [
+								'taxonomy'	=> $context->getBased(),
+								'terms'		=> $this->contextID
+							];
+							break;
+						case 'user':
+							$args['author'] = $this->contextID;
+							break;
+					}
+				}
+				$check = new WP_Query($args);
+				$hasPosts = !empty($check->posts);
+
+				$disabled = !$hasPosts;
+				$checked = $i === 0 && $hasPosts;
+				if ($hasPosts) {
+					$i++;
+				}
+				return sprintf(
+					'<input type="radio"
+					id="filter-%s"
+					class="btn"
+					name="content"
+					data-filter="content"
+					data-label="%s"
+					value="%s"%s%s>
+					<label for="filter-%s" title="Show %s">%s<span class="label">%s</span></label>',
+					$type,
+					$registrar->getSingular(),
+					$type,
+					$checked ? ' checked' : '',
+					$disabled ? ' disabled' : '',
+					$type,
+					$registrar->getSingular(),
+					jvbIcon($registrar->getIcon()),
+					$registrar->getSingular()
+				);
+			}, $this->content));
+
+
+
+			return sprintf(
+				'<div class="content row left nowrap"><span class="label">Showing:</span>
+					%s%s
+				</div>',
+				$content,
+				$favourites
+			);
+		}
+
+		protected function renderFilters():string
+		{
+			if (empty ($this->taxonomies)) {
+				return '';
+			}
+			$inside = implode('', array_filter(array_map(function($tax) {
+				$registrar = Registrar::getInstance($tax);
+				if (!$registrar) return '';
+
+				$current = BASE.$this->content[0];
+				$contentFor = $registrar->registrar->for;
+				$hidden = in_array($current, $contentFor) ? '' : ' hidden';
+
+				$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();
+
+			}, $this->taxonomies)));
+			return sprintf(
+				'<div class="taxonomies row left">
+				<div class="row top wrap">
+					<span class="label">Filter By:</span>
+					%s
+				</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')
+			);
+		}
+
+		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));
+
+			$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))
+			);
+
+			$order = [
+				[
+					'slug'	=> 'desc',
+					'icon'	=> 'sort-descending',
+					'label'	=> 'Descending (A-Z, 1-10)'
+				],
+				[
+					'slug'	=> 'asc',
+					'icon'	=> 'sort-ascending',
+					'label'	=> 'Ascending (Z-A, 10-1)'
+				]
+			];
+
+			$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))
+			);
+
+			return sprintf(
+				'<div class="ordering row top 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']
+			];
+
+			$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))
+			);
+		}
+
+	protected function renderGrid():string
+	{
+		$placeholders = '';
+		$total = count($this->content) - 1;
+		$icons = [];
+		$icon = apply_filters('jvbFeedPlaceholder', '');
+
+		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]);
+			}
+
+			$placeholders .= sprintf(
+				'<div class="placeholder">%s</div>',
+				$icon
+			);
+		}
+
+		return sprintf(
+			'<div class="item-grid">%s</div>',
+			$placeholders
+		);
 	}
 
-	protected function renderTemplates(): void
+	protected function renderLoader():string
 	{
-		echo '<template class="feed-item">'.apply_filters('jvbFeedItem', '<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>
+		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) : '',
+		);
+	}
 
-            <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>', $this->config).'</template>';
+	protected function renderTemplates():string
+	{
 
-		echo '<template class="emptyState">'.apply_filters('jvbFeedEmptyState', '<div class="feed-empty-state">
-                <h3>NOTHING HERE...</h3>
+		$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>', $this->config). '</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">'.apply_filters('jvbFeedPlaceholder', '').'</div></template>';
+		$placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon()));
+		$templates[] = sprintf(
+			'<template class="placeholderTemplate"><div class="placeholder">%s</div></template>',
+			$placeholder
+		);
+
+		return implode('', $templates);
 	}
 
-	/**
-	 * Get feed content using Features instead of get_option
-	 * Returns array of slug => config for types that show in feed
-	 */
-	public function getFeedContent(): array
+	protected function renderActions():string
 	{
-		return JVB()->routes('feed')->getFeedTypesConfig();
+		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>',
+		};
+	}
+
 }

--
Gitblit v1.10.0