From 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 28 May 2026 18:19:57 +0000
Subject: [PATCH] =New Gitbit setpu

---
 inc/blocks/FeedBlock.php |  932 +++++++++++++++++++++++++++++++++-------------------------
 1 files changed, 528 insertions(+), 404 deletions(-)

diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index c6b1f0b..3ed2f38 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -5,6 +5,7 @@
 use JVBase\registrar\Registrar;
 use JVBase\base\Site;
 use JVBase\forms\TaxonomySelector;
+use JVBase\ui\CRUDSkeleton;
 use WP_Block;
 
 if (!defined('ABSPATH')) {
@@ -17,13 +18,17 @@
 	protected array $config;
 	protected string $path = JVB_DIR.'/build/feed';
 
+	protected array $content = [];
+	protected array $taxonomies = [];
+	protected bool $isGallery = false;
+
 	public function __construct()
 	{
 		// Initialize cache with connections
 		$this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
-//		if (JVB_TESTING) {
-//			$this->cache->flush();
-//		}
+		if (JVB_TESTING) {
+			$this->cache->flush();
+		}
 
 		add_action('init', [$this, 'registerBlock']);
 	}
@@ -35,385 +40,483 @@
 		]);
 	}
 
-	protected function buildParams(array $attributes): array
-	{
-		if (!jvbCheck('inheritQuery', $attributes)) {
-			return [
-				'title' => $attributes['title'],
-				'content' => $attributes['contentTypes'],
-				'taxonomies' => $this->getTaxonomies($attributes['contentTypes'])
-			];
-		}
-		$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);
-
-			$registrar = Registrar::getInstance($content)??false;
-			if ($registrar) {
-				$config = array_merge($config, $registrar->getConfig('feed'));
-			} else {
-				$config['content'] = $content;
-				$config['icon'] = jvbDefaultIcon();
-			}
-			if (is_singular()) {
-				$config['source'] = $type->ID;
-			}
-
-			$config['taxonomies'] = $this->getTaxonomies([$content]);
-		} elseif (is_tax()) {
-			$content = jvbNoBase($type->taxonomy);
-			$registrar = Registrar::getInstance($content)??false;
-			if ($registrar) {
-				$config['content'] = $registrar->registrar->for;
-				$config['context'] = $content;
-				$config['taxonomies'] = $this->getTaxonomies($registrar->registrar->for);
-				if (!empty($registrar->getConfig('feed'))){
-					$config = array_merge($config, $registrar->getConfig('feed'));
-				}
-			}
-			$config['source'] = $type->term_id;
-		}
-
-		if (!is_array($config['content'])) {
-			$config['content'] = [$config['content']];
-		}
-
-		return $config;
-	}
-
-	/**
-	 * Get taxonomies for given content types
-
-	 */
-	protected function getTaxonomies(array $content): array
-	{
-
-		$taxonomies = [];
-
-		foreach ($content as $contentType) {
-			$registrar = Registrar::getInstance($contentType);
-			if (!$registrar) {
-				continue;
-			}
-			$contentTaxonomies = $registrar->registrar->taxonomies;
-			$contentTaxonomies = array_filter($contentTaxonomies, function($taxonomy) {
-				return Registrar::getInstance($taxonomy)?->hasFeature('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->cache->generateKey($this->config),
-			function() {
-				return $this->renderBlock();
-			}
-		);
-	}
-	protected function getContext():string|bool
-	{
-		return array_key_exists('context', $this->config)?$this->config['context']:false;
-	}
-	protected function getIDS():array|bool
-	{
-		return (array_key_exists('ids', $this->config) && !empty($this->config['ids'])) ? $this->config['ids'] : false;
-	}
-	protected function getClasses():array|bool
-	{
-		return array_key_exists('classes', $this->config) && !empty($this->config['classes']) ? $this->config['classes'] : false;
-	}
-	protected function getSource():string|bool
-	{
-		return array_key_exists('source', $this->config) ? $this->config['source'] : false;
-	}
-
-	protected function getIcon():string|bool
-	{
-		return array_key_exists('icon', $this->config) ? $this->config['icon'] : false;
-	}
-	protected function isGallery():bool
-	{
-		return (array_key_exists('is_gallery', $this->config) && $this->config['is_gallery']);
-	}
-	protected function getContent():array|bool
-	{
-		return (array_key_exists('content', $this->config) && !empty ($this->config['content'])) ? $this->config['content'] : false;
-	}
-
-	protected function renderBlock(): string
+	public function render(array $attributes): string
 	{
 		if (is_post_type_archive(BASE.'directory')) {
 			return '';
 		}
-		$ids = ($this->getIDS()) ? ' id="'.implode(' ',$this->getIDS()).'"' : '';
-		$classes = ($this->getClasses()) ? ' class="'.implode(' ',$this->getClasses()).'"' : '';
-		$source = ($this->getSource()) ? ' data-source="'.$this->getSource().'"' : '';
-		$context = ($this->getContext()) ? ' data-context="'.$this->getContext().'"' : '';
-		$icons = ($this->getIcon()) ? ' data-icon="'.$this->getIcon().'"' : ' data-icon="'.jvbLogoIcon().'"';
-		$gallery = $this->isGallery() ? ' data-gallery' : '';
-		$content = ($this->getContent()) ? ' data-content="'.implode(',',$this->getContent()).'"' : '';
-		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>
-		<footer><button data-action="refresh" data-ignore><?=jvbIcon('arrows-clockwise')?><span>Hard Refresh</span></span></button></footer>
-		<?php
-		return ob_get_clean();
+		$this->determineContent($attributes);
+		if (empty($this->content)) {
+			return '';
+		}
+		$this->determineTaxonomies();
+		$classes = '';
+
+		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 renderFilters(): void
+	protected function determineContent(array $attrs):void
 	{
-		if (empty($this->config)) {
-			return;
+		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) {
 
-		$feedContent = $this->getFeedContent();
-		$hasMany = count($this->getContent()) > 1;
-		?>
-		<form class="filters" data-save="feed-<?=$this->getContext()?>">
-			<?php if ($hasMany) {
-				//If we have multiple content, only show the content first
-				?>
-				<details class="col left">
-				<summary class="row x-btw">
-					<span class="label">SHOWING: </span>
-					<?php
-					$labels = [];
-					foreach ($this->getContent() 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 (Site::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 }
-				if (!empty ($this->config['taxonomies'])) {
-				?>
-					<div class="filters">
-						<div class="filter-group row left">
-							<span class="label">FILTER BY:</span>
-
-							<?php
-							foreach ($this->config['taxonomies'] as $tax) :
-								$registrar = Registrar::getInstance($tax)??false;
-								if (!$registrar) continue;
-
-								$contentForTax = $registrar->registrar->for;
-								$hidden = empty($contentForTax) ? ' hidden' : '';
-
-								$taxSelector = new TaxonomySelector(
-									'feed-'.$tax,
-									$tax,
-									[
-										'icon'			=> $registrar->getIcon()??jvbLogoIcon(),
-										'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">
-								<?= str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])) ?>
-								<button type="button" class="clear-filters row" hidden>
-									<?= jvbIcon('x') ?>
-									Clear All Filters
-								</button>
-							</div>
-						</div>
-					</div>
-				<?php } ?>
-					<div class="row x-btw nowrap">
-						<div class="order-by filter-group row left 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 Created" class="row">
-								<?= jvbIcon('calendar', ['title' => 'Date']) ?>
-								<span class="label">Date Created</span>
-							</label>
-
-							<input type="radio" id="order-modified" class="btn" name="orderby" value="modified" data-filter="orderby">
-							<label for="order-modified" title="Order by Date Modified" class="row">
-								<?= jvbIcon('clock-clockwise') ?>
-								<span class="label">Date Modified</span>
-							</label>
-
-							<?php
-								$custom = [];
-								foreach ($this->getContent() as $content) {
-									$registrar = Registrar::getInstance($content)??false;
-
-									if ($registrar && !empty($registrar->config('feed')->getCustomOrder())) {
-										$custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
-									}
-								}
-								foreach ($custom as $slug => $conf) {
-									?>
-									<input type="radio" id="order-<?=$slug?>" class="btn" name="orderby" value="<?=$slug?>" data-for="<?=$conf['for']?>" data-filter="orderby">
-									<label for="order-<?=$slug?>" title="<?= $conf['label']?>" class="row">
-										<?= jvbIcon($conf['icon']) ?>
-										<span class="label"><?=$conf['label']?></span>
-									</label>
-									<?php
-								}
-								$custom = implode(',', array_keys($custom));
-							?>
-							<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 left w-full" data-for-order="date,modified,title<?= $custom === '' ? '' : ','.$custom?>">
-							<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">
-			<?php
-			$total = count($this->getContent()) - 1;
-			for ($i = 1; $i <= 36; $i++) {
-				$rand = rand(0, $total);
-				$config = Registrar::getInstance($this->getContent()[$rand]);
-				$icon = jvbIcon($config->getIcon??jvbLogoIcon());
-				?>
-				<div class="placeholder"><?=apply_filters('jvbFeedPlaceholder', $icon) ?></div>
-				<?php
+					}
+				}
 			}
-			?>
-		</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();
-		}
-	}
-
-	protected function renderTemplates(): void
-	{
-		if ($this->getContent()) {
-			foreach ($this->getContent() as $content) {
-				echo $this->getDefaultTemplate($content);
-			}
+			$this->taxonomies = array_unique($taxonomies);
 		}
 
-		echo '<template class="feedTerm"><button class="remove-term">'.jvbIcon(jvbDefaultIcon()).'<span></span>'.jvbIcon('x').'</button></template>';
-		echo '<template class="emptyState">'.apply_filters('jvbFeedEmptyState', '<div class="empty-state">
-                <h3>'.jvbIcon($this->getIcon()).'NOTHING HERE'.jvbIcon($this->getIcon()).'</h3>
+	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));
+
+
+
+			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 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')
+			);
+		}
+
+		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 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 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>
-            </div>', $this->config). '</template>';
-		echo '<template class="placeholderTemplate"><div class="placeholder">'.apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon())).'</div></template>';
+            </div>',
+			jvbIcon(jvbDefaultIcon()),
+			jvbIcon(jvbDefaultIcon()),
+		);
+		$emptyState = apply_filters('jvbFeedEmptyState', $defaultEmptyState, $this->content);
+		$templates[] = sprintf(
+			'<template class="emptyState">%s</template>',
+			$emptyState
+		);
+
+		$placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon()));
+		$templates[] = sprintf(
+			'<template class="placeholderTemplate"><div class="placeholder">%s</div></template>',
+			$placeholder
+		);
+
+		return implode('', $templates);
 	}
 
-	protected function getFavouritesButton(string $content):string
+	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')) {
@@ -424,7 +527,7 @@
 			.jvbIcon('heart', ['style'=>'fill']).'
 		</button>';
 	}
-	protected function getUpvotesButton(string $content):string
+	public static function getUpvotesButton(string $content):string
 	{
 		$registrar = Registrar::getInstance($content);
 		if (!Site::has('karma') || !$registrar || !$registrar->hasFeature('karma')){
@@ -444,39 +547,66 @@
 	}
 	protected function getDefaultTemplate(string $content): string
 	{
-		$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 = '<div class="feed item col '.$content.'">'.$this->getFavouritesButton($content).$this->getUpvotesButton($content);
+		$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);
 
-		//Add all defined images, but allow for filtering
-		$imageTemplate = '<a>';
-		foreach ($images as $image) {
-			$imageTemplate .= '<img data-field="'.$image.'" width="300px" height="300px" sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 800px" loading="lazy" decoding="async">';
+
+			$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>';
 		}
-		$imageTemplate .= '</a>';
-		$template .= '<div class="images">'.apply_filters('jvbFeedImages', $imageTemplate, $content, $images).'</div>';
 
-		//Output default fields, but allow for filtering
-		$template .= '<details>
-		<summary>'.apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content).'</summary>';
 
-		$fieldsTemplate = '';
-
-		foreach ($fields as $fieldName => $config) {
-			$fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($config['type'], $fieldName), $content, $fieldName, $config['type']);
-		}
-		$template .= '<div class="item-info">'.apply_filters('jvbFeedItemFields', $fieldsTemplate, $content, $fields).'</div>';
-		$template .= '</details></div>';
-
-		return '<template class="feedItem'.ucfirst($content).'">'.apply_filters('jvbFeedItem', $template, $content).'</template>';
+		return sprintf(
+			'<template class="feedItem%s">%s</template>',
+			ucfirst($content),
+			$template
+		);
 	}
 
 	protected function defaultFieldTemplate(string $fieldType, string $fieldName):string
@@ -490,17 +620,11 @@
 				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>',
 		};
 	}
-	/**
-	 * Get feed content using Features instead of get_option
-	 * Returns array of slug => config for types that show in feed
-	 */
-	public function getFeedContent(): array
-	{
-		return JVB()->routes('feed')->getFeedTypesConfig();
-	}
+
 }

--
Gitblit v1.10.0