From ad052f72a6c994dfb2fe0aa11970c9d110564004 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 10 Feb 2026 17:50:45 +0000
Subject: [PATCH] =Fix for FAQpage schema not outputting correctly, as well as Form.php status radios and editForm on CRUDSkeleton.php

---
 inc/managers/SEO/schemas/_setup.php                    |    1 
 inc/managers/SEO/schemas/SchemaResolverRegistry.php    |    3 
 inc/meta/Form.php                                      |   49 ++++++++---
 assets/js/concise/PopulateForm.js                      |    4 
 assets/js/min/populate.min.js                          |    2 
 inc/ui/CRUDSkeleton.php                                |   63 +++++++++++----
 inc/managers/SEO/schemas/resolvers/FAQPageResolver.php |   75 ++++++++++++++++++
 7 files changed, 162 insertions(+), 35 deletions(-)

diff --git a/assets/js/concise/PopulateForm.js b/assets/js/concise/PopulateForm.js
index 2bd02b8..761a134 100644
--- a/assets/js/concise/PopulateForm.js
+++ b/assets/js/concise/PopulateForm.js
@@ -203,7 +203,7 @@
 		}
 
 		const grid = field.querySelector('.item-grid');
-		let uploadContainer = field.querySelector('.file-upload-container');
+		let uploadContainer = field.querySelector('.file-upload-wrapper');
 		uploadContainer.hidden = ids.length > 0;
 		field.querySelector('.progress')?.remove();
 		if (grid) {
@@ -251,7 +251,7 @@
 			if (!value || !Array.isArray(value) || value.length === 0) return;
 
 			let grid = field.querySelector('.item-grid');
-			let uploadContainer = field.querySelector('.file-upload-container');
+			let uploadContainer = field.querySelector('.file-upload-wrapper');
 			uploadContainer.hidden = value.length > 0;
 			if (grid) {
 				window.removeChildren(grid);
diff --git a/assets/js/min/populate.min.js b/assets/js/min/populate.min.js
index 5cc75f4..6e0ab9c 100644
--- a/assets/js/min/populate.min.js
+++ b/assets/js/min/populate.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.templates=window.jvbTemplates,this.formHelper=window.jvbForm,this.defineTemplates(),this.data=null,this.form=null}populate(e,t={}){if(this.data=t,this.mergeRootData(),this.form=e,this.formHelper||(this.formHelper=window.jvbForm),this.formHelper){if(Object.hasOwn(this.data,"fields")&&0!==Object.keys(this.data.fields).length)for(let[t,i]of Object.entries(this.data.fields)){let a=e.querySelector(`[data-field="${t}"]`);a&&this.populateField(a,t,i)}}else requestAnimationFrame((()=>{this.populate(e,t)}))}mergeRootData(){["status","date","modified"].forEach((e=>{this.data.fields[`post_${e}`]=this.data[e]}))}populateField(e,t,i){let a=this.formHelper.getFieldType(e);if(!a||this.isEmptyValue(t)||this.isEmptyValue(i))return;const l={repeater:this.populateRepeater.bind(this),"tag-list":this.populateTagList.bind(this),location:this.populateLocation.bind(this),selector:this.populateTaxonomy.bind(this),user:this.populateUser.bind(this),upload:this.populateUpload.bind(this),set:this.populateMultiValue.bind(this),checkbox:this.populateMultiValue.bind(this),select:this.populateSingleValue.bind(this),radio:this.populateSingleValue.bind(this),"true-false":this.populateBoolean.bind(this),date:this.populateDate.bind(this),time:this.populateDate.bind(this),datetime:this.populateDate.bind(this),number:this.populateNumber.bind(this),textarea:this.populateTextarea.bind(this)};Object.hasOwn(l,a)?l[a](e,t,i):this.populateText(e,t,i)}populateRepeater(e,t,i){if(!i||!Array.isArray(i))return;const a=e.querySelector(".repeater-items");let l=e.querySelector("template")?.className??!1;a&&l&&(window.removeChildren(a),i.forEach(((e,t)=>{e.index=t;const i=this.templates.create(l,e);if(i){for(let[t,a]of Object.entries(e)){if("index"===t)continue;let e=i.querySelector(`[data-field="${t}"]`);e&&this.populateField(e,t,a)}a.append(i)}})))}populateTagList(e,t,i){if(!i||!Array.isArray(i))return;const a=e.querySelector(".tag-items");let l=e.querySelector("template")?.className??!1;a&&l&&(window.removeChildren(a),i.forEach(((i,s)=>{const r=this.templates.create(l,{label:this.getTagLabel(i,e.dataset.tagFormat??"first_field"),fieldName:t,...i});r&&(r.querySelectorAll('input[type="hidden"]').forEach((e=>{const t=e.dataset.field;t&&void 0!==i[t]&&(e.value=i[t])})),a.append(r))})))}getTagLabel(e,t){const i=Object.values(e).filter((e=>!this.isEmptyValue(e)));switch(t){case"first_field":return i[0]??"New Item";case"all_fields":return i.join(", ")||"New Item";default:if(t.includes("{")){let i=t;for(const[t,a]of Object.entries(e))i=i.replace(`{${t}}`,a);return i}return e[t]??i[0]??"New Item"}}populateLocation(e,t,i){["address","lat","lng","street","city","province","postal_code","country"].forEach((t=>{if(Object.hasOwn(i,t)){let a=e.querySelector(`[data-location-field="${t}"]`);a&&(a.value=String(i[t]||""))}}))}populateTaxonomy(e,t,i){let a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector(`input[type="hidden"][name="${t}"]`);l&&(l.value=a.join(","),window.jvbSelector&&requestAnimationFrame((()=>{window.jvbSelector.updateFieldFromInput(l)})))}populateUser(e,t,i){this.populateTaxonomy(e,t,i)}populateUpload(e,t,i){if("timeline"===t||e.dataset.subtype&&"timeline"===e.dataset.subtype)return void this.populateTimelineGallery(e,t,i);if(this.isEmptyValue(i))return;const a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector('input[type="hidden"]');l&&(l.value=a.join(","));const s=e.querySelector(".item-grid");e.querySelector(".file-upload-container").hidden=a.length>0,e.querySelector(".progress")?.remove(),s&&(window.removeChildren(s),a.forEach((e=>{let t=this.data.images[e]??{};t.field={config:{showMeta:!0}},t.id=e,s.append(this.templates.create("uploadItem",t))}))),this.populateUploadMeta(e,t,i)}populateUploadMeta(e,t,i){const a=e.querySelector('[data-field="image_data"]');if(!a)return;let l=this.data.images[i]??!1;if(!l)return;a.dataset.attachmentId=l.id,a.setAttribute("data-ignore","");const s=["image-title","image-alt-text","image-caption"];for(const e of s){const t=a.querySelector(`[data-field="${e}"] input, [data-field="${e}"] textarea`);t&&""!==l[e]&&(t.value=l[e])}}populateTimelineGallery(e,t,i){if(!i||!Array.isArray(i)||0===i.length)return;let a=e.querySelector(".item-grid");if(e.querySelector(".file-upload-container").hidden=i.length>0,a){window.removeChildren(a),e.querySelector(".progress")?.remove();for(let e of i){let t=this.templates.create("timelineItem",e);t&&a.append(t)}}}populateMultiValue(e,t,i){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=i.split(",").map((e=>e.trim()))}Array.isArray(i)||(i=[String(i)]);let a=e.querySelector(`select[name="${t}"]`);if(a&&a.multiple)for(let e of a.options)e.selected=i.includes(e.value);else e.querySelectorAll(`[type="checkbox"][name=${t}]`).forEach((e=>{e.checked=i.includes(e.value)}))}populateSingleValue(e,t,i){i=String(i||"");let a=e.querySelector(`select[name="${t}"]`);if(a)return void(a.value=i);let l=e.querySelector(`[name="${t}"][value="${i}"]`);l&&(l.checked=!0)}populateBoolean(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="checkbox"]`);a&&(a.checked=Boolean(i))}populateDate(e,t,i){const a=e.querySelector(`[name="${t}"], input`);if(a){"object"==typeof i&&Object.hasOwn(i,"date")&&(i=i.date);try{const e=new Date(i);if(!isNaN(e.getTime()))switch(a.type){case"date":a.value=e.toISOString().split("T")[0];break;case"time":a.value=e.toTimeString().slice(0,5);break;case"datetime-local":a.value=e.toISOString().slice(0,16);break;default:a.value=i}}catch(e){a.value=i}}}populateNumber(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="number"]`);a&&(a.value=Number(i)||0)}populateTextarea(e,t,i){let a=e.querySelector("textarea");a.dataset.editor?(a.value=String(i||""),a.dispatchEvent(new Event("change",{bubbles:!0}))):this.populateText(e,t,i)}populateText(e,t,i){let a=e.querySelector(`[name="${t}"], input, textarea`);a&&"file"!==a.type&&(a.value=String(i||""))}getFormHelper(){window.requestAnimationFrame((()=>{this.formHelper=window.jvbForm}))}splitIDs(e){return String(e).split(",").map((e=>parseInt(e.trim()))).filter((e=>!isNaN(e)&&e>0))}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}defineTemplates(){const e=this.templates,t=this;e.define("timelineItem",{refs:{select:'[name="select-item"]',video:"video",file:".select-item span",img:"img",details:"details[data-field]",imgAlt:'[name="image-alt-text"]',imgTitle:'[name="image-title"]',imgDesc:'[name="image-caption"]'},manyRefs:{fields:".field"},setup({el:e,refs:i,manyRefs:a,data:l}){if(e.dataset.itemId=l.id,i.select){let e=i.select.closest(".preview");window.prefixInput(i.select,`${l.id}-`,e)}i.video&&i.video.remove(),i.file&&i.file.remove();let s=t.data.images[l.post_thumbnail]??!1;if(i.img&&s&&(i.img.src=s.medium||s.small||s.large||"",i.img.title=s["image-title"]??"",i.img.alt=s["image-alt-text"]??""),i.details){let e=t.data.images[l.post_thumbnail];i.details.setAttribute("data-ignore",""),i.details.dataset.attachmentId=l.post_thumbnail,Object.hasOwn(e,"image-alt-text")&&i.alt&&(i.alt.value=e["image-alt-text"]),(Object.hasOwn(e,"image-title")||Object.hasOwn(l,"file"))&&i.title&&(i.title.value=e["image-title"]||l.file.name),Object.hasOwn(e,"image-caption")&&i.description&&(i.description.value=e["image-caption"])}if(a.fields)for(let e of a.fields){if("group"===e.dataset.fieldType)continue;if("post_thumbnail"===e.dataset.field){e.remove();continue}let i=e.dataset.field,a=l[i]??"";t.isEmptyValue(a)||t.populateField(e,i,a);const s=e.querySelector('input:not([type="file"])');s&&window.prefixInput(s,`[${l.id}]`,e)}}})}}document.addEventListener("DOMContentLoaded",(function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbPopulate=new e)}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.templates=window.jvbTemplates,this.formHelper=window.jvbForm,this.defineTemplates(),this.data=null,this.form=null}populate(e,t={}){if(this.data=t,this.mergeRootData(),this.form=e,this.formHelper||(this.formHelper=window.jvbForm),this.formHelper){if(Object.hasOwn(this.data,"fields")&&0!==Object.keys(this.data.fields).length)for(let[t,i]of Object.entries(this.data.fields)){let a=e.querySelector(`[data-field="${t}"]`);a&&this.populateField(a,t,i)}}else requestAnimationFrame((()=>{this.populate(e,t)}))}mergeRootData(){["status","date","modified"].forEach((e=>{this.data.fields[`post_${e}`]=this.data[e]}))}populateField(e,t,i){let a=this.formHelper.getFieldType(e);if(!a||this.isEmptyValue(t)||this.isEmptyValue(i))return;const l={repeater:this.populateRepeater.bind(this),"tag-list":this.populateTagList.bind(this),location:this.populateLocation.bind(this),selector:this.populateTaxonomy.bind(this),user:this.populateUser.bind(this),upload:this.populateUpload.bind(this),set:this.populateMultiValue.bind(this),checkbox:this.populateMultiValue.bind(this),select:this.populateSingleValue.bind(this),radio:this.populateSingleValue.bind(this),"true-false":this.populateBoolean.bind(this),date:this.populateDate.bind(this),time:this.populateDate.bind(this),datetime:this.populateDate.bind(this),number:this.populateNumber.bind(this),textarea:this.populateTextarea.bind(this)};Object.hasOwn(l,a)?l[a](e,t,i):this.populateText(e,t,i)}populateRepeater(e,t,i){if(!i||!Array.isArray(i))return;const a=e.querySelector(".repeater-items");let l=e.querySelector("template")?.className??!1;a&&l&&(window.removeChildren(a),i.forEach(((e,t)=>{e.index=t;const i=this.templates.create(l,e);if(i){for(let[t,a]of Object.entries(e)){if("index"===t)continue;let e=i.querySelector(`[data-field="${t}"]`);e&&this.populateField(e,t,a)}a.append(i)}})))}populateTagList(e,t,i){if(!i||!Array.isArray(i))return;const a=e.querySelector(".tag-items");let l=e.querySelector("template")?.className??!1;a&&l&&(window.removeChildren(a),i.forEach(((i,r)=>{const s=this.templates.create(l,{label:this.getTagLabel(i,e.dataset.tagFormat??"first_field"),fieldName:t,...i});s&&(s.querySelectorAll('input[type="hidden"]').forEach((e=>{const t=e.dataset.field;t&&void 0!==i[t]&&(e.value=i[t])})),a.append(s))})))}getTagLabel(e,t){const i=Object.values(e).filter((e=>!this.isEmptyValue(e)));switch(t){case"first_field":return i[0]??"New Item";case"all_fields":return i.join(", ")||"New Item";default:if(t.includes("{")){let i=t;for(const[t,a]of Object.entries(e))i=i.replace(`{${t}}`,a);return i}return e[t]??i[0]??"New Item"}}populateLocation(e,t,i){["address","lat","lng","street","city","province","postal_code","country"].forEach((t=>{if(Object.hasOwn(i,t)){let a=e.querySelector(`[data-location-field="${t}"]`);a&&(a.value=String(i[t]||""))}}))}populateTaxonomy(e,t,i){let a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector(`input[type="hidden"][name="${t}"]`);l&&(l.value=a.join(","),window.jvbSelector&&requestAnimationFrame((()=>{window.jvbSelector.updateFieldFromInput(l)})))}populateUser(e,t,i){this.populateTaxonomy(e,t,i)}populateUpload(e,t,i){if("timeline"===t||e.dataset.subtype&&"timeline"===e.dataset.subtype)return void this.populateTimelineGallery(e,t,i);if(this.isEmptyValue(i))return;const a=this.splitIDs(i);if(0===a.length)return;const l=e.querySelector('input[type="hidden"]');l&&(l.value=a.join(","));const r=e.querySelector(".item-grid");e.querySelector(".file-upload-wrapper").hidden=a.length>0,e.querySelector(".progress")?.remove(),r&&(window.removeChildren(r),a.forEach((e=>{let t=this.data.images[e]??{};t.field={config:{showMeta:!0}},t.id=e,r.append(this.templates.create("uploadItem",t))}))),this.populateUploadMeta(e,t,i)}populateUploadMeta(e,t,i){const a=e.querySelector('[data-field="image_data"]');if(!a)return;let l=this.data.images[i]??!1;if(!l)return;a.dataset.attachmentId=l.id,a.setAttribute("data-ignore","");const r=["image-title","image-alt-text","image-caption"];for(const e of r){const t=a.querySelector(`[data-field="${e}"] input, [data-field="${e}"] textarea`);t&&""!==l[e]&&(t.value=l[e])}}populateTimelineGallery(e,t,i){if(!i||!Array.isArray(i)||0===i.length)return;let a=e.querySelector(".item-grid");if(e.querySelector(".file-upload-wrapper").hidden=i.length>0,a){window.removeChildren(a),e.querySelector(".progress")?.remove();for(let e of i){let t=this.templates.create("timelineItem",e);t&&a.append(t)}}}populateMultiValue(e,t,i){if("string"==typeof i)try{i=JSON.parse(i)}catch(e){i=i.split(",").map((e=>e.trim()))}Array.isArray(i)||(i=[String(i)]);let a=e.querySelector(`select[name="${t}"]`);if(a&&a.multiple)for(let e of a.options)e.selected=i.includes(e.value);else e.querySelectorAll(`[type="checkbox"][name=${t}]`).forEach((e=>{e.checked=i.includes(e.value)}))}populateSingleValue(e,t,i){i=String(i||"");let a=e.querySelector(`select[name="${t}"]`);if(a)return void(a.value=i);let l=e.querySelector(`[name="${t}"][value="${i}"]`);l&&(l.checked=!0)}populateBoolean(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="checkbox"]`);a&&(a.checked=Boolean(i))}populateDate(e,t,i){const a=e.querySelector(`[name="${t}"], input`);if(a){"object"==typeof i&&Object.hasOwn(i,"date")&&(i=i.date);try{const e=new Date(i);if(!isNaN(e.getTime()))switch(a.type){case"date":a.value=e.toISOString().split("T")[0];break;case"time":a.value=e.toTimeString().slice(0,5);break;case"datetime-local":a.value=e.toISOString().slice(0,16);break;default:a.value=i}}catch(e){a.value=i}}}populateNumber(e,t,i){const a=e.querySelector(`[name="${t}"], input[type="number"]`);a&&(a.value=Number(i)||0)}populateTextarea(e,t,i){let a=e.querySelector("textarea");a.dataset.editor?(a.value=String(i||""),a.dispatchEvent(new Event("change",{bubbles:!0}))):this.populateText(e,t,i)}populateText(e,t,i){let a=e.querySelector(`[name="${t}"], input, textarea`);a&&"file"!==a.type&&(a.value=String(i||""))}getFormHelper(){window.requestAnimationFrame((()=>{this.formHelper=window.jvbForm}))}splitIDs(e){return String(e).split(",").map((e=>parseInt(e.trim()))).filter((e=>!isNaN(e)&&e>0))}isEmptyValue(e){return null==e||""===e||(!(!Array.isArray(e)||0!==e.length)||"object"==typeof e&&0===Object.keys(e).length)}defineTemplates(){const e=this.templates,t=this;e.define("timelineItem",{refs:{select:'[name="select-item"]',video:"video",file:".select-item span",img:"img",details:"details[data-field]",imgAlt:'[name="image-alt-text"]',imgTitle:'[name="image-title"]',imgDesc:'[name="image-caption"]'},manyRefs:{fields:".field"},setup({el:e,refs:i,manyRefs:a,data:l}){if(e.dataset.itemId=l.id,i.select){let e=i.select.closest(".preview");window.prefixInput(i.select,`${l.id}-`,e)}i.video&&i.video.remove(),i.file&&i.file.remove();let r=t.data.images[l.post_thumbnail]??!1;if(i.img&&r&&(i.img.src=r.medium||r.small||r.large||"",i.img.title=r["image-title"]??"",i.img.alt=r["image-alt-text"]??""),i.details){let e=t.data.images[l.post_thumbnail];i.details.setAttribute("data-ignore",""),i.details.dataset.attachmentId=l.post_thumbnail,Object.hasOwn(e,"image-alt-text")&&i.alt&&(i.alt.value=e["image-alt-text"]),(Object.hasOwn(e,"image-title")||Object.hasOwn(l,"file"))&&i.title&&(i.title.value=e["image-title"]||l.file.name),Object.hasOwn(e,"image-caption")&&i.description&&(i.description.value=e["image-caption"])}if(a.fields)for(let e of a.fields){if("group"===e.dataset.fieldType)continue;if("post_thumbnail"===e.dataset.field){e.remove();continue}let i=e.dataset.field,a=l[i]??"";t.isEmptyValue(a)||t.populateField(e,i,a);const r=e.querySelector('input:not([type="file"])');r&&window.prefixInput(r,`[${l.id}]`,e)}}})}}document.addEventListener("DOMContentLoaded",(function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbPopulate=new e)}))}))})();
\ No newline at end of file
diff --git a/inc/managers/SEO/schemas/SchemaResolverRegistry.php b/inc/managers/SEO/schemas/SchemaResolverRegistry.php
index fc1dfc9..ccd2281 100644
--- a/inc/managers/SEO/schemas/SchemaResolverRegistry.php
+++ b/inc/managers/SEO/schemas/SchemaResolverRegistry.php
@@ -68,7 +68,8 @@
 
 		$collection = new CollectionPageResolver();
 		$this->register('CollectionPage', $collection);
-		$this->register('FAQPage', $collection);
 		$this->register('DefinedTermSet', $collection);
+
+		$this->register('FAQPage', new FAQPageResolver());
 	}
 }
diff --git a/inc/managers/SEO/schemas/_setup.php b/inc/managers/SEO/schemas/_setup.php
index 6c82f64..23d3e45 100644
--- a/inc/managers/SEO/schemas/_setup.php
+++ b/inc/managers/SEO/schemas/_setup.php
@@ -21,6 +21,7 @@
 require(JVB_DIR . '/inc/managers/SEO/schemas/resolvers/LocalBusinessResolver.php');
 require(JVB_DIR . '/inc/managers/SEO/schemas/resolvers/VisualArtworkResolver.php');
 require(JVB_DIR . '/inc/managers/SEO/schemas/resolvers/PersonResolver.php');
+require(JVB_DIR . '/inc/managers/SEO/schemas/resolvers/FAQPageResolver.php');
 require(JVB_DIR . '/inc/managers/SEO/schemas/resolvers/CollectionPageResolver.php');
 
 require(JVB_DIR . '/inc/managers/SEO/schemas/SchemaResolverRegistry.php');
diff --git a/inc/managers/SEO/schemas/resolvers/FAQPageResolver.php b/inc/managers/SEO/schemas/resolvers/FAQPageResolver.php
new file mode 100644
index 0000000..e09e34d
--- /dev/null
+++ b/inc/managers/SEO/schemas/resolvers/FAQPageResolver.php
@@ -0,0 +1,75 @@
+<?php
+namespace JVBase\managers\SEO\schemas\resolvers;
+
+use JVBase\managers\SEO\schemas\SchemaDefinition;
+use JVBase\meta\Meta;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+/**
+ * Resolver for FAQPage schema.
+ *
+ * Handles two contexts:
+ * - Single post: transforms question/answer into mainEntity Question structure
+ * - Archive/term: delegates to CollectionPageResolver for mainEntity from posts
+ */
+class FAQPageResolver extends BaseResolver
+{
+	private CollectionPageResolver $collectionResolver;
+
+	public function __construct()
+	{
+		$this->collectionResolver = new CollectionPageResolver();
+	}
+
+	public function resolve(SchemaDefinition $definition, ?Meta $meta = null): ?array
+	{
+		// Single FAQ post: restructure question/answer into mainEntity
+		if ($definition->objectType === 'post') {
+			$this->transformQuestionAnswer($definition);
+		}
+
+		return parent::resolve($definition, $meta);
+	}
+
+	public function getAutoFields(SchemaDefinition $definition): array
+	{
+		// Archive/term context: delegate to CollectionPageResolver
+		if (in_array($definition->objectType, ['archive', 'term'])) {
+			return $this->collectionResolver->getAutoFields($definition);
+		}
+
+		return [];
+	}
+
+	/**
+	 * Transform question/answer into proper mainEntity structure.
+	 *
+	 * Schema.org requires FAQPage Q&A nested as:
+	 *   mainEntity: [{ @type: Question, name: ..., acceptedAnswer: { @type: Answer, text: ... } }]
+	 */
+	private function transformQuestionAnswer(SchemaDefinition $definition): void
+	{
+		$question = $definition->config['question'] ?? null;
+		$answer   = $definition->config['answer'] ?? null;
+
+		if (!$question && !$answer) {
+			return;
+		}
+
+		unset($definition->config['question'], $definition->config['answer']);
+
+		$questionEntity = ['@type' => 'Question', 'name' => $question ?? ''];
+
+		if ($answer) {
+			$questionEntity['acceptedAnswer'] = [
+				'@type' => 'Answer',
+				'text'  => $answer,
+			];
+		}
+
+		$definition->config['mainEntity'] = [$questionEntity];
+	}
+}
diff --git a/inc/meta/Form.php b/inc/meta/Form.php
index 2ad8dc9..e5725b0 100644
--- a/inc/meta/Form.php
+++ b/inc/meta/Form.php
@@ -107,6 +107,8 @@
 		$output .= static::buildLabel($name, $config);
 		if (!array_key_exists('skipInput', $config)) {
 			$output .= static::buildInput($content);
+		} else {
+			$output .= $content;
 		}
 
 		$output .= static::buildHint($config);
@@ -570,30 +572,47 @@
 
 	protected static function renderRadio(string $name, mixed $value, array $config): string
 	{
-		$options = $config['options'] ?? [];
+		$options    = $config['options'] ?? [];
+		$inputClass = !empty($config['inputClass']) ? ' class="' . esc_attr($config['inputClass']) . '"' : '';
+		$idPrefix   = $config['idPrefix'] ?? '';
 
 		$radios = sprintf(
 			'<fieldset>
-			<legend>%s%s</legend>',
+        <legend>%s%s</legend>',
 			array_key_exists('label', $config) ? esc_html($config['label']) : 'Select an option',
-			array_key_exists('required', $config) && $config['required']===true ? '<span class="required" aria-label="required">*</span>' : ''
+			array_key_exists('required', $config) && $config['required'] === true
+				? '<span class="required" aria-label="required">*</span>' : ''
 		);
 
-		foreach ($options as $optValue => $optLabel) {
+		foreach ($options as $optValue => $optConfig) {
+			if (is_array($optConfig)) {
+				$optLabel    = $optConfig['label'] ?? $optValue;
+				$optIcon     = $optConfig['icon'] ?? null;
+				$optDisabled = !empty($optConfig['disabled']) ? ' disabled' : '';
+			} else {
+				$optLabel    = $optConfig;
+				$optIcon     = null;
+				$optDisabled = '';
+			}
+
+			$labelContent = $optIcon
+				? jvbDashIcon($optIcon)
+				: '<span>' . esc_html($optLabel) . '</span>';
+
+			$optId = esc_attr($idPrefix . $name . '-' . $optValue);
+
 			$radios .= sprintf(
-				'
-                    <input type="radio" name="%s" id="%s-%s" value="%s"%s />
-				<label class="radio-option" for="%s-%s">
-                    <span>%s</span>
-                </label>',
+				'<input type="radio" name="%s" id="%s" value="%s"%s%s%s />
+            <label class="radio-option" for="%s" title="%s">%s</label>',
 				esc_attr($name),
-				esc_attr($name),
-				$optValue,
+				$optId,
 				esc_attr($optValue),
-				checked($value, $optValue,false),
-				esc_attr($name),
-				$optValue,
-				esc_html($optLabel)
+				checked($value, $optValue, false),
+				$optDisabled,
+				$inputClass,
+				$optId,
+				esc_html($optLabel),
+				$labelContent
 			);
 		}
 
diff --git a/inc/ui/CRUDSkeleton.php b/inc/ui/CRUDSkeleton.php
index e74a010..c32dcce 100644
--- a/inc/ui/CRUDSkeleton.php
+++ b/inc/ui/CRUDSkeleton.php
@@ -1543,15 +1543,9 @@
 			<input type="hidden" name="form-id" value="<?=uniqid('new-')?>" />
 			<input type="hidden" name="content" value="<?=$this->dataType?>" />
 			<div class="fields">
-				<div class="field-group radio-options row" data-field="post_status" data-field-type="radio">
-					<span>Status:</span>
-					<?php
-					$this->getApplicableStatuses('edit');
-					?>
-				</div>
-				<?php if (!$this->userCanPublish) { ?>
-					<p class="description">Your account needs to be verified before you can publish content.</p>
-				<?php }
+				<?php
+				echo Form::render('post_status', '', $this->getStatusFieldConfig('edit-'));
+
 
 				if (!empty($this->sections)) {
 					$tabs = [];
@@ -1620,13 +1614,13 @@
 					$fields = $this->nonTimelineFields;
 				}
 				foreach ($fields as $n => $config) {
+					if (in_array($config['type'], ['taxonomy', 'selector'])) {
+						$config = array_merge($config, $this->taxConfig($config['taxonomy'], $config['label']));
+					}
 					if ($tabs) {
 						$section = (array_key_exists('section', $config)) ? $config['section'] : 'basic';
-						$tabs[$section]['content'] .= Form::render($n,'', $config);
+						$tabs[$section]['content'] .= Form::render($n, '', $config);
 					} else {
-						if (in_array($config['type'], ['taxonomy', 'selector'])) {
-							$config = array_merge($config, $this->taxConfig($config['taxonomy'], $config['label']));
-						}
 						echo Form::render($n, '', $config);
 					}
 				}
@@ -1663,9 +1657,8 @@
 			<p class="hint"><strong>IMPORTANT: </strong> Whatever changes you make here will be applied to all selected <?=$this->plural?>.</p>
 			<div class="fields">
 				<?php
-				$this->getApplicableStatuses('bulk-');
-				?>
-				<?php
+				echo Form::render('post_status', '', $this->getStatusFieldConfig('bulk-'));
+
 				if (!empty($this->taxonomies)) {
 					?>
 					<div class="taxonomies">
@@ -1702,6 +1695,44 @@
 		);
 	}
 
+	protected function getStatusFieldConfig(string $prefix): array
+	{
+		$options = [];
+		foreach ($this->statuses as $status) {
+			if ($status === 'all' || !array_key_exists($status, $this->allowedStatuses)) {
+				continue;
+			}
+			$config = $this->allowedStatuses[$status];
+
+			if (in_array($status, ['future', 'past'])) {
+				if ($status === 'future') {
+					$status = 'publish';
+					$config = ['icon' => 'eye', 'label' => 'Live'];
+				} else {
+					continue;
+				}
+			}
+
+			$options[$status] = [
+				'label'    => $config['label'],
+				'icon'     => $config['icon'],
+				'disabled' => ($status === 'publish' && !$this->userCanPublish),
+			];
+		}
+
+		return [
+			'type'       => 'radio',
+			'label'      => 'Status',
+			'options'    => $options,
+			'inputClass' => 'btn',
+			'idPrefix'   => $prefix,
+			'class'      => 'radio-options row',
+			'hint'       => !$this->userCanPublish
+				? 'Your account needs to be verified before you can publish content.'
+				: '',
+		];
+	}
+
 	protected function getApplicableStatuses(string $prefix) {
 		ob_start();
 		foreach ($this->statuses as $status) {

--
Gitblit v1.10.0