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

js support htmlimport

parent 5e123618
Loading
Loading
Loading
Loading
+5 −0
Changes for editor/html/index.html: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -248,6 +248,11 @@
            <textarea id="doc-global-css" rows="6" placeholder=":root { --brand: #ff5722; }"></textarea>
            <p class="hint" data-i18n="I18N_DOC_GLOBAL_CSS_HINT">Wird beim HTML-Import automatisch bef&uuml;llt (z.B. :root/*-Regeln), kann hier angepasst werden.</p>
        </div>
        <div class="set-field">
            <label data-i18n="I18N_DOC_GLOBAL_JS">Seiten-JavaScript</label>
            <textarea id="doc-global-js" rows="6" placeholder="console.log('hello');"></textarea>
            <p class="hint" data-i18n="I18N_DOC_GLOBAL_JS_HINT">Wird beim HTML-Import aus externen &lt;script src&gt;-Dateien automatisch bef&uuml;llt, kann hier angepasst werden.</p>
        </div>
        <div class="dialog-actions">
            <button id="btn-doc-seo-close" data-i18n="Close">Schlie&szlig;en</button>
        </div>
+4 −4
Changes for editor/html/js/api.js: 4 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -234,12 +234,12 @@ var EditorApi = (function() {
            return request('POST', '/api/document/import-xml', payload);
        },

        saveDocument: function(name, group, overwrite, seo, globalCss) {
            return request('POST', '/api/document/save', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {}, global_css: globalCss || '' });
        saveDocument: function(name, group, overwrite, seo, globalCss, globalJs) {
            return request('POST', '/api/document/save', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {}, global_css: globalCss || '', global_js: globalJs || '' });
        },

        saveDocumentAs: function(name, group, overwrite, seo, globalCss) {
            return request('POST', '/api/document/save-as', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {}, global_css: globalCss || '' });
        saveDocumentAs: function(name, group, overwrite, seo, globalCss, globalJs) {
            return request('POST', '/api/document/save-as', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {}, global_css: globalCss || '', global_js: globalJs || '' });
        },

        listTemplates: function() {
+9 −3
Changes for editor/html/js/editor.js: 9 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
    var currentDocGroup = '';
    var currentDocSeo = {};
    var currentDocGlobalCss = ''; // page-level CSS collected by HTML import, see doc-seo-dialog
    var currentDocGlobalJs = ''; // page-level JS collected from external <script src> during HTML import
    var currentDocPublishParams = {}; // target name -> last-used publish params for this template
    var currentDocLastPublishTarget = ''; // target name last successfully published to, see applyPreferredPublishTarget
    var importHtmlPendingSource = ''; // raw HTML staged in the import dialog, once past step 1
@@ -260,6 +261,7 @@
            currentDocId = resp.id || '';
            currentDocSeo = resp.seo || {};
            currentDocGlobalCss = resp.global_css || '';
            currentDocGlobalJs = resp.global_js || '';
            currentDocPublishParams = resp.publish_params || {};
            currentDocLastPublishTarget = resp.last_publish_target || '';
            applyPreferredPublishTarget();
@@ -293,7 +295,7 @@
                openSaveDialog('save', '', currentDocGroup);
            } else {
                saveWithConflictConfirm(function(overwrite) {
                    return EditorApi.saveDocument(name, currentDocGroup, overwrite, currentDocSeo, currentDocGlobalCss);
                    return EditorApi.saveDocument(name, currentDocGroup, overwrite, currentDocSeo, currentDocGlobalCss, currentDocGlobalJs);
                }, function(resp) {
                    document.getElementById('doc-name').textContent = resp.name;
                    currentDocGroup = resp.group || '';
@@ -508,6 +510,7 @@
        document.getElementById('doc-seo-robots-noindex').checked = robots.indexOf('noindex') !== -1;
        document.getElementById('doc-seo-robots-nofollow').checked = robots.indexOf('nofollow') !== -1;
        document.getElementById('doc-global-css').value = currentDocGlobalCss || '';
        document.getElementById('doc-global-js').value = currentDocGlobalJs || '';
    }

    function collectDocSeoFromDialog() {
@@ -515,6 +518,7 @@
        if (document.getElementById('doc-seo-robots-noindex').checked) robotsParts.push('noindex');
        if (document.getElementById('doc-seo-robots-nofollow').checked) robotsParts.push('nofollow');
        currentDocGlobalCss = document.getElementById('doc-global-css').value.trim();
        currentDocGlobalJs = document.getElementById('doc-global-js').value.trim();
        currentDocSeo = {
            og_title: document.getElementById('doc-seo-og-title').value.trim(),
            og_description: document.getElementById('doc-seo-og-description').value.trim(),
@@ -813,8 +817,8 @@
            var group = document.getElementById('save-group').value.trim();
            saveWithConflictConfirm(function(overwrite) {
                return saveDialogMode === 'save-as' ?
                    EditorApi.saveDocumentAs(name, group, overwrite, currentDocSeo, currentDocGlobalCss) :
                    EditorApi.saveDocument(name, group, overwrite, currentDocSeo, currentDocGlobalCss);
                    EditorApi.saveDocumentAs(name, group, overwrite, currentDocSeo, currentDocGlobalCss, currentDocGlobalJs) :
                    EditorApi.saveDocument(name, group, overwrite, currentDocSeo, currentDocGlobalCss, currentDocGlobalJs);
            }, function(resp) {
                document.getElementById('doc-name').textContent = resp.name;
                currentDocGroup = resp.group || '';
@@ -978,6 +982,8 @@
                if (resp.images_failed) parts.push(resp.images_failed + ' Bild(er) fehlgeschlagen');
                if (resp.css_fetched) parts.push(resp.css_fetched + ' externes Stylesheet geladen');
                if (resp.css_failed) parts.push(resp.css_failed + ' externes Stylesheet fehlgeschlagen');
                if (resp.js_fetched) parts.push(resp.js_fetched + ' externes Skript geladen');
                if (resp.js_failed) parts.push(resp.js_failed + ' externes Skript fehlgeschlagen');
                if (parts.length) alert(parts.join(', ') + '.');
            }).catch(function(err) {
                alert(I18n.t('I18N_IMPORT_FAILED') + ': ' + (err.error || ''));
+47 −1
Changes for editor/src/htmlimport.cpp: 47 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -1164,6 +1164,7 @@ blogi::htmlimport::buildImportTree(const std::string &html)
bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
                                               std::string &outXml,
                                               std::string &outGlobalCss,
                                               std::string &outGlobalJs,
                                               const std::set<std::string> *selectedPaths,
                                               ImportStats *outStats)
{
@@ -1192,15 +1193,23 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
        xmlDoc.Print(&printer);
        outXml = printer.CStr();
        outGlobalCss.clear();
        outGlobalJs.clear();
        return true;
    }

    // Collect all <style> elements (and, up to a small cap, externally
    // fetched <link rel="stylesheet"> sheets) and parse their CSS.
    // fetched <link rel="stylesheet"> sheets) and parse their CSS; also
    // collect externally hosted <script src="http(s)://..."> contents into
    // globalJs (inline <script> blocks, with no src, are left untouched).
    const int kMaxExternalStylesheets = 10;
    const size_t kMaxStylesheetBytes = 3 * 1024 * 1024;
    int externalCssSeen = 0;

    const int kMaxExternalScripts = 10;
    const size_t kMaxScriptBytes = 5 * 1024 * 1024;
    int externalJsSeen = 0;
    std::string globalJs;

    libhtmlpp::CSSStyleSheet stylesheet;
    std::stack<libhtmlpp::Element*> searchStack;
    searchStack.push(root);
@@ -1208,6 +1217,39 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
        libhtmlpp::Element *cur = searchStack.top();
        searchStack.pop();
        while (cur) {
            if (cur->getType() == libhtmlpp::ScriptEL) {
                libhtmlpp::ScriptElement *sel = static_cast<libhtmlpp::ScriptElement*>(cur);
                std::string src = sel->getAtributte("src");
                bool isAbsolute = src.compare(0, 7, "http://") == 0 ||
                                   src.compare(0, 8, "https://") == 0;

                if (isAbsolute) {
                    if (externalJsSeen >= kMaxExternalScripts) {
                        if (outStats) outStats->jsFailed++;
                    } else {
                        externalJsSeen++;
                        // Same rationale as the external-CSS fetch below:
                        // never let a hostile/broken external script abort
                        // the whole import.
                        try {
                            std::vector<std::uint8_t> bytes;
                            std::string fetchError;
                            if (fetchExternalResource(src, kMaxScriptBytes, "",
                                                       bytes, fetchError)) {
                                if (!globalJs.empty()) globalJs += "\n;\n";
                                globalJs.append(bytes.begin(), bytes.end());
                                if (outStats) outStats->jsFetched++;
                            } else if (outStats) {
                                outStats->jsFailed++;
                            }
                        } catch (...) {
                            if (outStats) outStats->jsFailed++;
                        }
                    }
                }
                cur = cur->nextElement();
                continue;
            }
            if (cur->getType() == libhtmlpp::HtmlEl) {
                libhtmlpp::HtmlElement *hel =
                    static_cast<libhtmlpp::HtmlElement*>(cur);
@@ -1335,6 +1377,9 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
    if (!globalCss.empty()) {
        xmlRoot->SetAttribute("GlobalCss", globalCss.c_str());
    }
    if (!globalJs.empty()) {
        xmlRoot->SetAttribute("GlobalJs", globalJs.c_str());
    }

    if (root) {
        if (selectedPaths) {
@@ -1353,5 +1398,6 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
    xmlDoc.Print(&printer);
    outXml = printer.CStr();
    outGlobalCss = globalCss;
    outGlobalJs = globalJs;
    return true;
}
+12 −5
Changes for editor/src/htmlimport.h: 12 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -56,12 +56,14 @@ namespace htmlimport {
                               std::vector<std::uint8_t> &out,
                               std::string &error);

    /** Counters describing what convertHtmlToWidgetXml's external-CSS
     *  fetching (see the "link" branch in its <style>-collection loop) did,
     *  for callers that want to surface a summary to the user. */
    /** Counters describing what convertHtmlToWidgetXml's external-CSS/JS
     *  fetching (see the "link"/"script" branches in its <style>-collection
     *  loop) did, for callers that want to surface a summary to the user. */
    struct ImportStats {
        int cssFetched = 0;
        int cssFailed = 0;
        int jsFetched = 0;
        int jsFailed = 0;
    };

    std::map<std::string,std::string> declarationToMap(
@@ -114,8 +116,12 @@ namespace htmlimport {
    std::vector<ImportNode> buildImportTree(const std::string &html);

    /** Convert raw HTML string to a widget XML string (<WebEditor>...).
     *  Returns true on success, writes the XML to @p outXml and optional
     *  global CSS to @p outGlobalCss. If @p selectedPaths is non-null, only
     *  Returns true on success, writes the XML to @p outXml, optional
     *  global CSS to @p outGlobalCss, and the concatenated contents of any
     *  externally hosted <script src="http(s)://..."> tags (each fetched via
     *  fetchExternalResource, same limits/safety as external stylesheets) to
     *  @p outGlobalJs -- inline <script> blocks (no src) are left untouched,
     *  matching existing behavior. If @p selectedPaths is non-null, only
     *  the nodes (see ImportNode::path, at any depth) named in the set are
     *  converted/imported, each as its own top-level element -- otherwise
     *  the whole document is imported (today's behavior). Selecting both an
@@ -125,6 +131,7 @@ namespace htmlimport {
    bool convertHtmlToWidgetXml(const std::string &html,
                                std::string &outXml,
                                std::string &outGlobalCss,
                                std::string &outGlobalJs,
                                const std::set<std::string> *selectedPaths = nullptr,
                                ImportStats *outStats = nullptr);

Loading