Loading editor/html/css/editor.css +76 −0 Changes for editor/html/css/editor.css: 76 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1155,6 +1155,82 @@ dialog label { gap: 4px; } /* FAQ items editor */ .faq-items-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; } .faq-item-card { display: flex; flex-direction: column; gap: 6px; padding: 8px; background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 4px; } .faq-item-header { display: flex; align-items: center; justify-content: space-between; font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; } .faq-item-remove { background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 13px; line-height: 1; padding: 2px 4px; } .faq-item-remove:hover { color: var(--danger); } .faq-item-question, .faq-item-answer { width: 100%; padding: 6px 8px; background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 3px; font-size: 13px; font-family: inherit; box-sizing: border-box; } .faq-item-answer { resize: vertical; min-height: 50px; } .faq-item-add { width: 100%; padding: 6px; background: var(--bg-tertiary); color: var(--accent); border: 1px dashed var(--accent); border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; } .faq-item-add:hover { background: var(--bg-hover); } .media-browse-field input { flex: 1; } Loading editor/html/js/properties.js +92 −0 Changes for editor/html/js/properties.js: 92 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -698,6 +698,92 @@ var PropertiesPanel = (function() { group.appendChild(wrapper); panel.appendChild(group); continue; } else if (field.type === 'faq_items') { var faqWrap = document.createElement('div'); faqWrap.className = 'faq-items-editor'; var faqHidden = document.createElement('input'); faqHidden.type = 'hidden'; faqHidden.name = field.key; faqHidden.setAttribute('data-json-array', 'true'); var faqList = Array.isArray(value) ? value.slice() : []; var faqListEl = document.createElement('div'); faqListEl.className = 'faq-items-list'; function faqSyncHidden() { faqHidden.value = JSON.stringify(faqList); faqHidden.dispatchEvent(new Event('change', { bubbles: true })); } function faqRenderList() { faqListEl.innerHTML = ''; faqList.forEach(function(item, idx) { var card = document.createElement('div'); card.className = 'faq-item-card'; var cardHeader = document.createElement('div'); cardHeader.className = 'faq-item-header'; cardHeader.textContent = 'Frage ' + (idx + 1); var removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'faq-item-remove'; removeBtn.textContent = '✕'; removeBtn.title = 'Entfernen'; removeBtn.addEventListener('click', function() { faqList.splice(idx, 1); faqSyncHidden(); faqRenderList(); }); cardHeader.appendChild(removeBtn); var qInput = document.createElement('input'); qInput.type = 'text'; qInput.placeholder = 'Frage'; qInput.className = 'faq-item-question'; qInput.value = item.question || ''; qInput.addEventListener('input', function() { faqList[idx].question = qInput.value; faqSyncHidden(); }); var aInput = document.createElement('textarea'); aInput.placeholder = 'Antwort'; aInput.className = 'faq-item-answer'; aInput.rows = 3; aInput.value = item.answer || ''; aInput.addEventListener('input', function() { faqList[idx].answer = aInput.value; faqSyncHidden(); }); card.appendChild(cardHeader); card.appendChild(qInput); card.appendChild(aInput); faqListEl.appendChild(card); }); } faqRenderList(); faqSyncHidden(); var faqAddBtn = document.createElement('button'); faqAddBtn.type = 'button'; faqAddBtn.className = 'faq-item-add'; faqAddBtn.textContent = '+ Frage hinzufügen'; faqAddBtn.addEventListener('click', function() { faqList.push({ question: '', answer: '' }); faqSyncHidden(); faqRenderList(); }); faqWrap.appendChild(faqListEl); faqWrap.appendChild(faqAddBtn); faqWrap.appendChild(faqHidden); group.appendChild(faqWrap); panel.appendChild(group); continue; } else { input = document.createElement('input'); input.type = 'text'; Loading Loading @@ -730,6 +816,12 @@ var PropertiesPanel = (function() { var input = inputs[i]; if (input.type === 'checkbox') { properties[input.name] = input.checked; } else if (input.getAttribute('data-json-array') === 'true') { try { properties[input.name] = JSON.parse(input.value || '[]'); } catch (e) { properties[input.name] = []; } } else { properties[input.name] = input.value; } Loading editor/widgets/faq/faq.cpp +35 −2 Changes for editor/widgets/faq/faq.cpp: 35 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ extern "C" { std::string titleColor = "var(--blogi-text)"; std::string padding = "0"; std::string maxWidth = ""; std::string columns = "1"; // Question / answer colors (dark mode defaults) std::string questionBgColor = "var(--blogi-surface)"; Loading Loading @@ -203,6 +204,11 @@ extern "C" { if (options) json_object_object_add(f, "options", options); json_object_array_add(arr, f); }; auto mkOpts = [](std::initializer_list<const char*> vals) { json_object *a = json_object_new_array(); for (auto v : vals) json_object_array_add(a, json_object_new_string(v)); return a; }; // Container addField("title", "Section Title", "text", "e.g. FAQ"); Loading @@ -211,6 +217,7 @@ extern "C" { addField("l_title_color", "Title Color (Light)", "color_var", nullptr, "light"); addField("padding", "Padding", "text", "0", "desktop"); addField("max_width", "Max Width", "text", "e.g. 800px"); addField("columns", "Spalten", "select", nullptr, "desktop", mkOpts({"1","2","3","4"})); // Question row styling addField("question_bg_color", "Question Background (Dark)", "color_var"); Loading Loading @@ -282,6 +289,7 @@ extern "C" { element->SetAttribute("title_color", titleColor.c_str()); element->SetAttribute("padding", padding.c_str()); element->SetAttribute("max_width", maxWidth.c_str()); element->SetAttribute("columns", columns.c_str()); element->SetAttribute("question_bg_color", questionBgColor.c_str()); element->SetAttribute("question_text_color", questionTextColor.c_str()); Loading Loading @@ -337,6 +345,7 @@ extern "C" { val = xml_data->Attribute("title_color"); if (val) titleColor = val; val = xml_data->Attribute("padding"); if (val) padding = val; val = xml_data->Attribute("max_width"); if (val) maxWidth = val; val = xml_data->Attribute("columns"); if (val) columns = val; val = xml_data->Attribute("question_bg_color"); if (val) questionBgColor = val; val = xml_data->Attribute("question_text_color"); if (val) questionTextColor = val; Loading Loading @@ -395,6 +404,7 @@ extern "C" { json_object_object_add(obj, "title_color", json_object_new_string(titleColor.c_str())); json_object_object_add(obj, "padding", json_object_new_string(padding.c_str())); json_object_object_add(obj, "max_width", json_object_new_string(maxWidth.c_str())); json_object_object_add(obj, "columns", json_object_new_string(columns.c_str())); json_object_object_add(obj, "question_bg_color", json_object_new_string(questionBgColor.c_str())); json_object_object_add(obj, "question_text_color", json_object_new_string(questionTextColor.c_str())); Loading Loading @@ -477,6 +487,7 @@ extern "C" { strNonEmpty("title_color", titleColor); str("padding", padding); str("max_width", maxWidth); strNonEmpty("columns", columns); strNonEmpty("question_bg_color", questionBgColor); strNonEmpty("question_text_color", questionTextColor); Loading Loading @@ -558,6 +569,12 @@ extern "C" { return !v.empty() && v != "none"; }; int colsN = 1; try { colsN = std::stoi(columns); } catch (...) { colsN = 1; } if (colsN < 1) colsN = 1; if (colsN > 4) colsN = 4; bool gridMode = colsN > 1; // Outer container libhtmlpp::HtmlElement outer("div"); outer.setAttribute("class", (rc + "-faq").c_str()); Loading @@ -584,6 +601,14 @@ extern "C" { } // FAQ items libhtmlpp::HtmlElement itemsWrap("div"); itemsWrap.setAttribute("class", (rc + "-items").c_str()); if (gridMode) { std::string gridStyle = "display: grid; grid-template-columns: repeat(" + std::to_string(colsN) + ", 1fr); gap: 16px;"; itemsWrap.setAttribute("style", gridStyle.c_str()); } for (size_t i = 0; i < items.size(); ++i) { const auto &item = items[i]; std::string contentId = rc + "-ans-" + std::to_string(i); Loading @@ -595,6 +620,10 @@ extern "C" { itemEl.setAttribute("itemtype", "https://schema.org/Question"); std::string itemStyle; if (gridMode) { if (validCss(borderColor)) itemStyle += "border: 1px solid " + borderColor + ";"; if (validCss(borderRadius)) itemStyle += " border-radius: " + borderRadius + ";"; } else { if (i > 0 && validCss(borderColor)) itemStyle += "border-top: 1px solid " + borderColor + ";"; if (i == 0 && validCss(borderColor)) Loading @@ -603,6 +632,7 @@ extern "C" { itemStyle += " border-radius: " + borderRadius + " " + borderRadius + " 0 0;"; if (validCss(borderRadius) && i + 1 == items.size()) itemStyle += " border-radius: 0 0 " + borderRadius + " " + borderRadius + ";"; } itemStyle += " overflow: hidden;"; if (!itemStyle.empty()) itemEl.setAttribute("style", itemStyle.c_str()); Loading Loading @@ -669,9 +699,11 @@ extern "C" { aDiv.appendChild(ansText); itemEl.appendChild(aDiv); outer.appendChild(itemEl); itemsWrap.appendChild(itemEl); } outer.appendChild(itemsWrap); el.appendChild(outer); // --- Responsive & Light mode CSS --- Loading @@ -688,11 +720,12 @@ extern "C" { if (validCss(m_answerFontSize)) mobileACSS += " font-size: " + m_answerFontSize + " !important;"; if (validCss(m_padding)) mobileWrapCSS += " padding: " + m_padding + " !important;"; if (!mobileWrapCSS.empty() || !mobileQCSS.empty() || !mobileACSS.empty()) { if (!mobileWrapCSS.empty() || !mobileQCSS.empty() || !mobileACSS.empty() || gridMode) { mediaCSS += "@media (max-width:768px){"; if (!mobileWrapCSS.empty()) mediaCSS += "." + rc + "-faq{" + mobileWrapCSS + "}"; if (!mobileQCSS.empty()) mediaCSS += "." + rc + "-q{" + mobileQCSS + "}"; if (!mobileACSS.empty()) mediaCSS += "." + rc + "-a{" + mobileACSS + "}"; if (gridMode) mediaCSS += "." + rc + "-items{grid-template-columns:1fr !important;}"; mediaCSS += "}"; } Loading Loading
editor/html/css/editor.css +76 −0 Changes for editor/html/css/editor.css: 76 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1155,6 +1155,82 @@ dialog label { gap: 4px; } /* FAQ items editor */ .faq-items-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; } .faq-item-card { display: flex; flex-direction: column; gap: 6px; padding: 8px; background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: 4px; } .faq-item-header { display: flex; align-items: center; justify-content: space-between; font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; } .faq-item-remove { background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 13px; line-height: 1; padding: 2px 4px; } .faq-item-remove:hover { color: var(--danger); } .faq-item-question, .faq-item-answer { width: 100%; padding: 6px 8px; background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 3px; font-size: 13px; font-family: inherit; box-sizing: border-box; } .faq-item-answer { resize: vertical; min-height: 50px; } .faq-item-add { width: 100%; padding: 6px; background: var(--bg-tertiary); color: var(--accent); border: 1px dashed var(--accent); border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; } .faq-item-add:hover { background: var(--bg-hover); } .media-browse-field input { flex: 1; } Loading
editor/html/js/properties.js +92 −0 Changes for editor/html/js/properties.js: 92 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -698,6 +698,92 @@ var PropertiesPanel = (function() { group.appendChild(wrapper); panel.appendChild(group); continue; } else if (field.type === 'faq_items') { var faqWrap = document.createElement('div'); faqWrap.className = 'faq-items-editor'; var faqHidden = document.createElement('input'); faqHidden.type = 'hidden'; faqHidden.name = field.key; faqHidden.setAttribute('data-json-array', 'true'); var faqList = Array.isArray(value) ? value.slice() : []; var faqListEl = document.createElement('div'); faqListEl.className = 'faq-items-list'; function faqSyncHidden() { faqHidden.value = JSON.stringify(faqList); faqHidden.dispatchEvent(new Event('change', { bubbles: true })); } function faqRenderList() { faqListEl.innerHTML = ''; faqList.forEach(function(item, idx) { var card = document.createElement('div'); card.className = 'faq-item-card'; var cardHeader = document.createElement('div'); cardHeader.className = 'faq-item-header'; cardHeader.textContent = 'Frage ' + (idx + 1); var removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'faq-item-remove'; removeBtn.textContent = '✕'; removeBtn.title = 'Entfernen'; removeBtn.addEventListener('click', function() { faqList.splice(idx, 1); faqSyncHidden(); faqRenderList(); }); cardHeader.appendChild(removeBtn); var qInput = document.createElement('input'); qInput.type = 'text'; qInput.placeholder = 'Frage'; qInput.className = 'faq-item-question'; qInput.value = item.question || ''; qInput.addEventListener('input', function() { faqList[idx].question = qInput.value; faqSyncHidden(); }); var aInput = document.createElement('textarea'); aInput.placeholder = 'Antwort'; aInput.className = 'faq-item-answer'; aInput.rows = 3; aInput.value = item.answer || ''; aInput.addEventListener('input', function() { faqList[idx].answer = aInput.value; faqSyncHidden(); }); card.appendChild(cardHeader); card.appendChild(qInput); card.appendChild(aInput); faqListEl.appendChild(card); }); } faqRenderList(); faqSyncHidden(); var faqAddBtn = document.createElement('button'); faqAddBtn.type = 'button'; faqAddBtn.className = 'faq-item-add'; faqAddBtn.textContent = '+ Frage hinzufügen'; faqAddBtn.addEventListener('click', function() { faqList.push({ question: '', answer: '' }); faqSyncHidden(); faqRenderList(); }); faqWrap.appendChild(faqListEl); faqWrap.appendChild(faqAddBtn); faqWrap.appendChild(faqHidden); group.appendChild(faqWrap); panel.appendChild(group); continue; } else { input = document.createElement('input'); input.type = 'text'; Loading Loading @@ -730,6 +816,12 @@ var PropertiesPanel = (function() { var input = inputs[i]; if (input.type === 'checkbox') { properties[input.name] = input.checked; } else if (input.getAttribute('data-json-array') === 'true') { try { properties[input.name] = JSON.parse(input.value || '[]'); } catch (e) { properties[input.name] = []; } } else { properties[input.name] = input.value; } Loading
editor/widgets/faq/faq.cpp +35 −2 Changes for editor/widgets/faq/faq.cpp: 35 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ extern "C" { std::string titleColor = "var(--blogi-text)"; std::string padding = "0"; std::string maxWidth = ""; std::string columns = "1"; // Question / answer colors (dark mode defaults) std::string questionBgColor = "var(--blogi-surface)"; Loading Loading @@ -203,6 +204,11 @@ extern "C" { if (options) json_object_object_add(f, "options", options); json_object_array_add(arr, f); }; auto mkOpts = [](std::initializer_list<const char*> vals) { json_object *a = json_object_new_array(); for (auto v : vals) json_object_array_add(a, json_object_new_string(v)); return a; }; // Container addField("title", "Section Title", "text", "e.g. FAQ"); Loading @@ -211,6 +217,7 @@ extern "C" { addField("l_title_color", "Title Color (Light)", "color_var", nullptr, "light"); addField("padding", "Padding", "text", "0", "desktop"); addField("max_width", "Max Width", "text", "e.g. 800px"); addField("columns", "Spalten", "select", nullptr, "desktop", mkOpts({"1","2","3","4"})); // Question row styling addField("question_bg_color", "Question Background (Dark)", "color_var"); Loading Loading @@ -282,6 +289,7 @@ extern "C" { element->SetAttribute("title_color", titleColor.c_str()); element->SetAttribute("padding", padding.c_str()); element->SetAttribute("max_width", maxWidth.c_str()); element->SetAttribute("columns", columns.c_str()); element->SetAttribute("question_bg_color", questionBgColor.c_str()); element->SetAttribute("question_text_color", questionTextColor.c_str()); Loading Loading @@ -337,6 +345,7 @@ extern "C" { val = xml_data->Attribute("title_color"); if (val) titleColor = val; val = xml_data->Attribute("padding"); if (val) padding = val; val = xml_data->Attribute("max_width"); if (val) maxWidth = val; val = xml_data->Attribute("columns"); if (val) columns = val; val = xml_data->Attribute("question_bg_color"); if (val) questionBgColor = val; val = xml_data->Attribute("question_text_color"); if (val) questionTextColor = val; Loading Loading @@ -395,6 +404,7 @@ extern "C" { json_object_object_add(obj, "title_color", json_object_new_string(titleColor.c_str())); json_object_object_add(obj, "padding", json_object_new_string(padding.c_str())); json_object_object_add(obj, "max_width", json_object_new_string(maxWidth.c_str())); json_object_object_add(obj, "columns", json_object_new_string(columns.c_str())); json_object_object_add(obj, "question_bg_color", json_object_new_string(questionBgColor.c_str())); json_object_object_add(obj, "question_text_color", json_object_new_string(questionTextColor.c_str())); Loading Loading @@ -477,6 +487,7 @@ extern "C" { strNonEmpty("title_color", titleColor); str("padding", padding); str("max_width", maxWidth); strNonEmpty("columns", columns); strNonEmpty("question_bg_color", questionBgColor); strNonEmpty("question_text_color", questionTextColor); Loading Loading @@ -558,6 +569,12 @@ extern "C" { return !v.empty() && v != "none"; }; int colsN = 1; try { colsN = std::stoi(columns); } catch (...) { colsN = 1; } if (colsN < 1) colsN = 1; if (colsN > 4) colsN = 4; bool gridMode = colsN > 1; // Outer container libhtmlpp::HtmlElement outer("div"); outer.setAttribute("class", (rc + "-faq").c_str()); Loading @@ -584,6 +601,14 @@ extern "C" { } // FAQ items libhtmlpp::HtmlElement itemsWrap("div"); itemsWrap.setAttribute("class", (rc + "-items").c_str()); if (gridMode) { std::string gridStyle = "display: grid; grid-template-columns: repeat(" + std::to_string(colsN) + ", 1fr); gap: 16px;"; itemsWrap.setAttribute("style", gridStyle.c_str()); } for (size_t i = 0; i < items.size(); ++i) { const auto &item = items[i]; std::string contentId = rc + "-ans-" + std::to_string(i); Loading @@ -595,6 +620,10 @@ extern "C" { itemEl.setAttribute("itemtype", "https://schema.org/Question"); std::string itemStyle; if (gridMode) { if (validCss(borderColor)) itemStyle += "border: 1px solid " + borderColor + ";"; if (validCss(borderRadius)) itemStyle += " border-radius: " + borderRadius + ";"; } else { if (i > 0 && validCss(borderColor)) itemStyle += "border-top: 1px solid " + borderColor + ";"; if (i == 0 && validCss(borderColor)) Loading @@ -603,6 +632,7 @@ extern "C" { itemStyle += " border-radius: " + borderRadius + " " + borderRadius + " 0 0;"; if (validCss(borderRadius) && i + 1 == items.size()) itemStyle += " border-radius: 0 0 " + borderRadius + " " + borderRadius + ";"; } itemStyle += " overflow: hidden;"; if (!itemStyle.empty()) itemEl.setAttribute("style", itemStyle.c_str()); Loading Loading @@ -669,9 +699,11 @@ extern "C" { aDiv.appendChild(ansText); itemEl.appendChild(aDiv); outer.appendChild(itemEl); itemsWrap.appendChild(itemEl); } outer.appendChild(itemsWrap); el.appendChild(outer); // --- Responsive & Light mode CSS --- Loading @@ -688,11 +720,12 @@ extern "C" { if (validCss(m_answerFontSize)) mobileACSS += " font-size: " + m_answerFontSize + " !important;"; if (validCss(m_padding)) mobileWrapCSS += " padding: " + m_padding + " !important;"; if (!mobileWrapCSS.empty() || !mobileQCSS.empty() || !mobileACSS.empty()) { if (!mobileWrapCSS.empty() || !mobileQCSS.empty() || !mobileACSS.empty() || gridMode) { mediaCSS += "@media (max-width:768px){"; if (!mobileWrapCSS.empty()) mediaCSS += "." + rc + "-faq{" + mobileWrapCSS + "}"; if (!mobileQCSS.empty()) mediaCSS += "." + rc + "-q{" + mobileQCSS + "}"; if (!mobileACSS.empty()) mediaCSS += "." + rc + "-a{" + mobileACSS + "}"; if (gridMode) mediaCSS += "." + rc + "-items{grid-template-columns:1fr !important;}"; mediaCSS += "}"; } Loading