From 77c68ce96a5db9d702c744699e55b6c50c672585 Mon Sep 17 00:00:00 2001 From: Chuck Date: Thu, 8 Jan 2026 13:35:39 -0500 Subject: [PATCH] fix: Reorder array type checks to match template order Fix inconsistent rendering where JavaScript and Jinja template had opposite ordering for array type checks, causing schemas with both x-widget: file-upload AND items.type: object (like static-image) to render differently. Problem: - Template checks file-upload FIRST (to avoid breaking static-image plugin) - JavaScript checked array-of-objects FIRST - Server-rendered forms showed file-upload widget correctly - JS-rendered forms incorrectly displayed array-of-objects table widget Solution: - Reorder JavaScript checks to match template order: 1. Check file-upload widget FIRST 2. Check checkbox-group widget 3. Check custom-feeds widget 4. Check array-of-objects as fallback 5. Regular array input (comma-separated) This ensures consistent rendering between server-rendered and JS-rendered forms for schemas that have both x-widget: file-upload AND items.type: object. --- web_interface/static/v3/plugins_manager.js | 157 ++++++++++++++------- 1 file changed, 105 insertions(+), 52 deletions(-) diff --git a/web_interface/static/v3/plugins_manager.js b/web_interface/static/v3/plugins_manager.js index 5360a8c0..0bd239cc 100644 --- a/web_interface/static/v3/plugins_manager.js +++ b/web_interface/static/v3/plugins_manager.js @@ -2874,57 +2874,26 @@ function generateFieldHtml(key, prop, value, prefix = '') { `; } else if (prop.type === 'array') { - // Check if this is an array of objects FIRST (before other checks) - if (prop.items && prop.items.type === 'object' && prop.items.properties) { - // Array of objects widget (like custom_feeds with name, url, enabled, logo) - console.log(`[DEBUG] ✅ Detected array-of-objects widget for ${fullKey}`); - const fieldId = fullKey.replace(/\./g, '_'); - const itemsSchema = prop.items; - const itemProperties = itemsSchema.properties || {}; - const maxItems = prop.maxItems || 50; - const currentItems = Array.isArray(value) ? value : []; - - html += ` -
-
- `; - - // Render existing items - currentItems.forEach((item, index) => { - html += renderArrayObjectItem(fieldId, fullKey, itemProperties, item, index, itemsSchema); - }); - - html += ` -
- - -
- `; - } else { - // Check if this is a file upload widget - try multiple ways to access x-widget - const hasXWidget = prop.hasOwnProperty('x-widget'); - const xWidgetValue = prop['x-widget']; - const xWidgetValue2 = prop['x-widget'] || prop['x_widget'] || prop.xWidget; - - console.log(`[DEBUG] Array field ${fullKey}:`, { - type: prop.type, - hasItems: !!prop.items, - itemsType: prop.items?.type, - itemsHasProperties: !!prop.items?.properties, - hasXWidget: hasXWidget, - 'x-widget': xWidgetValue, - 'x-widget (alt)': xWidgetValue2, - 'x-upload-config': prop['x-upload-config'], - propKeys: Object.keys(prop), - value: value - }); + // Array - check for file upload widget first (to avoid breaking static-image plugin), + // then checkbox-group, then custom-feeds, then array of objects + const hasXWidget = prop.hasOwnProperty('x-widget'); + const xWidgetValue = prop['x-widget']; + const xWidgetValue2 = prop['x-widget'] || prop['x_widget'] || prop.xWidget; - // Check for file-upload widget - be more defensive + console.log(`[DEBUG] Array field ${fullKey}:`, { + type: prop.type, + hasItems: !!prop.items, + itemsType: prop.items?.type, + itemsHasProperties: !!prop.items?.properties, + hasXWidget: hasXWidget, + 'x-widget': xWidgetValue, + 'x-widget (alt)': xWidgetValue2, + 'x-upload-config': prop['x-upload-config'], + propKeys: Object.keys(prop), + value: value + }); + + // Check for file-upload widget FIRST (to avoid breaking static-image plugin) if (xWidgetValue === 'file-upload' || xWidgetValue2 === 'file-upload') { console.log(`[DEBUG] ✅ Detected file-upload widget for ${fullKey} - rendering upload zone`); const uploadConfig = prop['x-upload-config'] || {}; @@ -3050,9 +3019,93 @@ function generateFieldHtml(key, prop, value, prefix = '') { `; }); html += ``; + } else if (xWidgetValue === 'custom-feeds' || xWidgetValue2 === 'custom-feeds') { + // Custom feeds widget - check schema validation first + const itemsSchema = prop.items || {}; + const itemProperties = itemsSchema.properties || {}; + if (!itemProperties.name || !itemProperties.url) { + // Schema doesn't match expected structure - fallback to regular array input + console.log(`[DEBUG] ⚠️ Custom feeds widget requires 'name' and 'url' properties for ${fullKey}, using regular array input`); + let arrayValue = ''; + if (value === null || value === undefined) { + arrayValue = Array.isArray(prop.default) ? prop.default.join(', ') : ''; + } else if (Array.isArray(value)) { + arrayValue = value.join(', '); + } else { + arrayValue = ''; + } + html += ` + +

Enter values separated by commas

+ `; + } else { + // Custom feeds table interface - widget-specific implementation + // Note: This is handled by the template, but we include it here for consistency + // The template renders the custom feeds table, so JS-rendered forms should match + console.log(`[DEBUG] ✅ Detected custom-feeds widget for ${fullKey} - note: custom feeds table is typically rendered server-side`); + let arrayValue = ''; + if (value === null || value === undefined) { + arrayValue = Array.isArray(prop.default) ? prop.default.join(', ') : ''; + } else if (Array.isArray(value)) { + arrayValue = value.join(', '); + } else { + arrayValue = ''; + } + html += ` + +

Enter values separated by commas (custom feeds table rendered server-side)

+ `; + } + } else if (prop.items && prop.items.type === 'object' && prop.items.properties) { + // Array of objects widget (generic fallback - like custom_feeds with name, url, enabled, logo) + console.log(`[DEBUG] ✅ Detected array-of-objects widget for ${fullKey}`); + const fieldId = fullKey.replace(/\./g, '_'); + const itemsSchema = prop.items; + const itemProperties = itemsSchema.properties || {}; + const maxItems = prop.maxItems || 50; + const currentItems = Array.isArray(value) ? value : []; + + html += ` +
+
+ `; + + // Render existing items + currentItems.forEach((item, index) => { + if (typeof window.renderArrayObjectItem === 'function') { + html += window.renderArrayObjectItem(fieldId, fullKey, itemProperties, item, index, itemsSchema); + } else { + // Fallback: create basic HTML structure + html += `
`; + Object.keys(itemProperties || {}).forEach(propKey => { + const propSchema = itemProperties[propKey]; + const propLabel = propSchema.title || propKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + html += `
`; + if (propSchema.type === 'boolean') { + html += ``; + } else { + html += ``; + } + html += `
`; + }); + html += `
`; + } + }); + + html += ` +
+ + +
+ `; } else { - // Regular array input - console.log(`[DEBUG] ❌ NOT a file upload widget for ${fullKey}, using regular array input`); + // Regular array input (comma-separated) + console.log(`[DEBUG] ❌ No special widget detected for ${fullKey}, using regular array input`); // Handle null/undefined values - use default if available let arrayValue = ''; if (value === null || value === undefined) {