/** * Field and schema manipulation helpers */ export const LIST_OPTION_ALIAS_KEYS = ["options", "optionsList", "optionList", "choices", "values", "items"]; export const optionEntryToLine = (entry) => { if (entry == null) { return null; } if (typeof entry === "string") { const trimmed = entry.trim(); if (!trimmed) return null; if (trimmed.includes("|")) return trimmed.replace(/\r/g, ""); return `${trimmed}|${trimmed}`; } if (Array.isArray(entry)) { const [valueRaw, labelRaw] = entry; const value = valueRaw ?? labelRaw; const label = labelRaw ?? valueRaw; if (value == null && label == null) return null; const valueStr = `${value ?? ""}`.trim(); const labelStr = `${label ?? valueStr}`.trim(); if (!valueStr) return null; return `${valueStr}|${labelStr || valueStr}`; } if (typeof entry === "object") { const value = entry.value ?? entry.id ?? entry.key ?? entry.slug ?? entry.code ?? entry.name ?? entry.label ?? entry.text; const label = entry.label ?? entry.text ?? entry.name ?? entry.title ?? value; if (value == null && label == null) return null; const valueStr = `${value ?? ""}`.trim(); const labelStr = `${label ?? valueStr}`.trim(); if (!valueStr) return null; return `${valueStr}|${labelStr || valueStr}`; } return null; }; export const buildOptionsTextFromInput = (input) => { if (input == null) { return ""; } if (Array.isArray(input)) { return input.map(optionEntryToLine).filter(Boolean).join("\n"); } if (typeof input === "object") { return Object.entries(input) .map(([value, label]) => optionEntryToLine({ value, label })) .filter(Boolean) .join("\n"); } if (typeof input === "string") { const trimmed = input.trim(); if (!trimmed) { return ""; } if (trimmed.includes("|")) { return trimmed .replace(/\r/g, "") .split("\n") .map((line) => line.trim()) .filter(Boolean) .join("\n"); } const hasNewLines = /\r|\n/.test(trimmed); const separator = hasNewLines ? /\r?\n/ : /,/; return trimmed .split(separator) .map((token) => token.trim()) .filter(Boolean) .map((token) => `${token}|${token}`) .join("\n"); } return ""; }; export const normalizeListFieldDefinition = (field = {}) => { if (!field || field.type !== "list") { return field; } if (!field.listType) { field.listType = "pulldown"; } if (!field.optionsType) { field.optionsType = "text"; } if (field.optionsType === "text") { let source = field.optionsText; if (Array.isArray(source) || (source && typeof source === "object" && !Array.isArray(source))) { field.optionsText = buildOptionsTextFromInput(source); } else { let aliasValue; for (const aliasKey of LIST_OPTION_ALIAS_KEYS) { if (field[aliasKey] != null) { aliasValue = field[aliasKey]; delete field[aliasKey]; break; } } if (aliasValue != null) { field.optionsText = buildOptionsTextFromInput(aliasValue); } else if (typeof field.optionsText === "string") { field.optionsText = buildOptionsTextFromInput(field.optionsText); } else { field.optionsText = field.optionsText ?? ""; } } } else { // Ensure plain text payload for non-text option sources. if (field.optionsText && typeof field.optionsText !== "string") { field.optionsText = ""; } for (const aliasKey of LIST_OPTION_ALIAS_KEYS) { if (field[aliasKey] != null) { delete field[aliasKey]; } } } return field; }; export const normalizeSchemaForSave = (schema = {}) => { if (!schema || typeof schema !== "object") { return schema; } if (schema.schema && typeof schema.schema === "object") { const normalized = {}; for (const [fieldName, fieldDefinition] of Object.entries(schema.schema)) { if (!fieldDefinition || typeof fieldDefinition !== "object") { normalized[fieldName] = fieldDefinition; continue; } const clonedDefinition = { ...fieldDefinition }; if (clonedDefinition.type === "list") { normalized[fieldName] = normalizeListFieldDefinition(clonedDefinition); } else { normalized[fieldName] = clonedDefinition; } } schema.schema = normalized; } return schema; }; export const mergeTableSchemas = (currentTable, incomingSchema = {}) => { const merged = { ...currentTable, schema: { ...(currentTable?.schema || {}) }, schemaInfo: { ...(currentTable?.schemaInfo || {}) }, }; if (!incomingSchema || typeof incomingSchema !== "object") { return merged; } for (const [key, value] of Object.entries(incomingSchema)) { if (key === "schema" && value && typeof value === "object") { merged.schema = { ...(currentTable?.schema || {}), ...value }; } else if (key === "schemaInfo" && value && typeof value === "object") { merged.schemaInfo = { ...(currentTable?.schemaInfo || {}), ...value }; } else { merged[key] = value; } } return merged; };