Loading editor/src/htmlimport.cpp +42 −32 Changes for editor/src/htmlimport.cpp: 42 added lines, 32 removed lines. Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ #include <arpa/inet.h> #include <zlib.h> #include <uuidp.h> using namespace blogi::htmlimport; Loading Loading @@ -665,8 +666,7 @@ std::string blogi::htmlimport::extractCustomCss( std::map<std::string,std::string> &props, const std::vector<std::string> &knownKeys, const std::string &mediaRules, const std::string &cssClass, const std::string &elemId) std::string &outInstanceId) { std::string plainDecls; std::vector<std::string> toRemove; Loading @@ -685,28 +685,18 @@ std::string blogi::htmlimport::extractCustomCss( std::string customCss; if (!plainDecls.empty()) { // The widgets that render CustomCss (Section/Container/Article/Grid) // emit this field's text verbatim inside a <style> tag, so it needs // its own selector -- bare declarations are invalid at stylesheet // top level and get silently dropped by the browser. The element's // own class/id (captured at import time) is also applied onto the // rendered widget alongside its generated "we-<uuid>" class (see // e.g. section.cpp's Render()), so scoping to it here reliably // matches. With neither class nor id there's no stable selector to // hang these declarations on, so they're dropped (same as before). std::string selector; if (!elemId.empty()) { selector = "#" + elemId; } else if (!cssClass.empty()) { std::istringstream iss(cssClass); std::string firstClass; iss >> firstClass; if (!firstClass.empty()) selector = "." + firstClass; } if (!selector.empty()) { // The widgets that render CustomCss emit this field's text verbatim // inside a <style> tag, so it needs its own selector -- bare // declarations are invalid at stylesheet top level and get silently // dropped by the browser. See this function's header doc comment // for why that selector is this element's own "we-<uuid>" auto-class // rather than its source class/id. uuid::uuid id; id.generate(); outInstanceId = id.c_str(); std::string selector = ".we-" + outInstanceId; customCss = selector + " { " + plainDecls + " }"; } } if (!mediaRules.empty()) { if (!customCss.empty()) customCss += " "; Loading Loading @@ -993,7 +983,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( } } applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1036,7 +1028,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( } } applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1089,7 +1083,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( knownCss.push_back("align-items"); } } std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading @@ -1107,7 +1103,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( } } applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1140,7 +1138,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( std::vector<std::string> knownCss = { "width","height","object-fit","border-radius","border", "display","visibility"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading @@ -1160,7 +1160,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( w->SetAttribute("height", cssProps["height"].c_str()); std::vector<std::string> knownCss = {"width","height","display"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1303,7 +1305,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( std::vector<std::string> knownCss = { "border","background-color","border-collapse","width","height", "display","visibility"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); std::string structureCss = collectTableStructureCss(sheet, usedClasses); if (!structureCss.empty()) { if (!custom.empty()) custom += " "; Loading Loading @@ -1353,7 +1357,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( "font-size","color","line-height","list-style-type", "padding","margin","width","height","display","visibility","gap"}; applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1410,7 +1416,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( "background-color","color","font-size","padding", "border-radius","border","width","height","display", "visibility","text-decoration","cursor","text-align"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1469,7 +1477,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( "font-family","font-size","color","text-align", "font-weight","font-style","width","height", "padding","margin","line-height"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading editor/src/htmlimport.h +26 −2 Changes for editor/src/htmlimport.h: 26 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -72,12 +72,36 @@ namespace htmlimport { int jsFailed = 0; }; /** Pulls every property in @p props that isn't one of @p knownKeys * (i.e. has no widget-attribute equivalent) out into a standalone CSS * rule text, scoped to this element's own "we-<uuid>" auto-class -- * the same class every widget's Render() unconditionally applies to * its rendered root node (see e.g. Section/Container/TextBox's own * Render()), regardless of widget type. This is deliberate, not a * fallback: only Section/Container/Article/Grid/Table ever forward an * imported class/id onto the rendered element at all (see their own * cssClass member/Render()); TextBox/List/Image/Video do not, so a * selector built from the source element's class/id would silently * never match on those and the "leftover" CSS extracted here would be * dead weight. Scoping to we-<uuid> instead works uniformly. * Since that class only exists once the widget XML this call is * building is actually loaded into a widget instance -- which happens * later, in a different function entirely (see loadChildrenFromXml / * loadDocumentTreeFromXml in webedit_api.cpp) -- the uuid has to be * picked here, at XML-generation time, and threaded through: if there * is anything to scope, a fresh one is generated and written to * @p outInstanceId (left untouched otherwise) for the caller to set * as this XML element's own `instance_id` attribute. Every loader that * honors `instance_id` (loadChildrenFromXml's preserveInstanceIds, * loadDocumentTreeFromXml, and handleImportHtml's own top-level loop) * assigns exactly this id to the resulting widget instance instead of * generating a random one, which is what makes the selector chosen * here still correct once rendered. */ std::string extractCustomCss( std::map<std::string,std::string> &props, const std::vector<std::string> &knownKeys, const std::string &mediaRules, const std::string &cssClass, const std::string &elemId); std::string &outInstanceId); std::string serializeElement(const libhtmlpp::Element *el); std::string serializeInnerHtml(libhtmlpp::HtmlElement *el); Loading editor/src/webedit_api.cpp +8 −1 Changes for editor/src/webedit_api.cpp: 8 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -5509,6 +5509,13 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, if (!el) continue; el->fromXmlObject(child); // Honor the instance_id htmlimport.cpp assigned to this element (see // extractCustomCss's doc comment) -- without this, the widget gets a // fresh random id here and any CustomCss scoped to "we-<that uuid>" // silently never matches its rendered class. const char *instanceIdAttr = child->Attribute("instance_id"); if (instanceIdAttr && instanceIdAttr[0]) el->setInstanceId(instanceIdAttr); if (insertParent) { // Child mode: add as children of target element Loading @@ -5530,7 +5537,7 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, } lastTop = el; loadChildrenFromXml(child, el, doc); loadChildrenFromXml(child, el, doc, /*preserveInstanceIds=*/true); } json_object *resp = json_object_new_object(); Loading Loading
editor/src/htmlimport.cpp +42 −32 Changes for editor/src/htmlimport.cpp: 42 added lines, 32 removed lines. Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ #include <arpa/inet.h> #include <zlib.h> #include <uuidp.h> using namespace blogi::htmlimport; Loading Loading @@ -665,8 +666,7 @@ std::string blogi::htmlimport::extractCustomCss( std::map<std::string,std::string> &props, const std::vector<std::string> &knownKeys, const std::string &mediaRules, const std::string &cssClass, const std::string &elemId) std::string &outInstanceId) { std::string plainDecls; std::vector<std::string> toRemove; Loading @@ -685,28 +685,18 @@ std::string blogi::htmlimport::extractCustomCss( std::string customCss; if (!plainDecls.empty()) { // The widgets that render CustomCss (Section/Container/Article/Grid) // emit this field's text verbatim inside a <style> tag, so it needs // its own selector -- bare declarations are invalid at stylesheet // top level and get silently dropped by the browser. The element's // own class/id (captured at import time) is also applied onto the // rendered widget alongside its generated "we-<uuid>" class (see // e.g. section.cpp's Render()), so scoping to it here reliably // matches. With neither class nor id there's no stable selector to // hang these declarations on, so they're dropped (same as before). std::string selector; if (!elemId.empty()) { selector = "#" + elemId; } else if (!cssClass.empty()) { std::istringstream iss(cssClass); std::string firstClass; iss >> firstClass; if (!firstClass.empty()) selector = "." + firstClass; } if (!selector.empty()) { // The widgets that render CustomCss emit this field's text verbatim // inside a <style> tag, so it needs its own selector -- bare // declarations are invalid at stylesheet top level and get silently // dropped by the browser. See this function's header doc comment // for why that selector is this element's own "we-<uuid>" auto-class // rather than its source class/id. uuid::uuid id; id.generate(); outInstanceId = id.c_str(); std::string selector = ".we-" + outInstanceId; customCss = selector + " { " + plainDecls + " }"; } } if (!mediaRules.empty()) { if (!customCss.empty()) customCss += " "; Loading Loading @@ -993,7 +983,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( } } applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1036,7 +1028,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( } } applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1089,7 +1083,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( knownCss.push_back("align-items"); } } std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading @@ -1107,7 +1103,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( } } applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1140,7 +1138,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( std::vector<std::string> knownCss = { "width","height","object-fit","border-radius","border", "display","visibility"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading @@ -1160,7 +1160,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( w->SetAttribute("height", cssProps["height"].c_str()); std::vector<std::string> knownCss = {"width","height","display"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1303,7 +1305,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( std::vector<std::string> knownCss = { "border","background-color","border-collapse","width","height", "display","visibility"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); std::string structureCss = collectTableStructureCss(sheet, usedClasses); if (!structureCss.empty()) { if (!custom.empty()) custom += " "; Loading Loading @@ -1353,7 +1357,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( "font-size","color","line-height","list-style-type", "padding","margin","width","height","display","visibility","gap"}; applyFlexLayoutAttributes(w, cssProps, knownCss); std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1410,7 +1416,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( "background-color","color","font-size","padding", "border-radius","border","width","height","display", "visibility","text-decoration","cursor","text-align"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading Loading @@ -1469,7 +1477,9 @@ void blogi::htmlimport::htmlElementToWidgetXml( "font-family","font-size","color","text-align", "font-weight","font-style","width","height", "padding","margin","line-height"}; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, cssClass, elemId); std::string newInstanceId; std::string custom = extractCustomCss(cssProps, knownCss, mediaRules, newInstanceId); if (!newInstanceId.empty()) w->SetAttribute("instance_id", newInstanceId.c_str()); if (!custom.empty()) { tinyxml2::XMLElement *ccEl = doc.NewElement("CustomCss"); ccEl->SetText(custom.c_str()); Loading
editor/src/htmlimport.h +26 −2 Changes for editor/src/htmlimport.h: 26 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -72,12 +72,36 @@ namespace htmlimport { int jsFailed = 0; }; /** Pulls every property in @p props that isn't one of @p knownKeys * (i.e. has no widget-attribute equivalent) out into a standalone CSS * rule text, scoped to this element's own "we-<uuid>" auto-class -- * the same class every widget's Render() unconditionally applies to * its rendered root node (see e.g. Section/Container/TextBox's own * Render()), regardless of widget type. This is deliberate, not a * fallback: only Section/Container/Article/Grid/Table ever forward an * imported class/id onto the rendered element at all (see their own * cssClass member/Render()); TextBox/List/Image/Video do not, so a * selector built from the source element's class/id would silently * never match on those and the "leftover" CSS extracted here would be * dead weight. Scoping to we-<uuid> instead works uniformly. * Since that class only exists once the widget XML this call is * building is actually loaded into a widget instance -- which happens * later, in a different function entirely (see loadChildrenFromXml / * loadDocumentTreeFromXml in webedit_api.cpp) -- the uuid has to be * picked here, at XML-generation time, and threaded through: if there * is anything to scope, a fresh one is generated and written to * @p outInstanceId (left untouched otherwise) for the caller to set * as this XML element's own `instance_id` attribute. Every loader that * honors `instance_id` (loadChildrenFromXml's preserveInstanceIds, * loadDocumentTreeFromXml, and handleImportHtml's own top-level loop) * assigns exactly this id to the resulting widget instance instead of * generating a random one, which is what makes the selector chosen * here still correct once rendered. */ std::string extractCustomCss( std::map<std::string,std::string> &props, const std::vector<std::string> &knownKeys, const std::string &mediaRules, const std::string &cssClass, const std::string &elemId); std::string &outInstanceId); std::string serializeElement(const libhtmlpp::Element *el); std::string serializeInnerHtml(libhtmlpp::HtmlElement *el); Loading
editor/src/webedit_api.cpp +8 −1 Changes for editor/src/webedit_api.cpp: 8 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -5509,6 +5509,13 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, if (!el) continue; el->fromXmlObject(child); // Honor the instance_id htmlimport.cpp assigned to this element (see // extractCustomCss's doc comment) -- without this, the widget gets a // fresh random id here and any CustomCss scoped to "we-<that uuid>" // silently never matches its rendered class. const char *instanceIdAttr = child->Attribute("instance_id"); if (instanceIdAttr && instanceIdAttr[0]) el->setInstanceId(instanceIdAttr); if (insertParent) { // Child mode: add as children of target element Loading @@ -5530,7 +5537,7 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, } lastTop = el; loadChildrenFromXml(child, el, doc); loadChildrenFromXml(child, el, doc, /*preserveInstanceIds=*/true); } json_object *resp = json_object_new_object(); Loading