Loading editor/CMakeLists.txt +0 −3 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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) editor/src/main.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -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) { Loading editor/src/nodewidget_adapter_stub.cpp +112 −5 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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() << "): " Loading Loading @@ -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")); Loading Loading @@ -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; Loading editor/src/webedit_api.cpp +49 −198 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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; Loading Loading @@ -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; Loading editor/widgets/CMakeLists.txt +15 −1 Original line number Diff line number Diff line Loading @@ -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
editor/CMakeLists.txt +0 −3 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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)
editor/src/main.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -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) { Loading
editor/src/nodewidget_adapter_stub.cpp +112 −5 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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() << "): " Loading Loading @@ -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")); Loading Loading @@ -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; Loading
editor/src/webedit_api.cpp +49 −198 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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; Loading Loading @@ -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; Loading
editor/widgets/CMakeLists.txt +15 −1 Original line number Diff line number Diff line Loading @@ -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)