Loading editor/html/index.html +2 −1 Changes for editor/html/index.html: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ <div class="toolbar-group"> <button id="btn-new" data-i18n-title="I18N_TITLE_NEW_TEMPLATE">📄 <span data-i18n="I18N_NEW">Neu</span></button> <button id="btn-save" data-i18n-title="I18N_TITLE_SAVE_TO_DB">💾 <span data-i18n="I18N_SAVE">Speichern</span></button> <button id="btn-save-as" data-i18n-title="I18N_TITLE_SAVE_AS_TO_DB">💾 <span data-i18n="I18N_SAVE_AS">Speichern unter</span></button> <button id="btn-open" data-i18n-title="I18N_TEMPLATES">📂 <span data-i18n="I18N_TEMPLATES">Öffnen</span></button> <button id="btn-template-wizard" title="CSV Vorlagen-Wizard">🧪 <span>CSV-Wizard</span></button> </div> Loading Loading @@ -116,7 +117,7 @@ </dialog> <dialog id="save-dialog"> <h3 data-i18n="I18N_SAVE_TPL_TITLE">Dokument speichern</h3> <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_TPL_NAME_LABEL">Name</span>: <input type="text" id="save-name" data-i18n-placeholder="I18N_TPL_NAME_PLACEHOLDER" placeholder="Dokumentname"></label> <div class="dialog-actions"> Loading editor/html/js/api.js +4 −0 Changes for editor/html/js/api.js: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -212,6 +212,10 @@ var EditorApi = (function() { return request('POST', '/api/document/save', { name: name, group: group || '' }); }, saveDocumentAs: function(name, group) { return request('POST', '/api/document/save-as', { name: name, group: group || '' }); }, listTemplates: function() { return request('GET', '/api/templates'); }, Loading editor/html/js/editor.js +22 −5 Changes for editor/html/js/editor.js: 22 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ var plugins = []; var currentDocId = ''; var currentDocGroup = ''; var saveDialogMode = 'save'; var wizardTemplates = []; var wizardCsvRows = []; var wizardCsvColumns = []; Loading Loading @@ -190,10 +191,7 @@ document.getElementById('btn-save').addEventListener('click', function() { var name = document.getElementById('doc-name').textContent; if (name === 'Untitled') { var dialog = document.getElementById('save-dialog'); document.getElementById('save-name').value = ''; document.getElementById('save-group').value = currentDocGroup || ''; dialog.showModal(); openSaveDialog('save', '', currentDocGroup); } else { EditorApi.saveDocument(name, currentDocGroup).then(function(resp) { document.getElementById('doc-name').textContent = resp.name; Loading @@ -202,6 +200,11 @@ } }); document.getElementById('btn-save-as').addEventListener('click', function() { var name = document.getElementById('doc-name').textContent; openSaveDialog('save-as', name === 'Untitled' ? '' : name, currentDocGroup); }); document.getElementById('btn-open').addEventListener('click', function() { EditorApi.listDocuments().then(function(resp) { renderDocList(resp.documents || []); Loading Loading @@ -336,6 +339,18 @@ }); } // --- Save dialog --- function openSaveDialog(mode, name, group) { saveDialogMode = mode; document.getElementById('save-dialog-title').textContent = mode === 'save-as' ? 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 || ''; document.getElementById('save-dialog').showModal(); } // --- Dialog bindings --- function bindDialogs() { Loading @@ -344,7 +359,9 @@ var name = document.getElementById('save-name').value.trim(); if (!name) return; var group = document.getElementById('save-group').value.trim(); EditorApi.saveDocument(name, group).then(function(resp) { var savePromise = saveDialogMode === 'save-as' ? EditorApi.saveDocumentAs(name, group) : EditorApi.saveDocument(name, group); savePromise.then(function(resp) { document.getElementById('doc-name').textContent = resp.name; currentDocGroup = resp.group || ''; document.getElementById('save-dialog').close(); Loading editor/src/webedit_api.cpp +63 −0 Changes for editor/src/webedit_api.cpp: 63 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -613,6 +613,12 @@ bool webedit::Api::handleRequest(libhttppp::HttpRequest &curreq, const int tid, return true; } // Route: /api/document/save-as if (path == "/api/document/save-as") { handleSaveDocumentAs(curreq, sessionid); return true; } // Route: /api/templates if (path == "/api/templates") { handleListTemplates(curreq, sessionid); Loading Loading @@ -2178,6 +2184,63 @@ void webedit::Api::handleSaveDocument(libhttppp::HttpRequest &curreq, } } // --- Save current document as a new document ("Speichern unter") --- void webedit::Api::handleSaveDocumentAs(libhttppp::HttpRequest &curreq, const std::string &sessionid) { json_object *req = nullptr; try { std::string body = getRequestBody(curreq); req = json_tokener_parse(body.c_str()); json_object *nameObj = nullptr, *groupObj = nullptr; if (req) { json_object_object_get_ex(req, "name", &nameObj); json_object_object_get_ex(req, "group", &groupObj); } if (!nameObj || std::string(json_object_get_string(nameObj)).empty()) { if (req) json_object_put(req); sendJsonError(curreq, 400, "Name is required"); return; } auto &doc = getDocState(sessionid); std::lock_guard<std::mutex> lk(doc.mtx); doc.currentDocName = json_object_get_string(nameObj); if (groupObj) doc.currentDocGroup = json_object_get_string(groupObj); std::string xml = exportTreeXml(doc); std::string author, uid, domain; _session.getData(sessionid, "username", author); _session.getData(sessionid, "uid", uid); _session.getData(sessionid, "domain", domain); // Always insert a new document, regardless of whether the currently // open document was already saved — the open document then becomes // this new copy, matching classic "Save As" semantics. std::string docId = _db.saveDocument(uid, domain, doc.currentDocName, xml, author, doc.currentDocGroup); doc.currentDocId = docId; json_object *resp = json_object_new_object(); json_object_object_add(resp, "status", json_object_new_string("ok")); json_object_object_add(resp, "id", json_object_new_string(docId.c_str())); json_object_object_add(resp, "name", json_object_new_string(doc.currentDocName.c_str())); json_object_object_add(resp, "group", json_object_new_string(doc.currentDocGroup.c_str())); persistDocumentStateToSession(sessionid, doc); sendJson(curreq, resp); json_object_put(resp); if (req) json_object_put(req); } catch (const std::exception &e) { std::cerr << "[webedit] handleSaveDocumentAs failed: " << e.what() << std::endl; if (req) json_object_put(req); sendJsonError(curreq, 500, std::string("Save failed: ") + e.what()); } } void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, const std::string &sessionid) { std::string body = getRequestBody(curreq); Loading editor/src/webedit_api.h +1 −0 Changes for editor/src/webedit_api.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ namespace webedit { void handleExportXml(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleImportXml(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleSaveDocument(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleSaveDocumentAs(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleLoadDocument(libhttppp::HttpRequest &curreq, const std::string &sessionid, const std::string &docId); void handleListDocuments(libhttppp::HttpRequest &curreq, const std::string &sessionid); Loading Loading
editor/html/index.html +2 −1 Changes for editor/html/index.html: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ <div class="toolbar-group"> <button id="btn-new" data-i18n-title="I18N_TITLE_NEW_TEMPLATE">📄 <span data-i18n="I18N_NEW">Neu</span></button> <button id="btn-save" data-i18n-title="I18N_TITLE_SAVE_TO_DB">💾 <span data-i18n="I18N_SAVE">Speichern</span></button> <button id="btn-save-as" data-i18n-title="I18N_TITLE_SAVE_AS_TO_DB">💾 <span data-i18n="I18N_SAVE_AS">Speichern unter</span></button> <button id="btn-open" data-i18n-title="I18N_TEMPLATES">📂 <span data-i18n="I18N_TEMPLATES">Öffnen</span></button> <button id="btn-template-wizard" title="CSV Vorlagen-Wizard">🧪 <span>CSV-Wizard</span></button> </div> Loading Loading @@ -116,7 +117,7 @@ </dialog> <dialog id="save-dialog"> <h3 data-i18n="I18N_SAVE_TPL_TITLE">Dokument speichern</h3> <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_TPL_NAME_LABEL">Name</span>: <input type="text" id="save-name" data-i18n-placeholder="I18N_TPL_NAME_PLACEHOLDER" placeholder="Dokumentname"></label> <div class="dialog-actions"> Loading
editor/html/js/api.js +4 −0 Changes for editor/html/js/api.js: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -212,6 +212,10 @@ var EditorApi = (function() { return request('POST', '/api/document/save', { name: name, group: group || '' }); }, saveDocumentAs: function(name, group) { return request('POST', '/api/document/save-as', { name: name, group: group || '' }); }, listTemplates: function() { return request('GET', '/api/templates'); }, Loading
editor/html/js/editor.js +22 −5 Changes for editor/html/js/editor.js: 22 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ var plugins = []; var currentDocId = ''; var currentDocGroup = ''; var saveDialogMode = 'save'; var wizardTemplates = []; var wizardCsvRows = []; var wizardCsvColumns = []; Loading Loading @@ -190,10 +191,7 @@ document.getElementById('btn-save').addEventListener('click', function() { var name = document.getElementById('doc-name').textContent; if (name === 'Untitled') { var dialog = document.getElementById('save-dialog'); document.getElementById('save-name').value = ''; document.getElementById('save-group').value = currentDocGroup || ''; dialog.showModal(); openSaveDialog('save', '', currentDocGroup); } else { EditorApi.saveDocument(name, currentDocGroup).then(function(resp) { document.getElementById('doc-name').textContent = resp.name; Loading @@ -202,6 +200,11 @@ } }); document.getElementById('btn-save-as').addEventListener('click', function() { var name = document.getElementById('doc-name').textContent; openSaveDialog('save-as', name === 'Untitled' ? '' : name, currentDocGroup); }); document.getElementById('btn-open').addEventListener('click', function() { EditorApi.listDocuments().then(function(resp) { renderDocList(resp.documents || []); Loading Loading @@ -336,6 +339,18 @@ }); } // --- Save dialog --- function openSaveDialog(mode, name, group) { saveDialogMode = mode; document.getElementById('save-dialog-title').textContent = mode === 'save-as' ? 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 || ''; document.getElementById('save-dialog').showModal(); } // --- Dialog bindings --- function bindDialogs() { Loading @@ -344,7 +359,9 @@ var name = document.getElementById('save-name').value.trim(); if (!name) return; var group = document.getElementById('save-group').value.trim(); EditorApi.saveDocument(name, group).then(function(resp) { var savePromise = saveDialogMode === 'save-as' ? EditorApi.saveDocumentAs(name, group) : EditorApi.saveDocument(name, group); savePromise.then(function(resp) { document.getElementById('doc-name').textContent = resp.name; currentDocGroup = resp.group || ''; document.getElementById('save-dialog').close(); Loading
editor/src/webedit_api.cpp +63 −0 Changes for editor/src/webedit_api.cpp: 63 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -613,6 +613,12 @@ bool webedit::Api::handleRequest(libhttppp::HttpRequest &curreq, const int tid, return true; } // Route: /api/document/save-as if (path == "/api/document/save-as") { handleSaveDocumentAs(curreq, sessionid); return true; } // Route: /api/templates if (path == "/api/templates") { handleListTemplates(curreq, sessionid); Loading Loading @@ -2178,6 +2184,63 @@ void webedit::Api::handleSaveDocument(libhttppp::HttpRequest &curreq, } } // --- Save current document as a new document ("Speichern unter") --- void webedit::Api::handleSaveDocumentAs(libhttppp::HttpRequest &curreq, const std::string &sessionid) { json_object *req = nullptr; try { std::string body = getRequestBody(curreq); req = json_tokener_parse(body.c_str()); json_object *nameObj = nullptr, *groupObj = nullptr; if (req) { json_object_object_get_ex(req, "name", &nameObj); json_object_object_get_ex(req, "group", &groupObj); } if (!nameObj || std::string(json_object_get_string(nameObj)).empty()) { if (req) json_object_put(req); sendJsonError(curreq, 400, "Name is required"); return; } auto &doc = getDocState(sessionid); std::lock_guard<std::mutex> lk(doc.mtx); doc.currentDocName = json_object_get_string(nameObj); if (groupObj) doc.currentDocGroup = json_object_get_string(groupObj); std::string xml = exportTreeXml(doc); std::string author, uid, domain; _session.getData(sessionid, "username", author); _session.getData(sessionid, "uid", uid); _session.getData(sessionid, "domain", domain); // Always insert a new document, regardless of whether the currently // open document was already saved — the open document then becomes // this new copy, matching classic "Save As" semantics. std::string docId = _db.saveDocument(uid, domain, doc.currentDocName, xml, author, doc.currentDocGroup); doc.currentDocId = docId; json_object *resp = json_object_new_object(); json_object_object_add(resp, "status", json_object_new_string("ok")); json_object_object_add(resp, "id", json_object_new_string(docId.c_str())); json_object_object_add(resp, "name", json_object_new_string(doc.currentDocName.c_str())); json_object_object_add(resp, "group", json_object_new_string(doc.currentDocGroup.c_str())); persistDocumentStateToSession(sessionid, doc); sendJson(curreq, resp); json_object_put(resp); if (req) json_object_put(req); } catch (const std::exception &e) { std::cerr << "[webedit] handleSaveDocumentAs failed: " << e.what() << std::endl; if (req) json_object_put(req); sendJsonError(curreq, 500, std::string("Save failed: ") + e.what()); } } void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, const std::string &sessionid) { std::string body = getRequestBody(curreq); Loading
editor/src/webedit_api.h +1 −0 Changes for editor/src/webedit_api.h: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ namespace webedit { void handleExportXml(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleImportXml(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleSaveDocument(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleSaveDocumentAs(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleLoadDocument(libhttppp::HttpRequest &curreq, const std::string &sessionid, const std::string &docId); void handleListDocuments(libhttppp::HttpRequest &curreq, const std::string &sessionid); Loading