Commit c31f9ddf authored by Jan Koester's avatar Jan Koester
Browse files

test

parent dbbc5eb0
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@
#include <cstdio>
#include <cstdlib>
#include <set>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <regex>
@@ -1788,6 +1789,22 @@ void collectCanvasMeta(const blogi::webedit::EditPlugin *node, json_object *out)
    }
}

// Walks the document tree collecting {instance_id: custom_css_id} for every
// element that has a non-empty getCssId() (see the css_id widget property).
// Used by renderTree() below to fix up internal "#we-<uuid>" anchors (e.g.
// Button's scroll-to-section href, built purely from the raw target instance
// id with no knowledge of that target's own rendered output) so they resolve
// to the human id the target widget actually rendered.
void collectCssIdOverrides(const blogi::webedit::EditPlugin *node, std::map<std::string, std::string> &out) {
    while (node) {
        std::string cssId = node->getCssId();
        if (!cssId.empty()) out[std::string(node->getInstanceId().c_str())] = cssId;
        if (node->getChildElement())
            collectCssIdOverrides(node->getChildElement(), out);
        node = node->nextElement();
    }
}

// Interactive canvas layer injected into the preview iframe: click-to-select,
// click-to-edit inline text (elements marked with data-we-textprop by the widgets
// that support it), and drag-to-reorder. Every widget's root node already carries
@@ -6381,6 +6398,9 @@ std::string webedit::Api::renderTree(const blogi::webedit::EditPlugin *node, con
                                     const std::string &extraJs) {
    if (!node) return "";

    std::map<std::string, std::string> cssIdOverrides;
    collectCssIdOverrides(node, cssIdOverrides);

    libhtmlpp::HtmlElement wrapper("div");
    auto *el = node;
    while (el) {
@@ -6416,6 +6436,20 @@ std::string webedit::Api::renderTree(const blogi::webedit::EditPlugin *node, con
    libhtmlpp::HtmlString hs;
    libhtmlpp::print(wrapper, hs, false);
    std::string html = hs.str();
    // Rewrite "#we-<uuid>" anchors (Button's scroll-to-section href) to the
    // custom css_id of their target, if that target set one -- the target's
    // own Render() already swapped its "id" attribute to the same value, so
    // this keeps the two in sync without either widget needing a reference
    // to the other.
    for (const auto &kv : cssIdOverrides) {
        std::string needle = "#we-" + kv.first;
        std::string replacement = "#" + kv.second;
        size_t pos = 0;
        while ((pos = html.find(needle, pos)) != std::string::npos) {
            html.replace(pos, needle.size(), replacement);
            pos += replacement.size();
        }
    }
    if (!extraCss.empty()) {
        html = "<style>" + escapeInlineStyle(extraCss) + "</style>" + html;
    }
+28 −0
Original line number Diff line number Diff line
#pragma once

#include <string>
#include <cctype>

namespace blogi {
    namespace webedit {

        // Sanitizes a user-supplied "custom CSS id" into a value that is safe to
        // use both as an HTML id attribute and as a "#id" CSS/URL fragment
        // selector. Keeps only ASCII letters, digits, '-' and '_'; everything
        // else is dropped. A result starting with a digit is prefixed with
        // "id-" since CSS identifiers (and thus querySelector('#...')) must not
        // start with a digit.
        inline std::string sanitizeCssId(const std::string &raw) {
            std::string out;
            out.reserve(raw.size());
            for (unsigned char c : raw) {
                if (std::isalnum(c) || c == '-' || c == '_') out += static_cast<char>(c);
            }
            if (!out.empty() && std::isdigit(static_cast<unsigned char>(out[0]))) {
                out = "id-" + out;
            }
            return out;
        }

    } // namespace webedit
} // namespace blogi
+19 −2
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "plugin.h"
#include "translations.h"
#include "../common/css_id.h"

extern "C" {
    namespace blogi::webedit {
@@ -45,6 +46,7 @@ extern "C" {
            std::string content;
            std::string backgroundColor = "";
            std::string cssClass = "";
            std::string cssId = "";
            std::string borderRadius = "";
            std::string borderStyle = "";
            std::string opacity = "";
@@ -261,6 +263,7 @@ extern "C" {
                addField("background_gradient_angle", "Gradient Angle", "text", "e.g. 135deg", "desktop");
                addField("background_gradient_custom", "Gradient Custom", "text", "e.g. linear-gradient(135deg, #ff0000, #0000ff)", "desktop");
                addField("css_class", "CSS Class", "text", "e.g. hero-section");
                addField("css_id", "CSS ID", "text", "e.g. pricing-section");
                addSpacingBox("padding", "Padding", "desktop");
                addSpacingBox("margin", "Margin", "desktop");
                addField("max_width", "Max Width", "text", "e.g. 1200px", "desktop");
@@ -338,6 +341,7 @@ extern "C" {
                // Persist properties
                element->SetAttribute("background_color", backgroundColor.c_str());
                element->SetAttribute("css_class", cssClass.c_str());
                element->SetAttribute("css_id", cssId.c_str());
                if (semanticTag != "section") {
                    element->SetAttribute("semantic_tag", semanticTag.c_str());
                }
@@ -448,6 +452,8 @@ extern "C" {
                if (val) backgroundColor = val;
                val = xml_data->Attribute("css_class");
                if (val) cssClass = val;
                val = xml_data->Attribute("css_id");
                if (val) cssId = val;
                val = xml_data->Attribute("semantic_tag");
                if (val) {
                    static const std::vector<std::string> allowedTags = {
@@ -617,6 +623,7 @@ extern "C" {
                json_object *obj = json_object_new_object();
                json_object_object_add(obj, "background_color", json_object_new_string(backgroundColor.c_str()));
                json_object_object_add(obj, "css_class", json_object_new_string(cssClass.c_str()));
                json_object_object_add(obj, "css_id", json_object_new_string(cssId.c_str()));
                json_object_object_add(obj, "border_radius", json_object_new_string(borderRadius.c_str()));
                json_object_object_add(obj, "border_style", json_object_new_string(borderStyle.c_str()));
                json_object_object_add(obj, "opacity", json_object_new_string(opacity.c_str()));
@@ -693,6 +700,10 @@ extern "C" {
                    val = json_object_get_string(val_obj);
                    if (val) cssClass = val;
                }
                if (json_object_object_get_ex(obj, "css_id", &val_obj)) {
                    val = json_object_get_string(val_obj);
                    if (val) cssId = val;
                }
                if (json_object_object_get_ex(obj, "border_radius", &val_obj)) {
                    val = json_object_get_string(val_obj);
                    if (val) borderRadius = val;
@@ -937,8 +948,12 @@ extern "C" {
                // Unique CSS class for responsive targeting
                std::string responsiveClass = std::string("we-") + getInstanceId().c_str();

                // Anchor ID so buttons can scroll to this section
                sect.setAttribute("id", responsiveClass.c_str());
                // Anchor ID so buttons can scroll to this section -- a custom
                // css_id, when set, replaces the auto "we-<uuid>" id (see
                // renderTree(), which fixes up Button's scroll-to-section
                // anchor to follow this override).
                std::string customCssId = sanitizeCssId(cssId);
                sect.setAttribute("id", (customCssId.empty() ? responsiveClass : customCssId).c_str());

                auto gradientCss = [](const std::string &type, const std::string &angle, const std::string &c1, const std::string &c2) -> std::string {
                    if (type == "linear") {
@@ -1178,6 +1193,8 @@ extern "C" {
                el.appendChild(sect);
            }

            std::string getCssId() const override { return sanitizeCssId(cssId); }

            // --- Element Management ---

            bool canHaveChildren() const override { return true; }
+7 −0
Original line number Diff line number Diff line
@@ -427,6 +427,13 @@ namespace blogi {
            // Article, List, Table, FAQ, ...) override this.
            virtual std::optional<std::string> toLlmsBlock() const { return std::nullopt; }

            // The sanitized user-supplied "custom CSS id" (see the css_id
            // widget property), or "" when unset/unsupported. renderTree()
            // uses this to resolve internal "#we-<uuid>" anchors (e.g.
            // Button's scroll-to-section) to the human id actually rendered,
            // without any widget needing to know about any other widget.
            virtual std::string getCssId() const { return ""; }

            // Sets the UI language this instance should render property-panel
            // field labels in ("de"/"en"). Called before get_schema so
            // getSchemaJson() can emit already-localized labels directly —