Skip to content

Commit e3b19e4

Browse files
committed
fixes
1 parent 90e3a62 commit e3b19e4

File tree

1 file changed

+84
-6
lines changed

1 file changed

+84
-6
lines changed

botasaurus-controls/src/index.ts

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,13 @@ function extractValueFromObject(item: any): any {
376376
}
377377

378378
/**
379-
* Helper function to extract value from object format for searchMethod-based controls
379+
* Helper function to normalize control values - extracts values from object formats
380+
* and parses JSON strings for jsonTextArea controls
380381
* @param control - The control object
381-
* @param value - The value to extract from
382-
* @returns The extracted value
382+
* @param value - The value to normalize
383+
* @returns The normalized value
383384
*/
384-
function extractSearchMethodValue(control: any, value: any) {
385+
function normalizeControlValue(control: any, value: any) {
385386
// @ts-ignore
386387
if (control.searchMethod && control.type === 'select') {
387388
return extractValueFromObject(value)
@@ -392,6 +393,11 @@ function extractSearchMethodValue(control: any, value: any) {
392393
return value.map(extractValueFromObject)
393394
}
394395
}
396+
// Parse JSON string to object for jsonTextArea
397+
else if (control.type === 'jsonTextArea') {
398+
const { parsed } = parseJSON(value)
399+
return parsed
400+
}
395401
return value
396402
}
397403

@@ -425,6 +431,53 @@ const parseListOfTexts = (value:any)=>{
425431
return value;
426432
}
427433

434+
function parseJSON(value: any): { parsed: any; error: string | null } {
435+
if (typeof value !== 'string') {
436+
return { parsed: value, error: null };
437+
}
438+
439+
const trimmedValue = value.trim();
440+
if (trimmedValue === '') {
441+
return { parsed: null, error: null };
442+
}
443+
444+
try {
445+
const parsed = JSON.parse(trimmedValue);
446+
return { parsed, error: null };
447+
} catch (e: any) {
448+
// Provide helpful error messages based on common JSON mistakes
449+
const errorMessage = e.message || 'Invalid JSON';
450+
451+
// Extract position info if available
452+
const positionMatch = errorMessage.match(/position\s+(\d+)/i);
453+
const position = positionMatch ? parseInt(positionMatch[1], 10) : null;
454+
455+
let helpfulMessage = 'Invalid JSON: ';
456+
457+
// Check for common mistakes
458+
if (trimmedValue.includes("'") && !trimmedValue.includes('"')) {
459+
helpfulMessage += "Use double quotes (\") instead of single quotes (') for strings.";
460+
} else if (/,\s*[}\]]/.test(trimmedValue)) {
461+
helpfulMessage += "Trailing comma found. Remove the comma before the closing bracket.";
462+
} else if (errorMessage.includes('Unexpected token')) {
463+
if (position !== null) {
464+
const contextStart = Math.max(0, position - 10);
465+
const contextEnd = Math.min(trimmedValue.length, position + 10);
466+
const context = trimmedValue.substring(contextStart, contextEnd);
467+
helpfulMessage += `Unexpected character near position ${position}: "...${context}..."`;
468+
} else {
469+
helpfulMessage += errorMessage;
470+
}
471+
} else if (errorMessage.includes('Unexpected end')) {
472+
helpfulMessage += "JSON is incomplete. Check for missing closing brackets or quotes.";
473+
} else {
474+
helpfulMessage += errorMessage;
475+
}
476+
477+
return { parsed: null, error: helpfulMessage };
478+
}
479+
}
480+
428481
class Controls {
429482
private isSectionControl = false;
430483

@@ -605,6 +658,10 @@ class Controls {
605658
return this.add<string>(id, "textarea", { defaultValue: "", ...props })
606659
}
607660

661+
jsonTextArea(id: string, props: TextControlInput<string | object> = {}) {
662+
return this.add<string | object>(id, "jsonTextArea", { defaultValue: "", ...props })
663+
}
664+
608665
link(id: string, props: LinkControlInput<string> = {}) {
609666
return this.add<string>(id, "link", { defaultValue: "", ...props })
610667
}
@@ -781,7 +838,7 @@ private parse(data: any) {
781838
const controlId = control.id
782839
let value = data.hasOwnProperty(controlId) ? data[controlId] : null
783840

784-
value = extractSearchMethodValue(control, value)
841+
value = normalizeControlValue(control, value)
785842

786843
mergedData[controlId] = value
787844
})
@@ -791,7 +848,7 @@ private parse(data: any) {
791848
const controlId = control.id
792849
let value = data.hasOwnProperty(controlId) ? data[controlId] : defaultData[controlId]
793850

794-
value = extractSearchMethodValue(control, value)
851+
value = normalizeControlValue(control, value)
795852

796853
mergedData[controlId] = value
797854
})
@@ -830,6 +887,12 @@ private parse(data: any) {
830887
value = value.trim()
831888
}
832889
}
890+
else if (control.type === "jsonTextArea") {
891+
// Only trim if it's a string, objects are already parsed
892+
if (typeof value === "string" && canTrim(control)) {
893+
value = value.trim()
894+
}
895+
}
833896
else if (control.type === "listOfTexts") {
834897
value = value.filter(isNotEmpty)
835898
// @ts-ignore
@@ -930,6 +993,17 @@ private parse(data: any) {
930993
)
931994
}
932995

996+
// JSON validation for jsonTextArea (only validate if value is a string)
997+
if (
998+
!errorMessages.length && type === "jsonTextArea" &&
999+
isNotEmpty(value)
1000+
) {
1001+
const { error } = parseJSON(value)
1002+
if (error) {
1003+
errorMessages.push(error)
1004+
}
1005+
}
1006+
9331007
if (
9341008
!errorMessages.length && type === "listOfLinks"
9351009
) {
@@ -1044,6 +1118,10 @@ private parse(data: any) {
10441118
if (typeof value !== "string")
10451119
return "This field must be of type string."
10461120
break
1121+
case "jsonTextArea":
1122+
1123+
// jsonTextArea can be a string or object, or anything
1124+
break
10471125
case "number":
10481126
if (typeof value !== "number" && value !== null)
10491127
return "This field must be of type number or null."

0 commit comments

Comments
 (0)