diff --git a/test-fixtures/multiline.yml b/test-fixtures/multiline.yml
new file mode 100644
index 0000000..21b55ce
--- /dev/null
+++ b/test-fixtures/multiline.yml
@@ -0,0 +1,31 @@
+- keyname: example_default
+ field_type: code
+ name: Example Default
+ default: |
+ {
+ "Example": "Replace"
+ }
+ rows: 8
+- keyname: example_help
+ field_type: code
+ name: Example Help
+ help_text: |
+ {
+ "Example": "Replace"
+ }
+- keyname: example_placeholder
+ field_type: text
+ name: Example Placeholder
+ placeholder: |
+ {
+ "Place": "Holder"
+ }
+- keyname: example_desc
+ field_type: code
+ name: Example Desc
+ description: |
+ hello
+ world
+ description-es: |
+ hola
+ mundo
\ No newline at end of file
diff --git a/tests/pipe-symbol.test.js b/tests/pipe-symbol.test.js
new file mode 100644
index 0000000..27b1a45
--- /dev/null
+++ b/tests/pipe-symbol.test.js
@@ -0,0 +1,600 @@
+/**
+ * Test Suite for TRMNL Form Builder - Pipe Symbol YAML Support
+ *
+ * Tests validation of the pipe symbol (|) for multiline string values in YAML,
+ * particularly for the 'code' field_type with default values.
+ */
+
+import { describe, test, expect, beforeEach, afterEach } from 'vitest';
+import { readFileSync } from 'fs'
+import { join } from 'path'
+import '../trmnl-form-builder.js';
+
+describe('TRMLYamlForm - Pipe Symbol YAML Support', () => {
+ let formBuilder;
+
+ beforeEach(() => {
+ // Create a fresh instance of the form builder
+ formBuilder = document.createElement('trmnl-form-builder');
+ document.body.appendChild(formBuilder);
+ });
+
+ afterEach(() => {
+ // Clean up
+ document.body.removeChild(formBuilder);
+ });
+
+ describe('parseYaml with pipe symbol multiline strings', () => {
+ test('should parse code field with pipe symbol multiline default value', () => {
+ const yamlInput = `- keyname: example_field
+ field_type: code
+ rows: 8
+ name: Example Field
+ default: |
+ {
+ "Example": "Replace"
+ }`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].keyname).toBe('example_field');
+ expect(result[0].field_type).toBe('code');
+ expect(result[0].rows).toBe(8);
+ expect(result[0].name).toBe('Example Field');
+
+ // The default value should preserve the multiline formatting
+ const expectedDefault = '{\n "Example": "Replace" \n}';
+ expect(result[0].default).toBe(expectedDefault);
+ });
+
+ test('should parse multiline description field', () => {
+ const yamlInput = `- keyname: desc_field
+ field_type: code
+ name: Description Field
+ description: |
+ hello
+ world`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].description).toBe('hello\nworld');
+ });
+
+ test('should parse multiline help_text field', () => {
+ const yamlInput = `- keyname: help_field
+ field_type: code
+ name: Help Field
+ help_text: |
+ {
+ "Example": "Replace"
+ }`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].help_text).toBe('{\n "Example": "Replace" \n}');
+ });
+
+ test('should parse multiline placeholder field', () => {
+ const yamlInput = `- keyname: placeholder_field
+ field_type: text
+ name: Placeholder Field
+ placeholder: |
+ {
+ "Place": "Holder"
+ }`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].placeholder).toBe('{\n "Place": "Holder" \n}');
+ });
+
+ test('should parse multiline i18n description fields', () => {
+ const yamlInput = `- keyname: i18n_field
+ field_type: code
+ name: i18n Field
+ description: |
+ hello
+ world
+ description-es: |
+ hola
+ mundo
+ description-fr: |
+ bonjour
+ monde`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].description).toBe('hello\nworld');
+ expect(result[0]['description-es']).toBe('hola\nmundo');
+ expect(result[0]['description-fr']).toBe('bonjour\nmonde');
+ });
+
+ test('should parse the multiline.yml test fixture', async () => {
+ // Read the actual fixture file
+ const yamlInput = readFileSync(
+ join(process.cwd(), 'test-fixtures', 'multiline.yml'),
+ 'utf-8'
+ )
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(4);
+
+ // Field 1: example_default
+ expect(result[0].keyname).toBe('example_default');
+ expect(result[0].default).toBe('{\n "Example": "Replace" \n}');
+ expect(result[0].rows).toBe(8);
+
+ // Field 2: example_help
+ expect(result[1].keyname).toBe('example_help');
+ expect(result[1].help_text).toBe('{\n "Example": "Replace" \n}');
+
+ // Field 3: example_placeholder
+ expect(result[2].keyname).toBe('example_placeholder');
+ expect(result[2].placeholder).toBe('{\n "Place": "Holder" \n}');
+
+ // Field 4: example_desc
+ expect(result[3].keyname).toBe('example_desc');
+ expect(result[3].description).toBe('hello\nworld');
+ expect(result[3]['description-es']).toBe('hola\nmundo');
+ });
+
+ test('should handle multiple multiline fields in same field definition', () => {
+ const yamlInput = `- keyname: multi_multiline
+ field_type: text
+ name: Multiple Multiline
+ description: |
+ First multiline
+ description
+ help_text: |
+ Second multiline
+ help text
+ placeholder: |
+ Third multiline
+ placeholder
+ default: |
+ Fourth multiline
+ default`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].description).toBe('First multiline\ndescription');
+ expect(result[0].help_text).toBe('Second multiline\nhelp text');
+ expect(result[0].placeholder).toBe('Third multiline\nplaceholder');
+ expect(result[0].default).toBe('Fourth multiline\ndefault');
+ });
+
+ test('should parse multiple code fields with pipe symbol defaults', () => {
+ const yamlInput = `- keyname: json_config
+ field_type: code
+ rows: 5
+ name: JSON Configuration
+ default: |
+ {
+ "key": "value"
+ }
+- keyname: html_template
+ field_type: code
+ rows: 10
+ name: HTML Template
+ default: |
+
+
Title
+ `;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(2);
+
+ // First field
+ expect(result[0].keyname).toBe('json_config');
+ expect(result[0].default).toBe('{\n "key": "value"\n}');
+
+ // Second field
+ expect(result[1].keyname).toBe('html_template');
+ expect(result[1].default).toBe('\n
Title
\n');
+ });
+
+ test('should handle pipe symbol with various indentation levels', () => {
+ const yamlInput = `- keyname: nested_example
+ field_type: code
+ name: Nested Example
+ default: |
+ function example() {
+ if (true) {
+ console.log("nested");
+ }
+ }`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].default).toContain('function example()');
+ expect(result[0].default).toContain(' if (true)');
+ expect(result[0].default).toContain(' console.log("nested")');
+ });
+
+ test('should handle empty lines within pipe symbol content', () => {
+ const yamlInput = `- keyname: code_with_blank_lines
+ field_type: code
+ name: Code With Blanks
+ default: |
+ line 1
+
+ line 3
+
+ line 5`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ const lines = result[0].default.split('\n');
+ expect(lines).toHaveLength(5);
+ expect(lines[0]).toBe('line 1');
+ expect(lines[1]).toBe('');
+ expect(lines[2]).toBe('line 3');
+ expect(lines[3]).toBe('');
+ expect(lines[4]).toBe('line 5');
+ });
+
+ test('should handle pipe symbol for text field type', () => {
+ const yamlInput = `- keyname: multiline_text
+ field_type: text
+ name: Multiline Text
+ default: |
+ This is line 1
+ This is line 2
+ This is line 3`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].field_type).toBe('text');
+ expect(result[0].default).toBe('This is line 1\nThis is line 2\nThis is line 3');
+ });
+
+ test('should preserve special characters in pipe symbol content', () => {
+ const yamlInput = `- keyname: special_chars
+ field_type: code
+ name: Special Characters
+ default: |
+ {"key": "value with 'quotes' and \\"escapes\\""}
+
+ symbols: @ # $ % & * ( )`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].default).toContain('\'quotes\'');
+ expect(result[0].default).toContain('');
+ expect(result[0].default).toContain('@ # $ % & *');
+ });
+
+ test('should handle pipe symbol mixed with other field properties', () => {
+ const yamlInput = `- keyname: mixed_field
+ field_type: code
+ rows: 12
+ name: Mixed Properties
+ help_text: This is a helpful description
+ optional: true
+ default: |
+ // Default code here
+ const x = 1;
+ placeholder: Enter your code here`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ expect(result[0].keyname).toBe('mixed_field');
+ expect(result[0].rows).toBe(12);
+ expect(result[0].optional).toBe(true);
+ expect(result[0].default).toBe('// Default code here\nconst x = 1;');
+ expect(result[0].placeholder).toBe('Enter your code here');
+ });
+
+ test('should handle pipe symbol with trailing spaces', () => {
+ const yamlInput = `- keyname: trailing_spaces
+ field_type: code
+ name: Trailing Spaces
+ default: |
+ line with trailing space
+ another line`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(1);
+ // Trailing spaces should be preserved in the multiline string
+ expect(result[0].default).toBe('line with trailing space \nanother line');
+ });
+
+ test('should differentiate between pipe symbol and regular single-line values', () => {
+ const yamlInput = `- keyname: single_line
+ field_type: string
+ name: Single Line
+ default: simple value
+- keyname: multi_line
+ field_type: code
+ name: Multi Line
+ default: |
+ multi
+ line
+ value`;
+
+ const result = formBuilder.parseYaml(yamlInput);
+
+ expect(result).toHaveLength(2);
+ expect(result[0].default).toBe('simple value');
+ expect(result[1].default).toBe('multi\nline\nvalue');
+ });
+ });
+
+ describe('generateYaml with pipe symbol output', () => {
+ test('should generate YAML with pipe symbol for multiline code defaults', () => {
+ const fields = [{
+ id: 'field_1',
+ keyname: 'example_field',
+ field_type: 'code',
+ rows: 8,
+ name: 'Example Field',
+ default: '{\n "Example": "Replace"\n}'
+ }];
+
+ formBuilder.setFields(fields);
+ const yamlOutput = formBuilder.getYaml();
+
+ expect(yamlOutput).toContain('- keyname: example_field');
+ expect(yamlOutput).toContain('field_type: code');
+ expect(yamlOutput).toContain('default: |');
+ expect(yamlOutput).toContain(' {');
+ expect(yamlOutput).toContain(' "Example": "Replace"');
+ expect(yamlOutput).toContain(' }');
+ });
+
+ test('should generate YAML with pipe symbol for multiline description', () => {
+ const fields = [{
+ id: 'field_1',
+ keyname: 'desc_field',
+ field_type: 'string',
+ name: 'Description Field',
+ description: 'hello\nworld'
+ }];
+
+ formBuilder.setFields(fields);
+ const yamlOutput = formBuilder.getYaml();
+
+ expect(yamlOutput).toContain('description: |');
+ expect(yamlOutput).toContain(' hello');
+ expect(yamlOutput).toContain(' world');
+ });
+
+ test('should generate YAML with pipe symbol for multiline help_text', () => {
+ const fields = [{
+ id: 'field_1',
+ keyname: 'help_field',
+ field_type: 'string',
+ name: 'Help Field',
+ help_text: 'Line 1\nLine 2\nLine 3'
+ }];
+
+ formBuilder.setFields(fields);
+ const yamlOutput = formBuilder.getYaml();
+
+ expect(yamlOutput).toContain('help_text: |');
+ expect(yamlOutput).toContain(' Line 1');
+ expect(yamlOutput).toContain(' Line 2');
+ expect(yamlOutput).toContain(' Line 3');
+ });
+
+ test('should generate YAML with pipe symbol for multiline placeholder', () => {
+ const fields = [{
+ id: 'field_1',
+ keyname: 'placeholder_field',
+ field_type: 'text',
+ name: 'Placeholder Field',
+ placeholder: 'First line\nSecond line'
+ }];
+
+ formBuilder.setFields(fields);
+ const yamlOutput = formBuilder.getYaml();
+
+ expect(yamlOutput).toContain('placeholder: |');
+ expect(yamlOutput).toContain(' First line');
+ expect(yamlOutput).toContain(' Second line');
+ });
+
+ test('should generate YAML with pipe symbol for i18n descriptions', () => {
+ const fields = [{
+ id: 'field_1',
+ keyname: 'i18n_field',
+ field_type: 'string',
+ name: 'i18n Field',
+ description: 'hello\nworld',
+ 'description-es': 'hola\nmundo',
+ 'description-fr': 'bonjour\nmonde'
+ }];
+
+ formBuilder.setFields(fields);
+ const yamlOutput = formBuilder.getYaml();
+
+ expect(yamlOutput).toContain('description: |');
+ expect(yamlOutput).toContain(' hello');
+ expect(yamlOutput).toContain(' world');
+ expect(yamlOutput).toContain('description-es: |');
+ expect(yamlOutput).toContain(' hola');
+ expect(yamlOutput).toContain(' mundo');
+ expect(yamlOutput).toContain('description-fr: |');
+ expect(yamlOutput).toContain(' bonjour');
+ expect(yamlOutput).toContain(' monde');
+ });
+
+ test('should not use pipe symbol for single-line defaults', () => {
+ const fields = [{
+ id: 'field_1',
+ keyname: 'simple_field',
+ field_type: 'string',
+ name: 'Simple Field',
+ default: 'single line value'
+ }];
+
+ formBuilder.setFields(fields);
+ const yamlOutput = formBuilder.getYaml();
+
+ expect(yamlOutput).toContain('default: single line value');
+ expect(yamlOutput).not.toContain('default: |');
+ });
+ });
+
+ describe('round-trip parsing (parse -> generate -> parse)', () => {
+ test('should maintain pipe symbol multiline values through round-trip', () => {
+ const originalYaml = `- keyname: round_trip_test
+ field_type: code
+ rows: 6
+ name: Round Trip Test
+ default: |
+ {
+ "test": "value",
+ "number": 42
+ }`;
+
+ // Parse the YAML
+ const parsed = formBuilder.parseYaml(originalYaml);
+
+ // Set fields and generate new YAML
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+
+ // Parse the generated YAML
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ // The default value should remain the same
+ expect(reparsed[0].default).toBe(parsed[0].default);
+ expect(reparsed[0].default).toContain('\n');
+ expect(reparsed[0].default).toContain('"test": "value"');
+ });
+
+ test('should maintain multiline description through round-trip', () => {
+ const originalYaml = `- keyname: desc_test
+ field_type: string
+ name: Description Test
+ description: |
+ Line one
+ Line two
+ Line three`;
+
+ const parsed = formBuilder.parseYaml(originalYaml);
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ expect(reparsed[0].description).toBe(parsed[0].description);
+ expect(reparsed[0].description).toBe('Line one\nLine two\nLine three');
+ });
+
+ test('should maintain multiline help_text through round-trip', () => {
+ const originalYaml = `- keyname: help_test
+ field_type: string
+ name: Help Test
+ help_text: |
+ Help line 1
+ Help line 2`;
+
+ const parsed = formBuilder.parseYaml(originalYaml);
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ expect(reparsed[0].help_text).toBe(parsed[0].help_text);
+ expect(reparsed[0].help_text).toBe('Help line 1\nHelp line 2');
+ });
+
+ test('should maintain multiline placeholder through round-trip', () => {
+ const originalYaml = `- keyname: placeholder_test
+ field_type: text
+ name: Placeholder Test
+ placeholder: |
+ Placeholder line 1
+ Placeholder line 2`;
+
+ const parsed = formBuilder.parseYaml(originalYaml);
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ expect(reparsed[0].placeholder).toBe(parsed[0].placeholder);
+ expect(reparsed[0].placeholder).toBe('Placeholder line 1\nPlaceholder line 2');
+ });
+
+ test('should maintain multiline i18n descriptions through round-trip', () => {
+ const originalYaml = `- keyname: i18n_test
+ field_type: string
+ name: i18n Test
+ description: |
+ English line 1
+ English line 2
+ description-es: |
+ Spanish line 1
+ Spanish line 2
+ description-fr: |
+ French line 1
+ French line 2`;
+
+ const parsed = formBuilder.parseYaml(originalYaml);
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ expect(reparsed[0].description).toBe(parsed[0].description);
+ expect(reparsed[0]['description-es']).toBe(parsed[0]['description-es']);
+ expect(reparsed[0]['description-fr']).toBe(parsed[0]['description-fr']);
+ });
+
+ test('should maintain multiline.yml fixture through round-trip', async () => {
+ // Read the actual fixture file
+ const originalYaml = readFileSync(
+ join(process.cwd(), 'test-fixtures', 'multiline.yml'),
+ 'utf-8'
+ )
+ const parsed = formBuilder.parseYaml(originalYaml);
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ // Verify all fields maintained their multiline values
+ expect(reparsed[0].default).toBe(parsed[0].default);
+ expect(reparsed[1].help_text).toBe(parsed[1].help_text);
+ expect(reparsed[2].placeholder).toBe(parsed[2].placeholder);
+ expect(reparsed[3].description).toBe(parsed[3].description);
+ expect(reparsed[3]['description-es']).toBe(parsed[3]['description-es']);
+ });
+
+ test('should handle mixed single-line and multiline fields in round-trip', () => {
+ const originalYaml = `- keyname: mixed_test
+ field_type: text
+ name: Mixed Test
+ description: single line description
+ help_text: |
+ multiline
+ help text
+ placeholder: single line placeholder
+ default: |
+ multiline
+ default`;
+
+ const parsed = formBuilder.parseYaml(originalYaml);
+ formBuilder.setFields(parsed);
+ const generatedYaml = formBuilder.getYaml();
+ const reparsed = formBuilder.parseYaml(generatedYaml);
+
+ expect(reparsed[0].description).toBe('single line description');
+ expect(reparsed[0].help_text).toBe('multiline\nhelp text');
+ expect(reparsed[0].placeholder).toBe('single line placeholder');
+ expect(reparsed[0].default).toBe('multiline\ndefault');
+ });
+ });
+});
\ No newline at end of file
diff --git a/trmnl-form-builder.js b/trmnl-form-builder.js
index a9cff0a..3d8f6c8 100644
--- a/trmnl-form-builder.js
+++ b/trmnl-form-builder.js
@@ -1430,28 +1430,43 @@ class TRMLYamlForm extends HTMLElement {
// --- Special handling for 'default' field ---
if (key === 'default') {
- const hasDefault = field.hasOwnProperty('default');
- const defaultValue = hasDefault ? (field.default || '') : '';
-
- return `
- `;
- }
-
- // Default: Text, Number, URL inputs
- return `
+ const hasDefault = field.hasOwnProperty('default');
+ const defaultValue = hasDefault ? (field.default || '') : '';
+
+ // Check if parent field type should use textarea
+ const shouldUseTextarea = field.field_type === 'text' || field.field_type === 'code';
+
+ const inputElement = shouldUseTextarea
+ ? ``
+ : ``;
+
+ return `
`;
+ }
+
+
+ // Default: Text, Number, URL inputs
+ // Check if this is 'placeholder' and parent field type should use textarea
+ const shouldUseTextarea = (key === 'placeholder') && (field.field_type === 'text' || field.field_type === 'code');
+
+ const inputElement = shouldUseTextarea
+ ? ``
+ : ``;
+
+ return `
+ `;
}
@@ -1725,21 +1740,50 @@ class TRMLYamlForm extends HTMLElement {
lines.push(` name: ${this.escapeYaml(field.name)}`);
if (field.description) {
+ const formatted = this.formatYamlValue(field.description);
+ if (formatted.includes('\n')) {
+ lines.push(` description: |`);
+ field.description.split('\n').forEach(line => {
+ lines.push(` ${line}`);
+ });
+ } else {
lines.push(` description: ${this.escapeYaml(field.description)}`);
+ }
}
-
+
if (field.help_text) {
+ if (typeof field.help_text === 'string' && field.help_text.includes('\n')) {
+ lines.push(` help_text: |`);
+ field.help_text.split('\n').forEach(line => {
+ lines.push(` ${line}`);
+ });
+ } else {
lines.push(` help_text: ${this.escapeYaml(field.help_text)}`);
+ }
}
-
+
if (field.placeholder) {
+ if (typeof field.placeholder === 'string' && field.placeholder.includes('\n')) {
+ lines.push(` placeholder: |`);
+ field.placeholder.split('\n').forEach(line => {
+ lines.push(` ${line}`);
+ });
+ } else {
lines.push(` placeholder: ${this.escapeYaml(field.placeholder)}`);
+ }
}
-
+
if (field.hasOwnProperty('default')) {
- const quoteLiterals = (field.field_type == 'boolean');
- lines.push(` default: ${this.escapeYaml(field.default, quoteLiterals)}`);
- }
+ if (typeof field.default === 'string' && field.default.includes('\n')) {
+ lines.push(` default: |`);
+ field.default.split('\n').forEach(line => {
+ lines.push(` ${line}`);
+ });
+ } else {
+ const quoteLiterals = (field.field_type == 'boolean');
+ lines.push(` default: ${this.escapeYaml(field.default, quoteLiterals)}`);
+ }
+ }
if (field.optional) {
lines.push(` optional: true`);
@@ -1844,11 +1888,17 @@ if (field.max !== undefined) {
}
// Capture any i18n description keys
- Object.keys(field)
- .filter(k => k.startsWith('description-'))
- .sort()
- .forEach(k => {
- lines.push(` ${k}: ${this.escapeYaml(field[k])}`);
+ Object.keys(field).forEach(k => {
+ if (k.startsWith('description-')) {
+ if (typeof field[k] === 'string' && field[k].includes('\n')) {
+ lines.push(` ${k}: |`);
+ field[k].split('\n').forEach(line => {
+ lines.push(` ${line}`);
+ });
+ } else {
+ lines.push(` ${k}: "${field[k]}"`);
+ }
+ }
});
});
@@ -1879,6 +1929,20 @@ if (field.max !== undefined) {
return value;
}
+ formatYamlValue(value, indent = ' ') {
+ // Check if value contains newlines (multiline)
+ if (typeof value === 'string' && value.includes('\n')) {
+ const lines = [`${indent}|`];
+ // Split and indent each line
+ value.split('\n').forEach(line => {
+ lines.push(`${indent} ${line}`);
+ });
+ return lines.join('\n');
+ } else {
+ return `${indent}${this.escapeYaml(value)}`;
+ }
+ }
+
reorderFields(draggedId, targetId) {
const draggedIndex = this.fields.findIndex(f => f.id === draggedId);
const targetIndex = this.fields.findIndex(f => f.id === targetId);
@@ -2108,9 +2172,41 @@ if (field.max !== undefined) {
let currentArrayKey = null;
let currentCondition = null;
let currentNestedKey = null; // Helper to track if we are in hidden/required
+ let multilineKey = null; // Track which property is receiving multiline content
+ let multilineContent = []; // Collect multiline content
+ let multilineIndent = 0; // Track base indentation of multiline content
- lines.forEach(line => {
- // Skip comments and empty lines
+
+ lines.forEach((line, index) => {
+ // Handle multiline content (pipe symbol continuation)
+ if (multilineKey) {
+ // Check if this line is part of the multiline content
+ // It should be indented more than the property declaration
+ const lineIndent = line.search(/\S/); // Find first non-whitespace character
+
+ // If line is empty, add to multiline content
+ if (line.trim() === '') {
+ multilineContent.push('');
+ return;
+ }
+
+ // If line has proper indentation (more than property line), it's part of multiline
+ if (lineIndent > multilineIndent) {
+ // Remove the base indentation from the content line
+ const contentLine = line.substring(multilineIndent + 2); // +2 for the base indentation after |
+ multilineContent.push(contentLine);
+ return;
+ } else {
+ // End of multiline content - save it and continue processing this line
+ currentField[multilineKey] = multilineContent.join('\n');
+ multilineKey = null;
+ multilineContent = [];
+ multilineIndent = 0;
+ // Don't return - fall through to process this line normally
+ }
+ }
+
+ // Skip comments and empty lines (when not in multiline mode)
if (line.trim().startsWith('#') || !line.trim()) {
return;
}
@@ -2131,11 +2227,19 @@ if (field.max !== undefined) {
// 2. Field properties (2 spaces indentation)
else if (currentField && line.match(/^\s{2}[a-zA-Z_-]+:/)) {
- const match = line.match(/^\s{2}([a-zA-Z_-]+):\s*(.*)$/);
+ const match = line.match(/^\s{2}([a-zA-Z_-]+):\s*(.*)$/);
if (match) {
const key = match[1];
const value = match[2].trim();
+ // Check for pipe symbol (multiline string indicator)
+ if (value === '|') {
+ multilineKey = key;
+ multilineContent = [];
+ multilineIndent = 2; // Base indentation for field properties
+ return;
+ }
+
// Handle array start (value is empty)
if (!value || value === '') {
currentArrayKey = key;
@@ -2217,6 +2321,11 @@ if (field.max !== undefined) {
}
});
+ // Save any remaining multiline content
+ if (multilineKey && currentField) {
+ currentField[multilineKey] = multilineContent.join('\n');
+ }
+
// Don't forget the last field
if (currentField) {
fields.push(currentField);