Commit 1215b153 authored by jan.koester's avatar jan.koester
Browse files

better import

parent 11e92480
Loading
Loading
Loading
Loading
+61 −0
Changes for editor/html/css/editor.css: 61 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1732,6 +1732,67 @@ dialog label {
    margin-bottom: 0.2em;
}

/* HTML import dialog: step 2 block picker */
.import-html-picker-toggle {
    font-size: 12px;
    margin-bottom: 6px;
}

.import-html-picker-toggle a {
    color: var(--accent);
    cursor: pointer;
    text-decoration: none;
}

#import-html-candidates {
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 4px;
}

.import-html-candidate {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 6px 8px;
    border-bottom: 1px solid var(--border);
    cursor: pointer;
}

.import-html-candidate:last-child {
    border-bottom: none;
}

.import-html-candidate:hover {
    background: var(--bg-hover);
}

.import-html-candidate-tag {
    flex: 0 0 auto;
    font-family: monospace;
    font-size: 12px;
    padding: 1px 5px;
    border-radius: 3px;
    background: var(--bg-tertiary);
    color: var(--text-secondary);
}

.import-html-candidate-meta {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--text-muted);
}

.import-html-candidate-preview {
    flex: 1 1 auto;
    font-size: 12px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sm-q-panel input,
.sm-q-panel select,
.sm-q-panel textarea {
+20 −1
Changes for editor/html/index.html: 20 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -241,6 +241,11 @@
        <div class="set-field">
            <label><input type="checkbox" id="doc-seo-robots-nofollow"> <span data-i18n="I18N_DOC_ROBOTS_NOFOLLOW">Don't follow links on this page (nofollow)</span></label>
        </div>
        <div class="set-field">
            <label data-i18n="I18N_DOC_GLOBAL_CSS">Seiten-CSS</label>
            <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="dialog-actions">
            <button id="btn-doc-seo-close" data-i18n="Close">Schlie&szlig;en</button>
        </div>
@@ -294,10 +299,24 @@

    <dialog id="import-html-dialog">
        <h3 data-i18n="I18N_IMPORT_HTML">HTML importieren</h3>
        <div id="import-html-step1">
            <label class="file-upload-label">&#x1F4C2; <span data-i18n="I18N_UPLOAD_FILE">Datei w&auml;hlen</span><input type="file" id="import-html-file" accept=".html,.htm,.txt" style="display:none"></label>
            <textarea id="import-html-content" rows="15" placeholder="HTML hier einf&uuml;gen..."></textarea>
        </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>
            <p id="import-html-global-css-hint" class="hint" style="display:none" data-i18n="I18N_IMPORT_HTML_GLOBAL_CSS_FOUND">Seitenweites CSS gefunden, wird &uuml;bernommen.</p>
            <div class="import-html-picker-toggle">
                <a href="#" id="import-html-select-all" data-i18n="I18N_SELECT_ALL">Alle ausw&auml;hlen</a>
                &middot;
                <a href="#" id="import-html-select-none" data-i18n="I18N_SELECT_NONE">Keine ausw&auml;hlen</a>
            </div>
            <div id="import-html-candidates"></div>
        </div>
        <div class="dialog-actions">
            <button id="btn-import-html-confirm" data-i18n="I18N_IMPORT_HTML">Importieren</button>
            <button id="btn-import-html-back" data-i18n="I18N_BACK" style="display:none">Zur&uuml;ck</button>
            <button id="btn-import-html-next" data-i18n="I18N_NEXT">Weiter</button>
            <button id="btn-import-html-confirm" data-i18n="I18N_IMPORT_HTML" style="display:none">Importieren</button>
            <button id="btn-import-html-cancel" data-i18n="I18N_CANCEL">Abbrechen</button>
        </div>
    </dialog>
+10 −5
Changes for editor/html/js/api.js: 10 added lines, 5 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) {
            return request('POST', '/api/document/save', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {} });
        saveDocument: function(name, group, overwrite, seo, globalCss) {
            return request('POST', '/api/document/save', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {}, global_css: globalCss || '' });
        },

        saveDocumentAs: function(name, group, overwrite, seo) {
            return request('POST', '/api/document/save-as', { name: name, group: group || '', overwrite: !!overwrite, seo: seo || {} });
        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 || '' });
        },

        listTemplates: function() {
@@ -531,10 +531,15 @@ var EditorApi = (function() {
        },

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

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

+139 −14
Changes for editor/html/js/editor.js: 139 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -9,7 +9,10 @@
    var currentDocId = '';
    var currentDocGroup = '';
    var currentDocSeo = {};
    var currentDocGlobalCss = ''; // page-level CSS collected by HTML import, see doc-seo-dialog
    var currentDocPublishParams = {}; // target name -> last-used publish params for this template
    var importHtmlPendingSource = ''; // raw HTML staged in the import dialog, once past step 1
    var importHtmlPendingCandidates = []; // last parse-html result for the block picker
    var saveDialogMode = 'save';
    var projectsCache = [];
    var currentProjectId = '';
@@ -251,6 +254,7 @@
            document.getElementById('doc-name').textContent = resp.name || 'Untitled';
            currentDocId = resp.id || '';
            currentDocSeo = resp.seo || {};
            currentDocGlobalCss = resp.global_css || '';
            currentDocPublishParams = resp.publish_params || {};
            Canvas.setTreeData(resp.tree || []);
            DocumentTree.render(resp.tree || []);
@@ -282,7 +286,7 @@
                openSaveDialog('save', '', currentDocGroup);
            } else {
                saveWithConflictConfirm(function(overwrite) {
                    return EditorApi.saveDocument(name, currentDocGroup, overwrite, currentDocSeo);
                    return EditorApi.saveDocument(name, currentDocGroup, overwrite, currentDocSeo, currentDocGlobalCss);
                }, function(resp) {
                    document.getElementById('doc-name').textContent = resp.name;
                    currentDocGroup = resp.group || '';
@@ -371,7 +375,7 @@
        } catch(e) {}

        document.getElementById('btn-import-html').addEventListener('click', function() {
            document.getElementById('import-html-file').click();
            openImportHtmlDialog();
        });

        document.getElementById('btn-export-html').addEventListener('click', function() {
@@ -488,12 +492,14 @@
        var robots = seo.robots || '';
        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 || '';
    }

    function collectDocSeoFromDialog() {
        var robotsParts = [];
        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();
        currentDocSeo = {
            og_title: document.getElementById('doc-seo-og-title').value.trim(),
            og_description: document.getElementById('doc-seo-og-description').value.trim(),
@@ -504,6 +510,101 @@
        };
    }

    // --- HTML import dialog: step 1 (paste/upload) -> step 2 (block picker) ---

    function openImportHtmlDialog() {
        document.getElementById('import-html-content').value = '';
        importHtmlPendingSource = '';
        importHtmlPendingCandidates = [];
        showImportHtmlStep(1);
        document.getElementById('import-html-dialog').showModal();
    }

    function showImportHtmlStep(step) {
        document.getElementById('import-html-step1').style.display = step === 1 ? '' : 'none';
        document.getElementById('import-html-step2').style.display = step === 2 ? '' : 'none';
        document.getElementById('btn-import-html-back').style.display = step === 2 ? '' : 'none';
        document.getElementById('btn-import-html-next').style.display = step === 1 ? '' : 'none';
        document.getElementById('btn-import-html-confirm').style.display = step === 2 ? '' : 'none';
    }

    // Parses html for the block picker; if parsing finds nothing to choose
    // from (empty/unparseable input), falls straight through to importing
    // everything rather than blocking on an empty picker.
    function goToImportHtmlStep2(html) {
        importHtmlPendingSource = html;
        EditorApi.parseHtml(html).then(function(resp) {
            importHtmlPendingCandidates = resp.candidates || [];
            if (!importHtmlPendingCandidates.length) {
                importHtmlDirectImport(html);
                return;
            }
            renderImportHtmlCandidates(importHtmlPendingCandidates);
            document.getElementById('import-html-global-css-hint').style.display =
                resp.global_css ? '' : 'none';
            showImportHtmlStep(2);
        }).catch(function() {
            // Parsing failed (e.g. malformed input) -- don't block the import
            // on the picker, just import everything like before.
            importHtmlDirectImport(html);
        });
    }

    // 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).then(function() {
            document.getElementById('import-html-dialog').close();
            Canvas.clearSelection(); DocumentTree.clearSelection();
            PropertiesPanel.clear();
            refreshDocument();
        }).catch(function(err) {
            alert(I18n.t('I18N_IMPORT_FAILED') + ': ' + (err.error || ''));
        });
    }

    function renderImportHtmlCandidates(candidates) {
        var container = document.getElementById('import-html-candidates');
        container.innerHTML = '';
        for (var i = 0; i < candidates.length; i++) {
            var cand = candidates[i];
            var label = document.createElement('label');
            label.className = 'import-html-candidate';

            var checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.checked = true;
            checkbox.setAttribute('data-index', cand.index);
            label.appendChild(checkbox);

            var tag = document.createElement('span');
            tag.className = 'import-html-candidate-tag';
            tag.textContent = cand.tag || '?';
            label.appendChild(tag);

            var meta = (cand.css_id ? '#' + cand.css_id : '') +
                       (cand.css_class ? '.' + cand.css_class.split(' ').join('.') : '');
            if (meta) {
                var metaEl = document.createElement('span');
                metaEl.className = 'import-html-candidate-meta';
                metaEl.textContent = meta;
                label.appendChild(metaEl);
            }

            var preview = document.createElement('span');
            preview.className = 'import-html-candidate-preview';
            preview.textContent = cand.preview || '';
            label.appendChild(preview);

            container.appendChild(label);
        }
    }

    function setImportHtmlCandidatesChecked(checked) {
        var boxes = document.querySelectorAll('#import-html-candidates input[type=checkbox]');
        for (var i = 0; i < boxes.length; i++) boxes[i].checked = checked;
    }

    // --- Dialog bindings ---

    // Runs a save request; if the server reports a name/group conflict, asks
@@ -534,7 +635,8 @@
            var group = document.getElementById('save-group').value.trim();
            saveWithConflictConfirm(function(overwrite) {
                return saveDialogMode === 'save-as' ?
                    EditorApi.saveDocumentAs(name, group, overwrite, currentDocSeo) : EditorApi.saveDocument(name, group, overwrite, currentDocSeo);
                    EditorApi.saveDocumentAs(name, group, overwrite, currentDocSeo, currentDocGlobalCss) :
                    EditorApi.saveDocument(name, group, overwrite, currentDocSeo, currentDocGlobalCss);
            }, function(resp) {
                document.getElementById('doc-name').textContent = resp.name;
                currentDocGroup = resp.group || '';
@@ -619,30 +721,53 @@
            document.getElementById('revisions-dialog').close();
        });

        // HTML Import dialog
        // HTML Import dialog: paste/upload (step 1) -> block picker (step 2) -> import
        document.getElementById('import-html-file').addEventListener('change', function() {
            var file = this.files[0];
            this.value = '';
            if (!file) return;
            var reader = new FileReader();
            reader.onload = function(e) {
                var html = e.target.result;
                if (!html.trim()) return;
                EditorApi.importHtml(html).then(function() {
                    Canvas.clearSelection(); DocumentTree.clearSelection();
                    PropertiesPanel.clear();
                    refreshDocument();
                }).catch(function(err) {
                    alert(I18n.t('I18N_IMPORT_FAILED') + ': ' + (err.error || ''));
                });
                document.getElementById('import-html-content').value = html;
                goToImportHtmlStep2(html);
            };
            reader.readAsText(file);
            this.value = '';
        });

        document.getElementById('btn-import-html-confirm').addEventListener('click', function() {
        document.getElementById('btn-import-html-next').addEventListener('click', function() {
            var html = document.getElementById('import-html-content').value;
            if (!html.trim()) return;
            EditorApi.importHtml(html).then(function() {
            goToImportHtmlStep2(html);
        });

        document.getElementById('btn-import-html-back').addEventListener('click', function() {
            showImportHtmlStep(1);
        });

        document.getElementById('import-html-select-all').addEventListener('click', function(e) {
            e.preventDefault();
            setImportHtmlCandidatesChecked(true);
        });

        document.getElementById('import-html-select-none').addEventListener('click', function(e) {
            e.preventDefault();
            setImportHtmlCandidatesChecked(false);
        });

        document.getElementById('btn-import-html-confirm').addEventListener('click', function() {
            var html = importHtmlPendingSource;
            if (!html) return;
            var sections = null;
            if (importHtmlPendingCandidates.length) {
                sections = [];
                var boxes = document.querySelectorAll('#import-html-candidates input[type=checkbox]');
                for (var i = 0; i < boxes.length; i++) {
                    if (boxes[i].checked) sections.push(parseInt(boxes[i].getAttribute('data-index'), 10));
                }
            }
            EditorApi.importHtml(html, null, null, sections).then(function() {
                document.getElementById('import-html-dialog').close();
                Canvas.clearSelection(); DocumentTree.clearSelection();
                PropertiesPanel.clear();
+203 −24

File changed.

Preview size limit exceeded, changes collapsed.

Loading