Commit b62c7848 authored by jan.koester's avatar jan.koester
Browse files

nodejs removed for widgets to slow

parent d387ff6b
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ add_executable(blogi-editor
    src/webedit_server.cpp
    src/webedit_api.cpp
    src/htmlimport.cpp
    src/nodewidget_adapter_stub.cpp
)

if(TARGET blogidev)
@@ -75,6 +74,4 @@ add_subdirectory(widgets)

install(TARGETS blogi-editor DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY html/ DESTINATION ${CMAKE_INSTALL_DATADIR}/blogi-editor/html)
install(DIRECTORY node/ DESTINATION ${CMAKE_INSTALL_DATADIR}/blogi-editor/node
    PATTERN "node_widget_runner.js" EXCLUDE)
install(FILES config.yaml DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/blogi-editor)
+2 −2
Original line number Diff line number Diff line
@@ -115,8 +115,8 @@ int main(int argc, char *argv[]) {
        db.initTables();

        // Load editor widget plugins
        std::cout << "Loading JS widget runtime (native widget plugins disabled)..." << std::endl;
        blogi::webedit::EditPluginLoad plugins({});
        std::cout << "Loading widget plugins..." << std::endl;
        blogi::webedit::EditPluginLoad plugins(config.getPluginDirs());

        auto *plg = plugins.getFirstPlugin();
        while (plg) {
+112 −5
Original line number Diff line number Diff line
@@ -4,11 +4,92 @@
#include <cstring>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <stdexcept>

namespace blogi::webedit {

namespace {

std::string readScript(const std::string &path) {
    std::ifstream in(path);
    if (!in.is_open())
        return "";
    std::ostringstream ss;
    ss << in.rdbuf();
    return ss.str();
}

void parseMetadataFromScript(const std::string &js,
                             std::string &name,
                             std::string &author,
                             std::string &version,
                             bool &canHaveChildren) {
    if (js.empty())
        return;

    std::smatch m;
    static const std::regex nameRe(R"(name\s*:\s*'([^']+)')");
    static const std::regex authorRe(R"(author\s*:\s*'([^']+)')");
    static const std::regex versionRe(R"(version\s*:\s*'([^']+)')");
    static const std::regex childrenRe(R"(can_have_children\s*:\s*(true|false))");

    if (std::regex_search(js, m, nameRe) && m.size() > 1)
        name = m[1].str();
    if (std::regex_search(js, m, authorRe) && m.size() > 1)
        author = m[1].str();
    if (std::regex_search(js, m, versionRe) && m.size() > 1)
        version = m[1].str();
    if (std::regex_search(js, m, childrenRe) && m.size() > 1)
        canHaveChildren = (m[1].str() == "true");
}

json_object *schemaFromScript(const std::string &js) {
    if (js.empty())
        return nullptr;

    json_object *arr = json_object_new_array();
    static const std::regex fieldRe(
        R"(field\(\s*'([^']+)'\s*,\s*'([^']+)'\s*,\s*'([^']+)'(?:\s*,\s*([^,\)]*))?(?:\s*,\s*'([^']+)')?)"
    );

    for (std::sregex_iterator it(js.begin(), js.end(), fieldRe), end; it != end; ++it) {
        const std::smatch &m = *it;
        json_object *f = json_object_new_object();
        json_object_object_add(f, "key", json_object_new_string(m[1].str().c_str()));
        json_object_object_add(f, "label", json_object_new_string(m[2].str().c_str()));
        json_object_object_add(f, "type", json_object_new_string(m[3].str().c_str()));

        std::string placeholder;
        if (m.size() > 4)
            placeholder = m[4].str();
        if (!placeholder.empty() && placeholder != "null") {
            if (placeholder.size() >= 2 &&
                ((placeholder.front() == '\'' && placeholder.back() == '\'') ||
                 (placeholder.front() == '"' && placeholder.back() == '"'))) {
                placeholder = placeholder.substr(1, placeholder.size() - 2);
            }
            json_object_object_add(f, "placeholder", json_object_new_string(placeholder.c_str()));
        }

        std::string target = "all";
        if (m.size() > 5 && m[5].matched)
            target = m[5].str();
        json_object_object_add(f, "target", json_object_new_string(target.c_str()));
        json_object_array_add(arr, f);
    }

    if (json_object_array_length(arr) == 0) {
        json_object_put(arr);
        return nullptr;
    }

    return arr;
}

}

NodeWidgetAdapter::NodeWidgetAdapter(const std::string &defaultName,
                                     const std::string &defaultAuthor,
                                     const std::string &defaultVersion,
@@ -28,9 +109,20 @@ NodeWidgetAdapter::NodeWidgetAdapter(const std::string &defaultName,
    }
}

const std::string NodeWidgetAdapter::getName() const { return name; }
const std::string NodeWidgetAdapter::getAuthor() const { return author; }
const std::string NodeWidgetAdapter::getVersion() const { return version; }
const std::string NodeWidgetAdapter::getName() const {
    ensureMetadataLoaded();
    return name;
}

const std::string NodeWidgetAdapter::getAuthor() const {
    ensureMetadataLoaded();
    return author;
}

const std::string NodeWidgetAdapter::getVersion() const {
    ensureMetadataLoaded();
    return version;
}

void NodeWidgetAdapter::logError(const std::string &message) {
    std::cerr << "NodeWidgetAdapter Error (" << getInstanceId().c_str() << "): "
@@ -60,7 +152,8 @@ void NodeWidgetAdapter::JsonApi(json_object *request, json_object *response) {
        json_object_object_add(data, "properties", json_object_new_string(toPropertiesJson().c_str()));
        json_object_object_add(response, "data", data);
    } else if (action && std::strcmp(action, "get_schema") == 0) {
        json_object_object_add(response, "schema", getSchemaJson());
        json_object *schema = schemaFromScript(readScript(scriptPath));
        json_object_object_add(response, "schema", schema ? schema : getSchemaJson());
    }

    json_object_object_add(response, "status", json_object_new_string("success"));
@@ -328,7 +421,21 @@ void NodeWidgetAdapter::fromPropertiesJson(const std::string &json) {
    json_object_put(customObj);
}

void NodeWidgetAdapter::ensureMetadataLoaded() const {}
void NodeWidgetAdapter::ensureMetadataLoaded() const {
    if (metadataLoaded)
        return;

    std::string js = readScript(scriptPath);
    if (!js.empty()) {
        parseMetadataFromScript(js,
                                name,
                                author,
                                version,
                                canHaveChildrenFlag);
    }

    metadataLoaded = true;
}

bool NodeWidgetAdapter::renderNodeSettings(libhtmlpp::HtmlElement &) const {
    return false;
+49 −198
Original line number Diff line number Diff line
@@ -50,191 +50,7 @@
#include "webedit_api.h"
#include "htmlimport.h"

#include "../widgets/nodewidget/nodewidget_adapter.h"

namespace {
struct NodeWidgetDescriptor {
    std::string name;
    std::string typeId;
    std::string scriptPath;
    std::string author;
    std::string version;
    bool canHaveChildren;
};

std::string getEnvOrDefault(const char *key, const char *fallback) {
    const char *v = std::getenv(key);
    if (v && v[0] != '\0') {
        return std::string(v);
    }
    return std::string(fallback);
}

bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescriptor &desc) {
    (void)scriptPath;
    (void)desc;
    return false;
}

const std::vector<NodeWidgetDescriptor> &nodeWidgetCatalog() {
    static std::vector<NodeWidgetDescriptor> catalog;
    static std::once_flag initialized;

    std::call_once(initialized, []() {
    std::vector<std::string> widgetDirs;

    const std::string dirsEnv = getEnvOrDefault("BLOGI_WEBEDIT_NODE_WIDGET_DIRS", "");
    if (!dirsEnv.empty()) {
        size_t start = 0;
        while (start <= dirsEnv.size()) {
            size_t pos = dirsEnv.find(':', start);
            std::string part = (pos == std::string::npos) ? dirsEnv.substr(start) : dirsEnv.substr(start, pos - start);
            if (!part.empty())
                widgetDirs.push_back(part);
            if (pos == std::string::npos)
                break;
            start = pos + 1;
        }
    }

    if (widgetDirs.empty()) {
        const std::string singleDir = getEnvOrDefault("BLOGI_WEBEDIT_NODE_WIDGET_DIR", "/usr/share/blogi-editor/node/widgets");
        if (!singleDir.empty())
            widgetDirs.push_back(singleDir);
    }

    std::set<std::string> namesSeen;
    std::error_code ec;

    for (const auto &widgetsDir : widgetDirs) {
        if (!std::filesystem::exists(widgetsDir, ec) || ec)
            continue;

        try {
            for (const auto &entry : std::filesystem::directory_iterator(widgetsDir, ec)) {
                if (ec) break;
                if (!entry.is_regular_file()) continue;
                if (entry.path().extension() != ".js") continue;

                const std::string fname = entry.path().filename().string();
                if (!fname.empty() && fname[0] == '_') continue;

                NodeWidgetDescriptor desc;
                desc.scriptPath = entry.path().string();
                desc.name = entry.path().stem().string();
                desc.typeId = desc.name;
                desc.author = "Jan Koester";
                desc.version = "1.0";
                desc.canHaveChildren = false;

                // Prefer metadata from the widget itself (incl. hardcoded UUID/type_id).
                NodeWidgetDescriptor meta = desc;
                if (queryNodeWidgetDescriptor(desc.scriptPath, meta)) {
                    if (!meta.name.empty()) desc.name = meta.name;
                    if (!meta.typeId.empty()) desc.typeId = meta.typeId;
                    if (!meta.author.empty()) desc.author = meta.author;
                    if (!meta.version.empty()) desc.version = meta.version;
                    desc.canHaveChildren = meta.canHaveChildren;
                }

                if (desc.name.empty())
                    continue;

                if (namesSeen.find(desc.name) != namesSeen.end())
                    continue;

                namesSeen.insert(desc.name);
                catalog.push_back(std::move(desc));
            }
        } catch (const std::exception &e) {
            std::cerr << "[webedit] failed to scan widget dir '" << widgetsDir
                      << "': " << e.what() << std::endl;
        } catch (...) {
            std::cerr << "[webedit] failed to scan widget dir '" << widgetsDir
                      << "': unknown exception" << std::endl;
        }
    }

    std::sort(catalog.begin(), catalog.end(), [](const NodeWidgetDescriptor &a, const NodeWidgetDescriptor &b) {
        return a.name < b.name;
    });

    });

    return catalog;
}

bool findNodeWidgetByName(const std::string &name, NodeWidgetDescriptor &out) {
    auto lower = [](std::string value) {
        std::transform(value.begin(), value.end(), value.begin(),
                       [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
        return value;
    };

    const auto &catalog = nodeWidgetCatalog();

    // Fast path: exact match.
    auto it = std::find_if(catalog.begin(), catalog.end(), [&](const NodeWidgetDescriptor &d) {
        return name == d.name;
    });

    // Compatibility path: older templates often store capitalized metadata names
    // (e.g. "TextBox") while the script file is lower-case ("textbox.js").
    if (it == catalog.end()) {
        const std::string wanted = lower(name);
        it = std::find_if(catalog.begin(), catalog.end(), [&](const NodeWidgetDescriptor &d) {
            return lower(d.name) == wanted;
        });
    }

    if (it == catalog.end()) {
        return false;
    }

    out = *it;
    return true;
}

bool findNodeWidgetByTypeId(const std::string &typeId, NodeWidgetDescriptor &out) {
    if (typeId.empty()) {
        return false;
    }

    auto lower = [](std::string value) {
        std::transform(value.begin(), value.end(), value.begin(),
                       [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
        return value;
    };

    const std::string wanted = lower(typeId);
    const auto &catalog = nodeWidgetCatalog();
    auto it = std::find_if(catalog.begin(), catalog.end(), [&](const NodeWidgetDescriptor &d) {
        return lower(d.typeId) == wanted;
    });

    if (it == catalog.end()) {
        return false;
    }

    out = *it;
    return true;
}

bool isNodeWidgetPluginName(const std::string &name) {
    return name == "NodeWidget" || name == "Nodewidget";
}

class EmbeddedNodeWidget final : public blogi::webedit::NodeWidgetAdapter {
public:
    explicit EmbeddedNodeWidget(const NodeWidgetDescriptor &desc)
        : NodeWidgetAdapter(
              desc.name.empty() ? "NodeWidget" : desc.name,
              desc.author,
              desc.version,
              desc.typeId,
              desc.scriptPath,
              desc.canHaveChildren) {}
};

constexpr const char *kSessionDocXmlKey = "webedit_doc_xml";
constexpr const char *kSessionDocIdKey = "webedit_doc_id";
@@ -649,15 +465,22 @@ void webedit::Api::handleGetPlugins(libhttppp::HttpRequest &curreq) {
    json_object *arr = json_object_new_array();

    try {
        const auto &catalog = nodeWidgetCatalog();
        for (const auto &desc : catalog) {
        auto *plg = _plugins.getFirstPlugin();
        while (plg) {
            auto *inst = plg->getInstace();
            if (!inst) {
                plg = plg->getNextPlg();
                continue;
            }

            json_object *obj = json_object_new_object();
            json_object_object_add(obj, "name", json_object_new_string(desc.name.c_str()));
            json_object_object_add(obj, "type_id", json_object_new_string(desc.typeId.c_str()));
            json_object_object_add(obj, "author", json_object_new_string(desc.author.c_str()));
            json_object_object_add(obj, "version", json_object_new_string(desc.version.c_str()));
            json_object_object_add(obj, "can_have_children", json_object_new_boolean(desc.canHaveChildren));
            json_object_object_add(obj, "name", json_object_new_string(inst->getName().c_str()));
            json_object_object_add(obj, "type_id", json_object_new_string(inst->getTypeId().c_str()));
            json_object_object_add(obj, "author", json_object_new_string(inst->getAuthor().c_str()));
            json_object_object_add(obj, "version", json_object_new_string(inst->getVersion().c_str()));
            json_object_object_add(obj, "can_have_children", json_object_new_boolean(inst->canHaveChildren()));
            json_object_array_add(arr, obj);
            plg = plg->getNextPlg();
        }
    } catch (const std::exception &e) {
        std::cerr << "[webedit] handleGetPlugins failed: " << e.what() << std::endl;
@@ -3176,16 +2999,44 @@ webedit::Api::NodeContext webedit::Api::findNodeContext(

blogi::webedit::EditPlugin *webedit::Api::createPluginByName(
        const std::string &name, DocumentState &doc, const std::string &typeIdHint) {
    NodeWidgetDescriptor desc;
    const blogi::webedit::EditPluginLoad::PluginData *match = nullptr;

    // Preferred mapping for persisted templates: unique hardcoded type_id.
    if (!typeIdHint.empty() && findNodeWidgetByTypeId(typeIdHint, desc)) {
        // resolved by UUID
    } else if (!findNodeWidgetByName(name, desc)) {
        return nullptr;
    auto lower = [](std::string value) {
        std::transform(value.begin(), value.end(), value.begin(),
                       [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
        return value;
    };

    if (!typeIdHint.empty()) {
        const std::string wanted = lower(typeIdHint);
        for (auto *plg = _plugins.getFirstPlugin(); plg; plg = plg->getNextPlg()) {
            auto *inst = plg->getInstace();
            if (!inst)
                continue;
            if (lower(inst->getTypeId().c_str()) == wanted) {
                match = plg;
                break;
            }
        }
    }

    auto *newEl = new EmbeddedNodeWidget(desc);
    if (!match) {
        const std::string wantedName = lower(name);
        for (auto *plg = _plugins.getFirstPlugin(); plg; plg = plg->getNextPlg()) {
            auto *inst = plg->getInstace();
            if (!inst)
                continue;
            if (lower(inst->getName()) == wantedName) {
                match = plg;
                break;
            }
        }
    }

    if (!match)
        return nullptr;

    auto *newEl = match->createNewInstance();
    if (!newEl)
        return nullptr;

+15 −1
Original line number Diff line number Diff line
@@ -24,4 +24,18 @@ if(NOT TARGET blogidev)
    endif()
endif()

add_subdirectory(nodewidget)
add_subdirectory(container)
add_subdirectory(textbox)
add_subdirectory(grid)
add_subdirectory(table)
add_subdirectory(image)
add_subdirectory(button)
add_subdirectory(customhtml)
add_subdirectory(section)
add_subdirectory(video)
add_subdirectory(survey)
add_subdirectory(list)
add_subdirectory(article)
add_subdirectory(slider)
add_subdirectory(popup)
add_subdirectory(accordion)
Loading