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

test

parent 420e2f22
Loading
Loading
Loading
Loading
+16 −10
Changes for editor/html/index.html: 16 added lines, 10 removed lines.
Original line number Diff line number Diff line
@@ -45,9 +45,15 @@
            <button id="btn-export-xml" data-i18n-title="I18N_TITLE_EXPORT_XML">&#x2B06; <span data-i18n="I18N_EXPORT_XML">Export XML</span></button>
            <button id="btn-import-html" data-i18n-title="I18N_IMPORT_HTML">&#x2B07; <span data-i18n="I18N_IMPORT_HTML">Import HTML</span></button>
            <button id="btn-export-html" data-i18n-title="I18N_EXPORT_HTML">&#x2B06; <span data-i18n="I18N_EXPORT_HTML">Export HTML</span></button>
            <button id="btn-conn-manager" data-i18n-title="I18N_CONNECTIONS">&#x1F517; <span data-i18n="I18N_CONNECTIONS">Verbindungen</span></button>
            <button id="btn-conn-manager" data-i18n-title="I18N_PROJECTS">&#x1F517; <span data-i18n="I18N_PROJECTS">Projekte</span></button>
            <button id="btn-survey-manager" title="Umfragen verwalten">&#x1F4CB; <span>Umfragen</span></button>
        </div>
        <div class="toolbar-group">
            <select id="project-select" title="Projekt ausw&auml;hlen" style="padding:0.2em 0.4em;border-radius:4px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-size:0.8rem;cursor:pointer">
                <option value="" data-i18n="I18N_ALL_PROJECTS">Alle Projekte</option>
            </select>
            <span id="project-status" title="Verbindungsstatus" style="font-size:0.8rem"></span>
        </div>
        <div class="toolbar-group">
            <button id="btn-ai" data-i18n-title="I18N_AI_TITLE">&#x1F916; <span data-i18n="I18N_AI">KI</span></button>
        </div>
@@ -130,7 +136,7 @@

    <dialog id="save-dialog">
        <h3 id="save-dialog-title" data-i18n="I18N_SAVE_TPL_TITLE">Dokument speichern</h3>
        <label><span data-i18n="I18N_GROUP">Gruppe</span>: <input type="text" id="save-group" data-i18n-placeholder="I18N_GROUP_PLACEHOLDER" placeholder="z.B. Vorlagen/Blog"></label>
        <label><span data-i18n="I18N_PROJECT">Projekt</span>: <select id="save-group"></select></label>
        <label><span data-i18n="I18N_TPL_NAME_LABEL">Name</span>: <input type="text" id="save-name" data-i18n-placeholder="I18N_TPL_NAME_PLACEHOLDER" placeholder="Dokumentname"></label>
        <div class="set-field" style="display:flex;gap:0.5em;flex-wrap:wrap">
            <button id="btn-doc-seo" type="button" class="set-save-btn" style="background:var(--bg-tertiary)">&#x1F50D; <span data-i18n="I18N_SEO_SOCIAL">SEO &amp; Social&hellip;</span></button>
@@ -253,13 +259,13 @@
    <dialog id="connection-manager-dialog" class="settings-dialog">
        <div class="set-modal">
            <div class="set-sidebar">
                <h3 data-i18n="I18N_CONNECTIONS">Verbindungen</h3>
                <h3 data-i18n="I18N_PROJECTS">Projekte</h3>
                <div id="conn-list" class="conn-list"></div>
                <button id="btn-conn-add" class="set-save-btn" style="margin-top:0.5em;width:100%">+ <span data-i18n="I18N_ADD">Hinzuf&uuml;gen</span></button>
            </div>
            <div class="set-content">
                <div id="conn-form" style="display:none">
                    <h4 id="conn-form-title" data-i18n="I18N_CONNECTION">Verbindung</h4>
                    <h4 id="conn-form-title" data-i18n="I18N_PROJECT">Projekt</h4>
                    <input type="hidden" id="conn-edit-id" value="">
                    <div class="set-field">
                        <label data-i18n="I18N_CONN_GROUP">Gruppe</label>
@@ -297,7 +303,7 @@
                    <span id="conn-status" class="set-status"></span>
                </div>
                <div id="conn-placeholder">
                    <p class="hint" data-i18n="I18N_SELECT_CONNECTION">Verbindung ausw&auml;hlen oder neue hinzuf&uuml;gen.</p>
                    <p class="hint" data-i18n="I18N_SELECT_PROJECT">Projekt ausw&auml;hlen oder neues hinzuf&uuml;gen.</p>
                </div>
            </div>
        </div>
@@ -329,7 +335,7 @@
            </div>
        </div>
        <div class="dialog-actions">
            <button id="btn-publish-settings" data-i18n="Settings" style="margin-right:auto">&#x2699; <span data-i18n="I18N_CONNECTIONS">Verbindungen</span></button>
            <button id="btn-publish-settings" data-i18n="Settings" style="margin-right:auto">&#x2699; <span data-i18n="I18N_PROJECTS">Projekte</span></button>
            <button id="btn-publish-close" data-i18n="Close">Schlie&szlig;en</button>
        </div>
    </dialog>
@@ -404,7 +410,7 @@
                <ul class="set-nav">
                    <li class="set-nav-item active" data-section="language" data-i18n="Language">Sprache</li>
                    <li class="set-nav-item" data-section="ai" data-i18n="I18N_AI">KI</li>
                    <li class="set-nav-item" data-section="connection" data-i18n="I18N_CONNECTION">Verbindung</li>
                    <li class="set-nav-item" data-section="connection" data-i18n="I18N_PROJECT">Projekt</li>
                    <li class="set-nav-item" data-section="mcp" data-i18n="I18N_MCP">MCP</li>
                </ul>
            </div>
@@ -452,10 +458,10 @@

                <!-- Connection section -->
                <div id="settings-connection" class="set-section" style="display:none">
                    <h4 data-i18n="I18N_CONNECTIONS">Verbindungen</h4>
                    <p class="hint" data-i18n="I18N_CONN_MANAGER_HINT">Verbindungen werden im Verbindungsmanager verwaltet.</p>
                    <h4 data-i18n="I18N_PROJECTS">Projekte</h4>
                    <p class="hint" data-i18n="I18N_PROJECT_MANAGER_HINT">Projekte werden im Projekt-Manager verwaltet.</p>
                    <div class="set-field">
                        <button id="btn-settings-open-connmgr" class="set-save-btn" data-i18n="I18N_OPEN_CONN_MANAGER">Verbindungsmanager &ouml;ffnen</button>
                        <button id="btn-settings-open-connmgr" class="set-save-btn" data-i18n="I18N_OPEN_PROJECT_MANAGER">Projekt-Manager &ouml;ffnen</button>
                    </div>
                </div>

+24 −8
Changes for editor/html/js/api.js: 24 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -95,6 +95,21 @@ var EditorApi = (function() {
        return { promise: promise, abort: function() { xhr.abort(); } };
    }

    // Endpoints scoped to a connection/project (publish, media, survey) require a live
    // server-side session (_connSessions in webedit_api.cpp) that can be evicted (auth
    // expired, server restart, ...). Those handlers reply 403 "Not connected...". Rather
    // than surfacing that to the user, reconnect once and retry transparently.
    function requestWithReconnect(connId, method, url, data) {
        return request(method, url, data).catch(function(err) {
            if (err && typeof err.error === 'string' && err.error.indexOf('Not connected') === 0) {
                return request('POST', '/api/connection/connect/' + connId, {}).then(function() {
                    return request(method, url, data);
                });
            }
            return Promise.reject(err);
        });
    }

    return {
        login: function(username, password) {
            return request('POST', '/api/login', {
@@ -379,7 +394,7 @@ var EditorApi = (function() {
        },

        publishToConnection: function(id, target, params) {
            return request('POST', '/api/connection/publish/' + id, {
            return requestWithReconnect(id, 'POST', '/api/connection/publish/' + id, {
                target: target,
                params: params || {}
            });
@@ -387,19 +402,19 @@ var EditorApi = (function() {

        // Media browse (via blog connection)
        mediaListAlbums: function(connId) {
            return request('POST', '/api/connection/media/' + connId, {
            return requestWithReconnect(connId, 'POST', '/api/connection/media/' + connId, {
                command: 'media_list_albums'
            });
        },

        mediaListStores: function(connId) {
            return request('POST', '/api/connection/media/' + connId, {
            return requestWithReconnect(connId, 'POST', '/api/connection/media/' + connId, {
                command: 'media_list_stores'
            });
        },

        mediaCreateAlbum: function(connId, storeName, albumName, isPublic) {
            return request('POST', '/api/connection/media/' + connId, {
            return requestWithReconnect(connId, 'POST', '/api/connection/media/' + connId, {
                command: 'media_create_album',
                store_name: storeName,
                album_name: albumName,
@@ -408,7 +423,7 @@ var EditorApi = (function() {
        },

        mediaUpload: function(connId, albumId, filename, dataBase64) {
            return request('POST', '/api/connection/media/' + connId, {
            return requestWithReconnect(connId, 'POST', '/api/connection/media/' + connId, {
                command: 'media_upload',
                album_id: albumId,
                filename: filename,
@@ -417,7 +432,7 @@ var EditorApi = (function() {
        },

        mediaListMedia: function(connId, albumId) {
            return request('POST', '/api/connection/media/' + connId, {
            return requestWithReconnect(connId, 'POST', '/api/connection/media/' + connId, {
                command: 'media_list_media',
                album_id: albumId
            });
@@ -430,7 +445,7 @@ var EditorApi = (function() {
            };
            if (width > 0) params.width = width;
            if (height > 0) params.height = height;
            return request('POST', '/api/connection/media/' + connId, params);
            return requestWithReconnect(connId, 'POST', '/api/connection/media/' + connId, params);
        },

        // HTML import/export
@@ -471,7 +486,8 @@ var EditorApi = (function() {
        },

        publishSurvey: function(surveyId, connId) {
            return request('POST', '/api/survey/publish', { survey_id: surveyId, conn_id: connId });
            return requestWithReconnect(connId, 'POST', '/api/survey/publish',
                { survey_id: surveyId, conn_id: connId });
        },

        // Plugin settings (served by the backend settings router via AJAX)
+152 −5
Changes for editor/html/js/editor.js: 152 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,10 @@
    var currentDocGroup = '';
    var currentDocSeo = {};
    var saveDialogMode = 'save';
    var projectsCache = [];
    var currentProjectId = '';
    var currentProjectName = '';
    var CURRENT_PROJECT_STORAGE_KEY = 'blogi-current-project';
    var wizardTemplates = [];
    var wizardCsvRows = [];
    var wizardCsvColumns = [];
@@ -33,6 +37,7 @@
            try { SurveyManager.init(); } catch(e) { console.error('SurveyManager.init:', e); }
            try { Preview.bindToggle(); } catch(e) { console.error('Preview.bindToggle:', e); }
            try { Canvas.bindMessageListener(); } catch(e) { console.error('Canvas.bindMessageListener:', e); }
            try { initProjectSelector(); } catch(e) { console.error('initProjectSelector:', e); }
            Preview.clear();

            // Selection can be driven either by clicking directly on an
@@ -225,7 +230,11 @@

        document.getElementById('btn-open').addEventListener('click', function() {
            EditorApi.listDocuments().then(function(resp) {
                renderDocList(resp.documents || []);
                var docs = resp.documents || [];
                if (currentProjectName) {
                    docs = docs.filter(function(d) { return (d.group || '') === currentProjectName; });
                }
                renderDocList(docs);
                document.getElementById('open-dialog').showModal();
            });
        });
@@ -365,10 +374,37 @@
            I18n.t('I18N_SAVE_AS_TPL_TITLE', 'Dokument speichern unter') :
            I18n.t('I18N_SAVE_TPL_TITLE', 'Dokument speichern');
        document.getElementById('save-name').value = name || '';
        document.getElementById('save-group').value = group || '';
        populateSaveGroupSelect(group || currentProjectName || '');
        document.getElementById('save-dialog').showModal();
    }

    // Populates the "Speichern unter" project select from projectsCache. `selectedGroup`
    // is preselected if it matches a known project name; otherwise (legacy free-text
    // groups from before projects existed) it's appended as an extra option so existing
    // documents don't silently lose their group on the next save.
    function populateSaveGroupSelect(selectedGroup) {
        var select = document.getElementById('save-group');
        select.innerHTML = '';
        var blankOpt = document.createElement('option');
        blankOpt.value = '';
        blankOpt.textContent = I18n.t('I18N_NO_PROJECT', 'Kein Projekt');
        select.appendChild(blankOpt);
        appendGroupedProjectOptions(select, function(p) { return p.name; });

        var hasSelected = !selectedGroup;
        var options = select.querySelectorAll('option');
        for (var i = 0; i < options.length; i++) {
            if (options[i].value === selectedGroup) { hasSelected = true; break; }
        }
        if (selectedGroup && !hasSelected) {
            var extra = document.createElement('option');
            extra.value = selectedGroup;
            extra.textContent = selectedGroup;
            select.appendChild(extra);
        }
        select.value = selectedGroup || '';
    }

    // Fills the document-level SEO & Social dialog from currentDocSeo (see
    // DocumentState::seo / <WebEditor><SeoMeta> in webedit_api.cpp) — this
    // metadata travels with the saved template's XML, unlike the per-publish
@@ -1665,6 +1701,111 @@
        }
    }

    // --- Project selector (toolbar) ---
    // A "project" is the same underlying entity as a saved connection (name/group/url/
    // ignore_ssl — see EditorApi.listConnections/connectToConnection and the project
    // manager dialog below). The toolbar select exposes it under project terminology:
    // picking one auto-connects and becomes the default group when saving documents.

    function initProjectSelector() {
        document.getElementById('project-select').addEventListener('change', function() {
            onProjectSelected(this.value);
        });

        loadProjectSelectOptions(function() {
            var saved = '';
            try { saved = localStorage.getItem(CURRENT_PROJECT_STORAGE_KEY) || ''; } catch(e) {}
            if (saved && projectsCache.some(function(p) { return p.id === saved; })) {
                document.getElementById('project-select').value = saved;
                onProjectSelected(saved);
            }
        });
    }

    // Fetches the project list and (re)renders the toolbar select; shared with the
    // project manager dialog (loadConnectionList) so both stay in sync after add/edit/delete.
    function loadProjectSelectOptions(callback) {
        EditorApi.listConnections().then(function(resp) {
            projectsCache = resp.connections || [];
            renderProjectToolbarSelect();
            if (callback) callback();
        });
    }

    function renderProjectToolbarSelect() {
        var select = document.getElementById('project-select');
        select.innerHTML = '<option value="">' + I18n.t('I18N_ALL_PROJECTS', 'Alle Projekte') + '</option>';
        appendGroupedProjectOptions(select, function(p) { return p.id; });
        select.value = currentProjectId || '';
    }

    // Builds <optgroup>-grouped <option>s (grouped like the project manager's sidebar
    // list) into `select` from projectsCache; valueFn picks the option value per project.
    function appendGroupedProjectOptions(select, valueFn) {
        var groups = {};
        for (var i = 0; i < projectsCache.length; i++) {
            var g = projectsCache[i].group || '';
            if (!groups[g]) groups[g] = [];
            groups[g].push(projectsCache[i]);
        }
        var groupNames = Object.keys(groups).sort();
        for (var gi = 0; gi < groupNames.length; gi++) {
            var gName = groupNames[gi];
            var container = select;
            if (gName) {
                container = document.createElement('optgroup');
                container.label = gName;
                select.appendChild(container);
            }
            var projs = groups[gName];
            for (var pi = 0; pi < projs.length; pi++) {
                var opt = document.createElement('option');
                opt.value = valueFn(projs[pi]);
                opt.textContent = projs[pi].name;
                container.appendChild(opt);
            }
        }
    }

    function onProjectSelected(id) {
        var statusEl = document.getElementById('project-status');
        if (!id) {
            currentProjectId = '';
            currentProjectName = '';
            try { localStorage.removeItem(CURRENT_PROJECT_STORAGE_KEY); } catch(e) {}
            statusEl.textContent = '';
            statusEl.title = '';
            return;
        }
        var proj = null;
        for (var i = 0; i < projectsCache.length; i++) {
            if (projectsCache[i].id === id) { proj = projectsCache[i]; break; }
        }
        currentProjectId = id;
        currentProjectName = proj ? proj.name : '';
        try { localStorage.setItem(CURRENT_PROJECT_STORAGE_KEY, id); } catch(e) {}
        connectCurrentProject();
    }

    // Auto-connect (and, via the "Not connected" retry in EditorApi, auto-reconnect on
    // the next connection-scoped call) whenever the current project changes or is
    // restored from localStorage on load.
    function connectCurrentProject() {
        if (!currentProjectId) return;
        var statusEl = document.getElementById('project-status');
        statusEl.textContent = I18n.t('I18N_CONNECTING', 'Verbinde...');
        statusEl.title = '';
        EditorApi.connectToConnection(currentProjectId).then(function(resp) {
            connectedConnections[currentProjectId] = true;
            statusEl.textContent = '✓';
            statusEl.title = I18n.t('I18N_CONNECTION_OK', 'Verbunden');
            showPreviewPublishTargets(resp.targets || []);
        }).catch(function(err) {
            statusEl.textContent = '✗';
            statusEl.title = I18n.t('I18N_CONNECTION_FAILED', 'Verbindung fehlgeschlagen') + ': ' + ((err && err.error) || '');
        });
    }

    // --- Connection Manager ---

    // Track which connections are currently "connected" (have authid on server)
@@ -1738,6 +1879,12 @@
            if (!confirm(I18n.t('I18N_REMOVE_CONFIRM', 'Wirklich l\u00f6schen') + '?')) return;
            EditorApi.deleteConnection(id).then(function() {
                delete connectedConnections[id];
                if (id === currentProjectId) {
                    currentProjectId = '';
                    currentProjectName = '';
                    try { localStorage.removeItem(CURRENT_PROJECT_STORAGE_KEY); } catch(e) {}
                    document.getElementById('project-status').textContent = '';
                }
                document.getElementById('conn-form').style.display = 'none';
                document.getElementById('conn-placeholder').style.display = 'block';
                loadConnectionList();
@@ -1958,8 +2105,8 @@
    }

    function loadConnectionList() {
        EditorApi.listConnections().then(function(resp) {
            renderConnectionList(resp.connections || []);
        loadProjectSelectOptions(function() {
            renderConnectionList(projectsCache);
        });
    }

@@ -2023,7 +2170,7 @@

        if (connections.length === 0) {
            list.innerHTML = '<p class="hint" style="padding:0.5em">' +
                I18n.t('I18N_NO_CONNECTIONS', 'Keine Verbindungen') + '</p>';
                I18n.t('I18N_NO_PROJECTS', 'Keine Projekte') + '</p>';
        }
    }

+18 −0
Changes for src/translations.h: 18 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -392,6 +392,15 @@ namespace blogi {
            {"I18N_NO_CONNECTIONS",      "Keine Verbindungen"},
            {"I18N_CONN_MANAGER_HINT",   "Verbindungen werden im Verbindungsmanager verwaltet."},
            {"I18N_OPEN_CONN_MANAGER",   "Verbindungsmanager &ouml;ffnen"},
            // Projects (editor toolbar/dialog wording for the same connection entity)
            {"I18N_PROJECT",             "Projekt"},
            {"I18N_PROJECTS",            "Projekte"},
            {"I18N_SELECT_PROJECT",      "Projekt ausw&auml;hlen oder neues hinzuf&uuml;gen."},
            {"I18N_NO_PROJECTS",         "Keine Projekte"},
            {"I18N_NO_PROJECT",          "Kein Projekt"},
            {"I18N_ALL_PROJECTS",        "Alle Projekte"},
            {"I18N_PROJECT_MANAGER_HINT","Projekte werden im Projekt-Manager verwaltet."},
            {"I18N_OPEN_PROJECT_MANAGER","Projekt-Manager &ouml;ffnen"},
            {"I18N_ADD",                 "Hinzuf&uuml;gen"},
            {"I18N_DELETE",              "L&ouml;schen"},
            {"I18N_FIELDS_REQUIRED",     "Name und URL sind erforderlich"},
@@ -855,6 +864,15 @@ namespace blogi {
            {"I18N_NO_CONNECTIONS",      "No connections"},
            {"I18N_CONN_MANAGER_HINT",   "Connections are managed in the Connection Manager."},
            {"I18N_OPEN_CONN_MANAGER",   "Open Connection Manager"},
            // Projects (editor toolbar/dialog wording for the same connection entity)
            {"I18N_PROJECT",             "Project"},
            {"I18N_PROJECTS",            "Projects"},
            {"I18N_SELECT_PROJECT",      "Select a project or add a new one."},
            {"I18N_NO_PROJECTS",         "No projects"},
            {"I18N_NO_PROJECT",          "No project"},
            {"I18N_ALL_PROJECTS",        "All projects"},
            {"I18N_PROJECT_MANAGER_HINT","Projects are managed in the Project Manager."},
            {"I18N_OPEN_PROJECT_MANAGER","Open Project Manager"},
            {"I18N_ADD",                 "Add"},
            {"I18N_DELETE",              "Delete"},
            {"I18N_FIELDS_REQUIRED",     "Name and URL are required"},