diff --git a/assets/content-builder.css b/assets/content-builder.css index c66694b..a758535 100644 --- a/assets/content-builder.css +++ b/assets/content-builder.css @@ -1381,3 +1381,24 @@ body.modal-open { overflow-wrap: break-word; text-align: left; } + +/* Legacy HTML Migration */ +.yform-content-builder .content-builder-legacy { + margin-bottom: 15px; +} + +.yform-content-builder .content-builder-legacy-notice { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.yform-content-builder .content-builder-legacy-notice-text { + flex: 1 1 auto; +} + +.yform-content-builder .content-builder-legacy-notice .content-builder-legacy-migrate { + flex: 0 0 auto; + margin-left: auto; +} diff --git a/assets/content-builder.js b/assets/content-builder.js index aafe329..ebe5604 100644 --- a/assets/content-builder.js +++ b/assets/content-builder.js @@ -32,6 +32,33 @@ this.initMoveButtons(); this.initGridViews(); this.updateSectionClasses(); + this.initLegacyEditors(); + }, + + /** + * Legacy-HTML-Editor initialisieren (CKE5 oder TinyMCE) in Feldern, + * deren bisheriger Wert klassisches HTML statt Content-Builder-JSON enthielt. + */ + initLegacyEditors: function() { + $('.yform-content-builder .content-builder-legacy').each(function() { + var $wrap = $(this); + if ($wrap.data('legacyInitialized')) { + return; + } + $wrap.data('legacyInitialized', true); + + var editor = ($wrap.data('legacyEditor') || '').toString(); + var $textarea = $wrap.find('textarea.content-builder-legacy-textarea').first(); + if ($textarea.length === 0) { + return; + } + + if (editor === 'cke5' && typeof cke5_init === 'function') { + try { cke5_init($textarea); } catch (e) { /* noop */ } + } else if (editor === 'tinymce' && typeof tiny_init === 'function') { + try { tiny_init($wrap); } catch (e) { /* noop */ } + } + }); }, initElementMenuTooltips: function() { @@ -72,6 +99,14 @@ bindEvents: function() { var self = this; + // Legacy-HTML → Content Builder migrieren + $(document).on('click', '.content-builder-legacy-migrate', function(e) { + e.preventDefault(); + e.stopPropagation(); + self.migrateLegacyHtml($(this).closest('.yform-content-builder')); + return false; + }); + // Slice löschen - MUSS VOR edit kommen! $(document).on('click', '.btn-slice-delete', function(e) { e.preventDefault(); @@ -1376,6 +1411,94 @@ }, 500); }, + /** + * Konvertiert einen klassischen HTML-Wert in einen einzelnen + * starter_text-Slice und initialisiert den Content Builder. + */ + migrateLegacyHtml: function($container) { + if ($container.length === 0) { + return; + } + + var $legacy = $container.find('.content-builder-legacy').first(); + if ($legacy.length === 0) { + return; + } + + var $textarea = $legacy.find('textarea.content-builder-legacy-textarea').first(); + if ($textarea.length === 0) { + return; + } + + // Aktuellen HTML-Inhalt holen (TinyMCE / CKE5 in Textarea zurueckschreiben). + var html = ''; + var textareaId = $textarea.attr('id'); + + if (typeof tinymce !== 'undefined' && textareaId) { + var tinyEd = tinymce.get(textareaId); + if (tinyEd) { + try { + tinyEd.save(); + html = tinyEd.getContent(); + } catch (e) { /* noop */ } + } + } + + if (!html && typeof ckeditors !== 'undefined' && textareaId && ckeditors[textareaId]) { + try { html = ckeditors[textareaId].getData(); } catch (e) { /* noop */ } + } + + if (!html) { + html = $textarea.val() || ''; + } + + var $confirmBtn = $legacy.find('.content-builder-legacy-migrate'); + var confirmMsg = $confirmBtn.data('confirm'); + if (confirmMsg && !window.confirm(confirmMsg)) { + return; + } + + // Editor-Instanzen sauber entfernen. + if (typeof tinymce !== 'undefined' && textareaId) { + var ed = tinymce.get(textareaId); + if (ed) { try { ed.remove(); } catch (e) { /* noop */ } } + } + if (typeof ckeditors !== 'undefined' && textareaId && ckeditors[textareaId]) { + try { ckeditors[textareaId].destroy(); } catch (e) { /* noop */ } + } + + // Form-Input-Name vom Legacy-Textarea uebernehmen, damit der + // submit ohne weitere Aktion den Content-Builder-JSON sendet. + var fieldName = $textarea.attr('name') || ''; + + // Legacy-Block entfernen. + $legacy.remove(); + + // Versteckten Content-Builder-Datenspeicher (neu) anlegen. + var $hidden = $container.find('input.content-builder-data'); + if ($hidden.length === 0 && fieldName) { + $hidden = $('').attr('name', fieldName); + $container.append($hidden); + } + + // Container fuer Slices/Add-Button wieder anzeigen. + $container.find('.content-builder-slices, .content-builder-add').show(); + $container.removeClass('has-legacy-html'); + + // Einen starter_text-Slice mit dem HTML als "text"-Feld anlegen. + var $slicesContainer = $container.find('.content-builder-slices').first(); + this.addSlice($slicesContainer, 'starter_text', 'Text'); + + var $newSlice = $slicesContainer.children('.content-builder-slice').last(); + if ($newSlice.length) { + var sliceData = { text: html }; + $newSlice.attr('data-slice-data', JSON.stringify(sliceData)); + } + + // Hidden-Feld mit dem neuen Slice befuellen. + this.updateHiddenField(); + }, + updateHiddenField: function() { $('.yform-content-builder').each(function() { var $container = $(this); diff --git a/lang/de_de.lang b/lang/de_de.lang index 35b587e..27cfa42 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -26,6 +26,15 @@ yform_content_builder_enable_online_toggle = Online/Offline pro Abschnitt yform_content_builder_enable_online_toggle_label = Online/Offline-Schalter für einzelne Abschnitte aktivieren yform_content_builder_enable_online_toggle_notice = Wenn aktiv, erscheint in der Slice-Toolbar ein Augen-Button, mit dem einzelne Abschnitte offline geschaltet werden können. Offline-Abschnitte bleiben in den Daten erhalten, werden aber im Frontend (via yform_content_builder_helper) automatisch ausgeblendet. Gilt nur für die YForm-Variante – die Modulvariante nutzt die normalen REDAXO-Slice-Funktionen. +yform_content_builder_legacy_editor = Legacy-HTML-Editor +yform_content_builder_legacy_editor_none = Deaktiviert +yform_content_builder_legacy_editor_notice = Editor, der angezeigt wird, wenn ein bestehender Wert klassisches HTML statt Content-Builder-JSON enthält. Erlaubt die Migration alter Inhalte in den Content Builder. +yform_content_builder_legacy_profile = Legacy-Editor-Profil +yform_content_builder_legacy_profile_notice = Profilname (Attribute) für CKEditor 5 oder TinyMCE, das für den Legacy-Editor verwendet wird. +yform_content_builder_legacy_notice = Der bestehende Inhalt liegt im klassischen HTML-Format vor. Auf den Content Builder umstellen? +yform_content_builder_legacy_migrate = Jetzt in Content Builder umwandeln +yform_content_builder_legacy_migrate_confirm = Den vorhandenen HTML-Inhalt jetzt als Text-Element in den Content Builder übernehmen? + yform_content_builder_element_add = Element hinzufügen yform_content_builder_element_edit = Bearbeiten yform_content_builder_element_delete = Löschen diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 67a01e9..8b79a7c 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -47,6 +47,15 @@ yform_content_builder_enable_online_toggle = Online/Offline per section yform_content_builder_enable_online_toggle_label = Enable online/offline switch for individual sections yform_content_builder_enable_online_toggle_notice = When enabled, an eye button appears in the slice toolbar that allows individual sections to be set offline. Offline sections are preserved in the data but automatically hidden in the frontend (via yform_content_builder_helper). Applies only to the YForm variant – the module variant uses the standard REDAXO slice functions. +yform_content_builder_legacy_editor = Legacy HTML editor +yform_content_builder_legacy_editor_none = Disabled +yform_content_builder_legacy_editor_notice = Editor shown when an existing value contains classic HTML instead of Content Builder JSON. Allows migrating legacy content into the Content Builder. +yform_content_builder_legacy_profile = Legacy editor profile +yform_content_builder_legacy_profile_notice = Profile name (attribute) for CKEditor 5 or TinyMCE used for the legacy editor. +yform_content_builder_legacy_notice = The existing value is classic HTML. Switch to the Content Builder? +yform_content_builder_legacy_migrate = Convert to Content Builder +yform_content_builder_legacy_migrate_confirm = Move the existing HTML content into the Content Builder as a text element now? + yform_content_builder_empty = No elements yet. Add an element below. yform_content_builder_delete_confirm = Really delete element? diff --git a/lib/rex_yform_value_content_builder.php b/lib/rex_yform_value_content_builder.php index ad81f98..05d6cec 100644 --- a/lib/rex_yform_value_content_builder.php +++ b/lib/rex_yform_value_content_builder.php @@ -980,7 +980,37 @@ protected function getTemplateVars(): array { $value = $this->parseValue(); $framework = $this->getElement('framework', 'bootstrap'); - + + // Legacy-HTML-Erkennung: + // Wenn der gespeicherte Wert nicht leer ist aber keinen Slice-Array ergibt, + // gehen wir davon aus, dass es sich um klassisches HTML handelt und + // bieten dem Admin die Migration in den Content Builder an. + $rawValue = (string) $this->getValue(); + $legacyHtml = ''; + if ($rawValue !== '' && empty($value)) { + $decoded = json_decode($rawValue, true); + if (!is_array($decoded)) { + $legacyHtml = $rawValue; + } + } + + $addon = rex_addon::get('yform_content_builder'); + $legacyEditor = (string) $this->getElement('legacy_editor', ''); + if ($legacyEditor === '') { + $legacyEditor = (string) $addon->getConfig('legacy_editor', 'none'); + } + if (!in_array($legacyEditor, ['none', 'cke5', 'tinymce'], true)) { + $legacyEditor = 'none'; + } + + $legacyProfile = (string) $this->getElement('legacy_profile', ''); + if ($legacyProfile === '') { + $legacyProfile = (string) $addon->getConfig('legacy_profile', 'default'); + } + if ($legacyProfile === '') { + $legacyProfile = 'default'; + } + return [ 'value' => $value, 'field_type' => 'content_builder', @@ -993,6 +1023,9 @@ protected function getTemplateVars(): array 'description' => $this->getElement('description', ''), 'framework' => $framework, 'available_elements' => $this->getAvailableElements(), + 'legacy_html' => $legacyHtml, + 'legacy_editor' => $legacyEditor, + 'legacy_profile' => $legacyProfile, ]; } @@ -1281,6 +1314,21 @@ public function getDefinitions(): array 'expanded' => false, 'default' => '', ], + 'legacy_editor' => [ + 'type' => 'choice', + 'label' => 'Legacy-HTML-Editor (leer = Addon-Einstellung)', + 'choices' => [ + '' => '-- Addon-Einstellung --', + 'none' => 'Deaktiviert', + 'cke5' => 'CKEditor 5', + 'tinymce' => 'TinyMCE', + ], + 'default' => '', + ], + 'legacy_profile' => [ + 'type' => 'text', + 'label' => 'Legacy-Editor-Profil (leer = Addon-Einstellung)', + ], 'description' => ['type' => 'text', 'label' => 'Beschreibung'], 'notice' => ['type' => 'text', 'label' => rex_i18n::msg('yform_values_defaults_notice')], ], diff --git a/pages/settings.php b/pages/settings.php index 6d5cc27..129642b 100644 --- a/pages/settings.php +++ b/pages/settings.php @@ -11,6 +11,15 @@ $addon->setConfig('theme', rex_post('theme', 'string', '')); $addon->setConfig('compact_mode', rex_post('compact_mode', 'bool', false)); $addon->setConfig('enable_online_toggle', rex_post('enable_online_toggle', 'bool', false)); + + $allowedLegacyEditors = ['none', 'cke5', 'tinymce']; + $legacyEditor = rex_post('legacy_editor', 'string', 'none'); + if (!in_array($legacyEditor, $allowedLegacyEditors, true)) { + $legacyEditor = 'none'; + } + $addon->setConfig('legacy_editor', $legacyEditor); + $addon->setConfig('legacy_profile', rex_post('legacy_profile', 'string', 'default')); + echo rex_view::success(rex_i18n::msg('yform_content_builder_settings_saved')); // Theme Builder Cache zurücksetzen @@ -30,6 +39,11 @@ $currentTheme = $addon->getConfig('theme', ''); $compactMode = $addon->getConfig('compact_mode', false); $enableOnlineToggle = $addon->getConfig('enable_online_toggle', false); +$legacyEditor = $addon->getConfig('legacy_editor', 'none'); +if (!in_array($legacyEditor, ['none', 'cke5', 'tinymce'], true)) { + $legacyEditor = 'none'; +} +$legacyProfile = (string) $addon->getConfig('legacy_profile', 'default'); // Formular bauen $content = ''; @@ -64,6 +78,29 @@ $n['note'] = rex_i18n::msg('yform_content_builder_enable_online_toggle_notice'); $formElements[] = $n; +// Legacy-Editor (HTML-Erkennung) +$legacyEditorChoices = [ + 'none' => rex_i18n::msg('yform_content_builder_legacy_editor_none'), + 'cke5' => 'CKEditor 5', + 'tinymce' => 'TinyMCE', +]; +$n = []; +$n['label'] = ''; +$n['field'] = ''; +$n['note'] = rex_i18n::msg('yform_content_builder_legacy_editor_notice'); +$formElements[] = $n; + +$n = []; +$n['label'] = ''; +$n['field'] = ''; +$n['note'] = rex_i18n::msg('yform_content_builder_legacy_profile_notice'); +$formElements[] = $n; + $fragment = new rex_fragment(); $fragment->setVar('elements', $formElements, false); $content .= $fragment->parse('core/form/form.php'); diff --git a/ytemplates/bootstrap/value.content_builder.tpl.php b/ytemplates/bootstrap/value.content_builder.tpl.php index 9a4f713..e79375a 100644 --- a/ytemplates/bootstrap/value.content_builder.tpl.php +++ b/ytemplates/bootstrap/value.content_builder.tpl.php @@ -10,6 +10,9 @@ * @var string $description * @var string $framework * @var array $available_elements + * @var string $legacy_html + * @var string $legacy_editor + * @var string $legacy_profile */ $fieldClass = 'yform-content-builder'; @@ -17,6 +20,14 @@ $fieldClass .= ' required'; } +$legacyHtml = isset($legacy_html) ? (string) $legacy_html : ''; +$legacyEditor = isset($legacy_editor) ? (string) $legacy_editor : 'none'; +$legacyProfile = isset($legacy_profile) ? (string) $legacy_profile : 'default'; +$hasLegacyHtml = ($legacyHtml !== '' && $legacyEditor !== 'none'); +if ($hasLegacyHtml) { + $fieldClass .= ' has-legacy-html'; +} + // Kompaktmodus aus Addon-Config laden $addon = rex_addon::get('yform_content_builder'); if ($addon->getConfig('compact_mode')) { @@ -47,6 +58,8 @@
@@ -56,8 +69,30 @@

- -
+ + + getId() . '_' . uniqid(); + $legacyInputName = 'FORM[' . $this->params['form_name'] . '][' . $this->getId() . ']'; + ?> +
+
+ + + +
+ +
+ + + -
+ + +