Loading editor/CMakeLists.txt +16 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,22 @@ add_executable(blogi-editor src/htmlimport.cpp ) find_path(LIBNODE_INCLUDE_DIR NAMES node.h PATH_SUFFIXES node include/node) find_library(LIBNODE_LIBRARY NAMES node libnode) if(NOT LIBNODE_INCLUDE_DIR OR NOT LIBNODE_LIBRARY) message(FATAL_ERROR "libnode is required but was not found. Install libnode headers/library and reconfigure.") endif() message(STATUS "libnode enabled: ${LIBNODE_LIBRARY} (${LIBNODE_INCLUDE_DIR})") target_include_directories(blogi-editor PRIVATE ${LIBNODE_INCLUDE_DIR}) target_link_libraries(blogi-editor ${LIBNODE_LIBRARY}) target_compile_definitions(blogi-editor PRIVATE BLOGI_HAVE_LIBNODE=1) # Export to widget subdirectories that include the shared node bridge header. set(BLOGI_LIBNODE_INCLUDE_DIR "${LIBNODE_INCLUDE_DIR}" CACHE INTERNAL "blogi libnode include dir") set(BLOGI_LIBNODE_LIBRARY "${LIBNODE_LIBRARY}" CACHE INTERNAL "blogi libnode library") if(TARGET blogidev) target_link_libraries(blogi-editor blogidev Loading editor/config.yaml +0 −2 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ WEBEDIT: PREFIX: "" LANGUAGE: "DE" NODE: BINARY: "/usr/lib/blogi-editor/node/bin/node" RUNNER: "/usr/share/blogi-editor/node/node_widget_runner.js" WIDGETDIR: - "/usr/share/blogi-editor/node/widgets" AUTHDB: Loading editor/src/main.cpp +0 −8 Original line number Diff line number Diff line Loading @@ -77,14 +77,6 @@ int main(int argc, char *argv[]) { std::cout << "Loading config from: " << configPath << std::endl; webedit::Config config(configPath); if (!config.getNodeBinary().empty()) { setenv("BLOGI_WEBEDIT_NODE_BIN", config.getNodeBinary().c_str(), 1); std::cout << "Configured Node.js binary: " << config.getNodeBinary() << std::endl; } if (!config.getNodeRunner().empty()) { setenv("BLOGI_WEBEDIT_NODE_RUNNER", config.getNodeRunner().c_str(), 1); std::cout << "Configured Node.js widget runner: " << config.getNodeRunner() << std::endl; } { const auto &dirs = config.getNodeWidgetDirs(); std::string joined; Loading editor/src/node_bridge.h 0 → 100644 +80 −0 Original line number Diff line number Diff line #pragma once #include <cstdlib> #include <string> #include <vector> #ifndef BLOGI_HAVE_LIBNODE #error "BLOGI_HAVE_LIBNODE must be defined: this build supports only embedded libnode" #endif #include <node.h> namespace blogi::webedit::nodebridge { inline 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); } inline int runNodeRunner(const std::string &reqPath, const std::string &resPath) { static const std::string bridgeScript = "'use strict';" "const fs=require('fs');" "const path=require('path');" "(async()=>{" "const[, ,requestFile,responseFile]=process.argv;" "if(!requestFile||!responseFile)throw new Error('Usage: node -e <bridge> <request.json> <response.json>');" "const req=JSON.parse(fs.readFileSync(requestFile,'utf8'));" "const hook=(typeof req.hook==='string'&&req.hook)?req.hook:'render';" "if(!req.script_path)throw new Error('request.script_path is required');" "const scriptPath=path.resolve(req.script_path);" "const mod=require(scriptPath);" "const resolveHook=(m,h)=>{" "if(h==='render'){if(typeof m==='function')return m;if(typeof m.render==='function')return m.render;return null;}" "if(typeof m[h]==='function')return m[h];" "if(m.hooks&&typeof m.hooks[h]==='function')return m.hooks[h];" "return null;" "};" "const fn=resolveHook(mod,hook);" "if(!fn){fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:true,result:null}),'utf8');return;}" "const result=await Promise.resolve(fn(req));" "if(hook==='metadata'&&result&&typeof result==='object'){" "if(!result.type_id||typeof result.type_id!=='string'||!result.type_id.trim()){" "const scriptBase=path.basename(scriptPath,path.extname(scriptPath));" "const widgetName=(typeof result.name==='string'&&result.name.trim())?result.name.trim():scriptBase;" "if(typeof mod.getUUID==='function')result.type_id=String(mod.getUUID());" "if(!result.type_id||!result.type_id.trim())throw new Error('Widget '+widgetName+' has no hardcoded UUID (type_id/getUUID missing)');" "}" "}" "if(hook==='render'){" "let html='';" "if(typeof result==='string')html=result;" "else if(result&&typeof result==='object'&&typeof result.html==='string')html=result.html;" "else throw new Error('render() must return a string or { html: string }');" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',html,result:{html}}),'utf8');" "return;" "}" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:false,result}),'utf8');" "})().catch((err)=>{" "const[, , ,responseFile]=process.argv;" "const payload=JSON.stringify({status:'error',message:(err&&err.message)?err.message:'Unknown Node runner error'});" "if(responseFile){try{fs.writeFileSync(responseFile,payload,'utf8');}catch(_){process.stderr.write(payload+'\\n');}}" "else{process.stderr.write(payload+'\\n');}" "process.exitCode=1;" "});"; std::vector<std::string> args = {"node", "-e", bridgeScript, reqPath, resPath}; std::vector<char *> argv; argv.reserve(args.size()); for (auto &arg : args) { argv.push_back(const_cast<char *>(arg.c_str())); } return node::Start(static_cast<int>(argv.size()), argv.data()); } } // namespace blogi::webedit::nodebridge editor/src/webedit_api.cpp +3 −18 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ #include "webedit_api.h" #include "htmlimport.h" #include "node_bridge.h" namespace { struct NodeWidgetDescriptor { Loading @@ -57,20 +58,7 @@ struct NodeWidgetDescriptor { }; 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); } std::string quoteShell(const std::string &value) { std::string out = "'"; for (char c : value) { if (c == '\'') out += "'\\''"; else out += c; } out += "'"; return out; return blogi::webedit::nodebridge::getEnvOrDefault(key, fallback); } std::string makeTempPath(const std::string &name) { Loading @@ -94,8 +82,6 @@ std::string readFile(const std::string &path) { } bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescriptor &desc) { std::string nodeBin = getEnvOrDefault("BLOGI_WEBEDIT_NODE_BIN", "node"); std::string runner = getEnvOrDefault("BLOGI_WEBEDIT_NODE_RUNNER", "/usr/share/blogi-editor/node/node_widget_runner.js"); std::string reqPath = makeTempPath("request.json"); std::string resPath = makeTempPath("response.json"); Loading @@ -112,8 +98,7 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript writeFile(reqPath, reqJson ? reqJson : "{}"); json_object_put(req); std::string cmd = quoteShell(nodeBin) + " " + quoteShell(runner) + " " + quoteShell(reqPath) + " " + quoteShell(resPath); (void)std::system(cmd.c_str()); (void)blogi::webedit::nodebridge::runNodeRunner(reqPath, resPath); std::string raw = readFile(resPath); json_object *res = raw.empty() ? nullptr : json_tokener_parse(raw.c_str()); Loading Loading
editor/CMakeLists.txt +16 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,22 @@ add_executable(blogi-editor src/htmlimport.cpp ) find_path(LIBNODE_INCLUDE_DIR NAMES node.h PATH_SUFFIXES node include/node) find_library(LIBNODE_LIBRARY NAMES node libnode) if(NOT LIBNODE_INCLUDE_DIR OR NOT LIBNODE_LIBRARY) message(FATAL_ERROR "libnode is required but was not found. Install libnode headers/library and reconfigure.") endif() message(STATUS "libnode enabled: ${LIBNODE_LIBRARY} (${LIBNODE_INCLUDE_DIR})") target_include_directories(blogi-editor PRIVATE ${LIBNODE_INCLUDE_DIR}) target_link_libraries(blogi-editor ${LIBNODE_LIBRARY}) target_compile_definitions(blogi-editor PRIVATE BLOGI_HAVE_LIBNODE=1) # Export to widget subdirectories that include the shared node bridge header. set(BLOGI_LIBNODE_INCLUDE_DIR "${LIBNODE_INCLUDE_DIR}" CACHE INTERNAL "blogi libnode include dir") set(BLOGI_LIBNODE_LIBRARY "${LIBNODE_LIBRARY}" CACHE INTERNAL "blogi libnode library") if(TARGET blogidev) target_link_libraries(blogi-editor blogidev Loading
editor/config.yaml +0 −2 Original line number Diff line number Diff line Loading @@ -17,8 +17,6 @@ WEBEDIT: PREFIX: "" LANGUAGE: "DE" NODE: BINARY: "/usr/lib/blogi-editor/node/bin/node" RUNNER: "/usr/share/blogi-editor/node/node_widget_runner.js" WIDGETDIR: - "/usr/share/blogi-editor/node/widgets" AUTHDB: Loading
editor/src/main.cpp +0 −8 Original line number Diff line number Diff line Loading @@ -77,14 +77,6 @@ int main(int argc, char *argv[]) { std::cout << "Loading config from: " << configPath << std::endl; webedit::Config config(configPath); if (!config.getNodeBinary().empty()) { setenv("BLOGI_WEBEDIT_NODE_BIN", config.getNodeBinary().c_str(), 1); std::cout << "Configured Node.js binary: " << config.getNodeBinary() << std::endl; } if (!config.getNodeRunner().empty()) { setenv("BLOGI_WEBEDIT_NODE_RUNNER", config.getNodeRunner().c_str(), 1); std::cout << "Configured Node.js widget runner: " << config.getNodeRunner() << std::endl; } { const auto &dirs = config.getNodeWidgetDirs(); std::string joined; Loading
editor/src/node_bridge.h 0 → 100644 +80 −0 Original line number Diff line number Diff line #pragma once #include <cstdlib> #include <string> #include <vector> #ifndef BLOGI_HAVE_LIBNODE #error "BLOGI_HAVE_LIBNODE must be defined: this build supports only embedded libnode" #endif #include <node.h> namespace blogi::webedit::nodebridge { inline 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); } inline int runNodeRunner(const std::string &reqPath, const std::string &resPath) { static const std::string bridgeScript = "'use strict';" "const fs=require('fs');" "const path=require('path');" "(async()=>{" "const[, ,requestFile,responseFile]=process.argv;" "if(!requestFile||!responseFile)throw new Error('Usage: node -e <bridge> <request.json> <response.json>');" "const req=JSON.parse(fs.readFileSync(requestFile,'utf8'));" "const hook=(typeof req.hook==='string'&&req.hook)?req.hook:'render';" "if(!req.script_path)throw new Error('request.script_path is required');" "const scriptPath=path.resolve(req.script_path);" "const mod=require(scriptPath);" "const resolveHook=(m,h)=>{" "if(h==='render'){if(typeof m==='function')return m;if(typeof m.render==='function')return m.render;return null;}" "if(typeof m[h]==='function')return m[h];" "if(m.hooks&&typeof m.hooks[h]==='function')return m.hooks[h];" "return null;" "};" "const fn=resolveHook(mod,hook);" "if(!fn){fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:true,result:null}),'utf8');return;}" "const result=await Promise.resolve(fn(req));" "if(hook==='metadata'&&result&&typeof result==='object'){" "if(!result.type_id||typeof result.type_id!=='string'||!result.type_id.trim()){" "const scriptBase=path.basename(scriptPath,path.extname(scriptPath));" "const widgetName=(typeof result.name==='string'&&result.name.trim())?result.name.trim():scriptBase;" "if(typeof mod.getUUID==='function')result.type_id=String(mod.getUUID());" "if(!result.type_id||!result.type_id.trim())throw new Error('Widget '+widgetName+' has no hardcoded UUID (type_id/getUUID missing)');" "}" "}" "if(hook==='render'){" "let html='';" "if(typeof result==='string')html=result;" "else if(result&&typeof result==='object'&&typeof result.html==='string')html=result.html;" "else throw new Error('render() must return a string or { html: string }');" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',html,result:{html}}),'utf8');" "return;" "}" "fs.writeFileSync(responseFile,JSON.stringify({status:'success',missing_hook:false,result}),'utf8');" "})().catch((err)=>{" "const[, , ,responseFile]=process.argv;" "const payload=JSON.stringify({status:'error',message:(err&&err.message)?err.message:'Unknown Node runner error'});" "if(responseFile){try{fs.writeFileSync(responseFile,payload,'utf8');}catch(_){process.stderr.write(payload+'\\n');}}" "else{process.stderr.write(payload+'\\n');}" "process.exitCode=1;" "});"; std::vector<std::string> args = {"node", "-e", bridgeScript, reqPath, resPath}; std::vector<char *> argv; argv.reserve(args.size()); for (auto &arg : args) { argv.push_back(const_cast<char *>(arg.c_str())); } return node::Start(static_cast<int>(argv.size()), argv.data()); } } // namespace blogi::webedit::nodebridge
editor/src/webedit_api.cpp +3 −18 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ #include "webedit_api.h" #include "htmlimport.h" #include "node_bridge.h" namespace { struct NodeWidgetDescriptor { Loading @@ -57,20 +58,7 @@ struct NodeWidgetDescriptor { }; 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); } std::string quoteShell(const std::string &value) { std::string out = "'"; for (char c : value) { if (c == '\'') out += "'\\''"; else out += c; } out += "'"; return out; return blogi::webedit::nodebridge::getEnvOrDefault(key, fallback); } std::string makeTempPath(const std::string &name) { Loading @@ -94,8 +82,6 @@ std::string readFile(const std::string &path) { } bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescriptor &desc) { std::string nodeBin = getEnvOrDefault("BLOGI_WEBEDIT_NODE_BIN", "node"); std::string runner = getEnvOrDefault("BLOGI_WEBEDIT_NODE_RUNNER", "/usr/share/blogi-editor/node/node_widget_runner.js"); std::string reqPath = makeTempPath("request.json"); std::string resPath = makeTempPath("response.json"); Loading @@ -112,8 +98,7 @@ bool queryNodeWidgetDescriptor(const std::string &scriptPath, NodeWidgetDescript writeFile(reqPath, reqJson ? reqJson : "{}"); json_object_put(req); std::string cmd = quoteShell(nodeBin) + " " + quoteShell(runner) + " " + quoteShell(reqPath) + " " + quoteShell(resPath); (void)std::system(cmd.c_str()); (void)blogi::webedit::nodebridge::runNodeRunner(reqPath, resPath); std::string raw = readFile(resPath); json_object *res = raw.empty() ? nullptr : json_tokener_parse(raw.c_str()); Loading