Loading editor/src/webedit_api.cpp +97 −5 Changes for editor/src/webedit_api.cpp: 97 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -3421,16 +3421,18 @@ void webedit::Api::handlePublishToConnection(libhttppp::HttpRequest &curreq, return; } std::string docXml = exportTreeXml(doc); // Auto-publish surveys embedded in this document to the target blogi first, // so they exist server-side before the rendered HTML references them. { std::string domain; _session.getData(sessionid, "domain", domain); std::string docXml = exportTreeXml(doc); autoPublishSurveysForDocument(docXml, authid, blogUrl, domain); } std::string html = renderTree(doc.root); std::string llmsTxt = generateLlmsTxt(docXml); // Build the publish command: // [{"authid":"..."}, {"command":"publish","target":"template","html":"...","params":{...}}] Loading @@ -3440,14 +3442,19 @@ void webedit::Api::handlePublishToConnection(libhttppp::HttpRequest &curreq, json_object_object_add(authCmd, "authid", json_object_new_string(authid.c_str())); json_object_array_add(publishArr, authCmd); bool ownsParams = false; if (!paramsObj) { paramsObj = json_object_new_object(); ownsParams = true; } if (!llmsTxt.empty()) json_object_object_add(paramsObj, "llms_txt", json_object_new_string(llmsTxt.c_str())); json_object *publishCmd = json_object_new_object(); json_object_object_add(publishCmd, "command", json_object_new_string("publish")); json_object_object_add(publishCmd, "target", json_object_new_string(target.c_str())); json_object_object_add(publishCmd, "html", json_object_new_string(html.c_str())); if (paramsObj) json_object_object_add(publishCmd, "params", json_object_get(paramsObj)); else json_object_object_add(publishCmd, "params", json_object_new_object()); json_object_object_add(publishCmd, "params", ownsParams ? paramsObj : json_object_get(paramsObj)); json_object_array_add(publishArr, publishCmd); try { Loading Loading @@ -3890,6 +3897,91 @@ std::string webedit::Api::exportTreeXml(DocumentState &doc) { return printer.CStr(); } namespace { // Widget attributes that carry human-readable content (button/faq/image labels etc.) // rather than styling. Element text (tinyxml2 GetText()) is picked up separately — // that's how content-heavy widgets (Textbox, Article, List items, Table cells) store // their body text. const std::set<std::string> kLlmsTextAttributes = { "label", "title", "question", "answer", "alt" }; void appendLlmsText(std::string &out, const std::string &text) { std::string trimmed = text; size_t start = trimmed.find_first_not_of(" \t\r\n"); size_t end = trimmed.find_last_not_of(" \t\r\n"); if (start == std::string::npos) return; trimmed = trimmed.substr(start, end - start + 1); if (trimmed.empty()) return; if (!out.empty()) out += "\n\n"; out += trimmed; } // Strips HTML markup from a fragment by parsing it and concatenating text nodes, // falling back to the raw string if it isn't valid HTML. std::string stripHtml(const std::string &html) { if (html.find('<') == std::string::npos) return html; try { libhtmlpp::HtmlString parsed(html); libhtmlpp::Element &root = parsed.parse(); std::string out; std::vector<libhtmlpp::Element*> stack; stack.push_back(&root); while (!stack.empty()) { libhtmlpp::Element *el = stack.back(); stack.pop_back(); for (libhtmlpp::Element *cur = el; cur; cur = cur->nextElement()) { if (cur->getType() == libhtmlpp::TextEl) { if (!out.empty()) out += " "; out += static_cast<libhtmlpp::TextElement*>(cur)->getText(); } else if (cur->getType() == libhtmlpp::HtmlEl) { libhtmlpp::HtmlElement *hel = static_cast<libhtmlpp::HtmlElement*>(cur); std::string tag = hel->getTagname(); if (tag != "style" && tag != "script" && hel->firstChild()) stack.push_back(hel->firstChild()); } } } return out; } catch (...) { return html; } } void walkLlmsXml(const tinyxml2::XMLElement *el, std::string &out) { for (const tinyxml2::XMLElement *cur = el; cur; cur = cur->NextSiblingElement()) { std::string tag = cur->Name() ? cur->Name() : ""; if (tag == "CustomCss") continue; for (const tinyxml2::XMLAttribute *attr = cur->FirstAttribute(); attr; attr = attr->Next()) { if (kLlmsTextAttributes.count(attr->Name()) && attr->Value() && attr->Value()[0]) appendLlmsText(out, attr->Value()); } const char *text = cur->GetText(); if (text && text[0]) appendLlmsText(out, stripHtml(text)); if (const tinyxml2::XMLElement *child = cur->FirstChildElement()) walkLlmsXml(child, out); } } } std::string webedit::Api::generateLlmsTxt(const std::string &xml) { tinyxml2::XMLDocument xmlDoc; if (xmlDoc.Parse(xml.c_str(), xml.size()) != tinyxml2::XML_SUCCESS) return ""; const tinyxml2::XMLElement *root = xmlDoc.RootElement(); if (!root) return ""; std::string out; if (const tinyxml2::XMLElement *child = root->FirstChildElement()) walkLlmsXml(child, out); return out; } std::string webedit::Api::serializeElementXml(blogi::webedit::EditPlugin *node) { tinyxml2::XMLDocument xmlDoc; auto *root = xmlDoc.NewElement("WebEditor"); Loading editor/src/webedit_api.h +1 −0 Changes for editor/src/webedit_api.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -181,6 +181,7 @@ namespace webedit { std::string renderTree(const blogi::webedit::EditPlugin *node); std::string exportTreeXml(DocumentState &doc); std::string serializeElementXml(blogi::webedit::EditPlugin *node); std::string generateLlmsTxt(const std::string &xml); void persistDocumentStateToSession(const std::string &sessionid, DocumentState &doc); void restoreDocumentStateFromSession(const std::string &sessionid, DocumentState &doc); void loadDocumentTreeFromXml(DocumentState &doc, const std::string &xml); Loading Loading
editor/src/webedit_api.cpp +97 −5 Changes for editor/src/webedit_api.cpp: 97 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -3421,16 +3421,18 @@ void webedit::Api::handlePublishToConnection(libhttppp::HttpRequest &curreq, return; } std::string docXml = exportTreeXml(doc); // Auto-publish surveys embedded in this document to the target blogi first, // so they exist server-side before the rendered HTML references them. { std::string domain; _session.getData(sessionid, "domain", domain); std::string docXml = exportTreeXml(doc); autoPublishSurveysForDocument(docXml, authid, blogUrl, domain); } std::string html = renderTree(doc.root); std::string llmsTxt = generateLlmsTxt(docXml); // Build the publish command: // [{"authid":"..."}, {"command":"publish","target":"template","html":"...","params":{...}}] Loading @@ -3440,14 +3442,19 @@ void webedit::Api::handlePublishToConnection(libhttppp::HttpRequest &curreq, json_object_object_add(authCmd, "authid", json_object_new_string(authid.c_str())); json_object_array_add(publishArr, authCmd); bool ownsParams = false; if (!paramsObj) { paramsObj = json_object_new_object(); ownsParams = true; } if (!llmsTxt.empty()) json_object_object_add(paramsObj, "llms_txt", json_object_new_string(llmsTxt.c_str())); json_object *publishCmd = json_object_new_object(); json_object_object_add(publishCmd, "command", json_object_new_string("publish")); json_object_object_add(publishCmd, "target", json_object_new_string(target.c_str())); json_object_object_add(publishCmd, "html", json_object_new_string(html.c_str())); if (paramsObj) json_object_object_add(publishCmd, "params", json_object_get(paramsObj)); else json_object_object_add(publishCmd, "params", json_object_new_object()); json_object_object_add(publishCmd, "params", ownsParams ? paramsObj : json_object_get(paramsObj)); json_object_array_add(publishArr, publishCmd); try { Loading Loading @@ -3890,6 +3897,91 @@ std::string webedit::Api::exportTreeXml(DocumentState &doc) { return printer.CStr(); } namespace { // Widget attributes that carry human-readable content (button/faq/image labels etc.) // rather than styling. Element text (tinyxml2 GetText()) is picked up separately — // that's how content-heavy widgets (Textbox, Article, List items, Table cells) store // their body text. const std::set<std::string> kLlmsTextAttributes = { "label", "title", "question", "answer", "alt" }; void appendLlmsText(std::string &out, const std::string &text) { std::string trimmed = text; size_t start = trimmed.find_first_not_of(" \t\r\n"); size_t end = trimmed.find_last_not_of(" \t\r\n"); if (start == std::string::npos) return; trimmed = trimmed.substr(start, end - start + 1); if (trimmed.empty()) return; if (!out.empty()) out += "\n\n"; out += trimmed; } // Strips HTML markup from a fragment by parsing it and concatenating text nodes, // falling back to the raw string if it isn't valid HTML. std::string stripHtml(const std::string &html) { if (html.find('<') == std::string::npos) return html; try { libhtmlpp::HtmlString parsed(html); libhtmlpp::Element &root = parsed.parse(); std::string out; std::vector<libhtmlpp::Element*> stack; stack.push_back(&root); while (!stack.empty()) { libhtmlpp::Element *el = stack.back(); stack.pop_back(); for (libhtmlpp::Element *cur = el; cur; cur = cur->nextElement()) { if (cur->getType() == libhtmlpp::TextEl) { if (!out.empty()) out += " "; out += static_cast<libhtmlpp::TextElement*>(cur)->getText(); } else if (cur->getType() == libhtmlpp::HtmlEl) { libhtmlpp::HtmlElement *hel = static_cast<libhtmlpp::HtmlElement*>(cur); std::string tag = hel->getTagname(); if (tag != "style" && tag != "script" && hel->firstChild()) stack.push_back(hel->firstChild()); } } } return out; } catch (...) { return html; } } void walkLlmsXml(const tinyxml2::XMLElement *el, std::string &out) { for (const tinyxml2::XMLElement *cur = el; cur; cur = cur->NextSiblingElement()) { std::string tag = cur->Name() ? cur->Name() : ""; if (tag == "CustomCss") continue; for (const tinyxml2::XMLAttribute *attr = cur->FirstAttribute(); attr; attr = attr->Next()) { if (kLlmsTextAttributes.count(attr->Name()) && attr->Value() && attr->Value()[0]) appendLlmsText(out, attr->Value()); } const char *text = cur->GetText(); if (text && text[0]) appendLlmsText(out, stripHtml(text)); if (const tinyxml2::XMLElement *child = cur->FirstChildElement()) walkLlmsXml(child, out); } } } std::string webedit::Api::generateLlmsTxt(const std::string &xml) { tinyxml2::XMLDocument xmlDoc; if (xmlDoc.Parse(xml.c_str(), xml.size()) != tinyxml2::XML_SUCCESS) return ""; const tinyxml2::XMLElement *root = xmlDoc.RootElement(); if (!root) return ""; std::string out; if (const tinyxml2::XMLElement *child = root->FirstChildElement()) walkLlmsXml(child, out); return out; } std::string webedit::Api::serializeElementXml(blogi::webedit::EditPlugin *node) { tinyxml2::XMLDocument xmlDoc; auto *root = xmlDoc.NewElement("WebEditor"); Loading
editor/src/webedit_api.h +1 −0 Changes for editor/src/webedit_api.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -181,6 +181,7 @@ namespace webedit { std::string renderTree(const blogi::webedit::EditPlugin *node); std::string exportTreeXml(DocumentState &doc); std::string serializeElementXml(blogi::webedit::EditPlugin *node); std::string generateLlmsTxt(const std::string &xml); void persistDocumentStateToSession(const std::string &sessionid, DocumentState &doc); void restoreDocumentStateFromSession(const std::string &sessionid, DocumentState &doc); void loadDocumentTreeFromXml(DocumentState &doc, const std::string &xml); Loading