Commit 475e8869 authored by Jan Köster's avatar Jan Köster
Browse files

css support import html

parent 0d3a5b45
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -333,6 +333,13 @@
                    <select id="import-html-media-album" disabled></select>
                </div>
            </div>
            <div class="set-field">
                <label><input type="checkbox" id="import-html-extra-css-enabled"> <span data-i18n="I18N_IMPORT_HTML_EXTRA_CSS_ENABLE">Zus&auml;tzliches CSS anh&auml;ngen (z.B. nicht automatisch geladene Stylesheets)</span></label>
            </div>
            <div id="import-html-extra-css-options" style="display:none">
                <label class="file-upload-label">&#x1F4C2; <span data-i18n="I18N_UPLOAD_FILE">Datei w&auml;hlen</span><input type="file" id="import-html-extra-css-file" accept=".css,.txt" multiple style="display:none"></label>
                <textarea id="import-html-extra-css-content" rows="6" placeholder="CSS hier einf&uuml;gen..."></textarea>
            </div>
        </div>
        <div id="import-html-step2" style="display:none">
            <p class="hint" data-i18n="I18N_IMPORT_HTML_PICK_HINT">Welche Bereiche sollen importiert werden?</p>
+17 −5
Original line number Diff line number Diff line
@@ -531,8 +531,17 @@ var EditorApi = (function() {
        },

        // HTML import/export
        parseHtml: function(html) {
            return request('POST', '/api/document/parse-html', { html: html });

        // extraCss (optional): CSS the user attached separately from the
        // HTML itself -- e.g. a stylesheet the auto-fetcher couldn't reach
        // (a relative/local <link href>, or a CORS/network failure) -- see
        // handleParseHtml/convertHtmlToWidgetXml's extraCss parameter. Merged
        // into the same stylesheet used for per-element style resolution,
        // not just appended to the resulting global_css.
        parseHtml: function(html, extraCss) {
            var data = { html: html };
            if (extraCss) data.extra_css = extraCss;
            return request('POST', '/api/document/parse-html', data);
        },

        // Fetches @p url server-side and parses it exactly like parseHtml, but
@@ -540,17 +549,20 @@ var EditorApi = (function() {
        // against it. Response includes "html" (the fetched page) and "base_url"
        // (echoed back) so the caller can stash both and pass base_url to
        // importHtml unchanged, instead of fetching the page a second time.
        parseHtmlUrl: function(url) {
            return request('POST', '/api/document/parse-html', { url: url });
        parseHtmlUrl: function(url, extraCss) {
            var data = { url: url };
            if (extraCss) data.extra_css = extraCss;
            return request('POST', '/api/document/parse-html', data);
        },

        importHtml: function(html, mode, parentUuid, sections, mediaImport, baseUrl) {
        importHtml: function(html, mode, parentUuid, sections, mediaImport, baseUrl, extraCss) {
            var data = { html: html };
            if (mode) data.mode = mode;
            if (parentUuid) data.parent_uuid = parentUuid;
            if (sections) data.sections = sections;
            if (mediaImport) data.media_import = mediaImport;
            if (baseUrl) data.base_url = baseUrl;
            if (extraCss) data.extra_css = extraCss;
            return request('POST', '/api/document/import-html', data);
        },

+50 −4
Original line number Diff line number Diff line
@@ -544,6 +544,7 @@
        importHtmlPendingCandidates = [];
        importHtmlPendingBaseUrl = '';
        resetImportHtmlMediaOptions();
        resetImportHtmlExtraCss();
        showImportHtmlStep(1);
        document.getElementById('import-html-dialog').showModal();
    }
@@ -555,6 +556,24 @@
        el.className = 'set-status' + (isError ? ' error' : '');
    }

    // CSS the user attaches separately from the HTML itself -- e.g. a
    // stylesheet the auto-fetcher couldn't reach (a relative/local <link
    // href> with no known base URL, or a CORS/network failure). Read fresh
    // from the field on every use (unlike importHtmlPendingBaseUrl, it
    // doesn't need to survive a server round-trip) and merged straight into
    // the same stylesheet used for per-element style resolution -- see
    // convertHtmlToWidgetXml's extraCss parameter.
    function resetImportHtmlExtraCss() {
        document.getElementById('import-html-extra-css-enabled').checked = false;
        document.getElementById('import-html-extra-css-options').style.display = 'none';
        document.getElementById('import-html-extra-css-content').value = '';
    }

    function getImportHtmlExtraCss() {
        if (!document.getElementById('import-html-extra-css-enabled').checked) return '';
        return document.getElementById('import-html-extra-css-content').value.trim();
    }

    // --- CSS import dialog (paste/upload/URL) -- unlike HTML import there is
    // no widget tree/block picker: the result always merges straight into the
    // document's page-level GlobalCss (see handleImportCss), so both the
@@ -595,7 +614,7 @@
        var btn = document.getElementById('btn-import-html-load-url');
        btn.disabled = true;
        setImportHtmlUrlStatus(I18n.t('I18N_LOADING_URL', 'URL wird geladen...'));
        EditorApi.parseHtmlUrl(url).then(function(resp) {
        EditorApi.parseHtmlUrl(url, getImportHtmlExtraCss()).then(function(resp) {
            btn.disabled = false;
            setImportHtmlUrlStatus('');
            var html = resp.html || '';
@@ -755,7 +774,7 @@
        // loadImportHtmlFromUrl sets importHtmlPendingBaseUrl, and only for
        // HTML it fetched itself unmodified.
        importHtmlPendingBaseUrl = '';
        EditorApi.parseHtml(html).then(function(resp) {
        EditorApi.parseHtml(html, getImportHtmlExtraCss()).then(function(resp) {
            importHtmlPendingCandidates = resp.candidates || [];
            if (!importHtmlPendingCandidates.length) {
                importHtmlDirectImport(html);
@@ -775,7 +794,7 @@
    // Fallback used when the picker has nothing to show: import the whole
    // document directly instead of leaving the dialog stuck.
    function importHtmlDirectImport(html) {
        EditorApi.importHtml(html, null, null, null, null, importHtmlPendingBaseUrl).then(function() {
        EditorApi.importHtml(html, null, null, null, null, importHtmlPendingBaseUrl, getImportHtmlExtraCss()).then(function() {
            document.getElementById('import-html-dialog').close();
            Canvas.clearSelection(); DocumentTree.clearSelection();
            PropertiesPanel.clear();
@@ -1057,6 +1076,33 @@
            }
        });

        document.getElementById('import-html-extra-css-enabled').addEventListener('change', function() {
            document.getElementById('import-html-extra-css-options').style.display = this.checked ? '' : 'none';
        });

        // Reads each selected file in order, appending its text to the extra-
        // CSS textarea as it finishes (so pasted content and multiple file
        // selections all accumulate rather than overwrite each other).
        document.getElementById('import-html-extra-css-file').addEventListener('change', function() {
            var files = Array.prototype.slice.call(this.files);
            this.value = '';
            if (!files.length) return;
            var textarea = document.getElementById('import-html-extra-css-content');
            function readNext(i) {
                if (i >= files.length) return;
                var reader = new FileReader();
                reader.onload = function(e) {
                    var css = e.target.result;
                    if (css.trim()) {
                        textarea.value = (textarea.value ? textarea.value + '\n' : '') + css;
                    }
                    readNext(i + 1);
                };
                reader.readAsText(files[i]);
            }
            readNext(0);
        });

        document.getElementById('btn-import-html-confirm').addEventListener('click', function() {
            var html = importHtmlPendingSource;
            if (!html) return;
@@ -1069,7 +1115,7 @@
                }
            }
            var mediaImport = getImportHtmlMediaImportOptions();
            EditorApi.importHtml(html, null, null, sections, mediaImport, importHtmlPendingBaseUrl).then(function(resp) {
            EditorApi.importHtml(html, null, null, sections, mediaImport, importHtmlPendingBaseUrl, getImportHtmlExtraCss()).then(function(resp) {
                document.getElementById('import-html-dialog').close();
                Canvas.clearSelection(); DocumentTree.clearSelection();
                PropertiesPanel.clear();
+24 −1
Original line number Diff line number Diff line
@@ -2031,7 +2031,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
                                               std::string &outGlobalJs,
                                               const std::set<std::string> *selectedPaths,
                                               ImportStats *outStats,
                                               const std::string &baseUrl)
                                               const std::string &baseUrl,
                                               const std::string &extraCss)
{
    libhtmlpp::HtmlPage page;
    libhtmlpp::HtmlElement index;
@@ -2096,6 +2097,28 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
    std::string globalJs;

    libhtmlpp::CSSStyleSheet stylesheet;

    // User-attached CSS -- e.g. a stylesheet the auto-fetcher couldn't reach
    // (a relative/local <link href> with no baseUrl, or a CORS/network
    // failure) that the import dialog lets the user paste/upload alongside
    // the HTML itself (see webedit_api.cpp's handleParseHtml/handleImportHtml).
    // Merged in before any of this page's own <style>/<link> content below,
    // so this page's own rules keep the final say for any selector both
    // define -- same "last one wins" cascade approximation this file already
    // relies on elsewhere (see collectApproximateMatches).
    if (!extraCss.empty()) {
        std::string extra = extraCss;
        if (!baseUrl.empty()) {
            extra = rewriteCssUrls(extra,
                [&](const std::string &ref) { return resolveUrl(baseUrl, ref); });
        }
        libhtmlpp::CSSStyleSheet partial;
        partial.parse(extra);
        for (const auto &rule : partial.getRules()) {
            stylesheet.addRule(rule);
        }
    }

    std::stack<libhtmlpp::Element*> searchStack;
    searchStack.push(root);
    while (!searchStack.empty()) {
+12 −2
Original line number Diff line number Diff line
@@ -164,14 +164,24 @@ namespace htmlimport {
     *  relative resources on the original page still work once hosted by
     *  blogi (see rewriteRelativeUrls/rewriteCssUrls in htmlimport.cpp).
     *  Already-absolute references and non-http(s) schemes (data:, mailto:,
     *  javascript:, fragment-only "#...") are left untouched. */
     *  javascript:, fragment-only "#...") are left untouched.
     *  If @p extraCss is non-empty, it is parsed and merged into the same
     *  stylesheet used for per-element style resolution (not just appended
     *  to @p outGlobalCss afterwards) -- BEFORE the document's own <style>/
     *  <link> content, so the document's own rules still win any selector
     *  both define. Meant for CSS the import dialog's user attaches
     *  separately (e.g. a stylesheet the auto-fetcher couldn't reach: a
     *  relative/local <link href> with no @p baseUrl, or a CORS/network
     *  failure) -- see convertCssToGlobalCss for importing a stylesheet on
     *  its own, with no HTML document alongside it. */
    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,
                                const std::string &baseUrl = "");
                                const std::string &baseUrl = "",
                                const std::string &extraCss = "");

    /** Convert a standalone CSS stylesheet (pasted/uploaded/fetched on its
     *  own, not part of an HTML document) into global CSS text, using the
Loading