Loading editor/html/css/editor.css +34 −0 Original line number Diff line number Diff line Loading @@ -376,6 +376,40 @@ body { background: var(--accent-hover); } /* Property panel tabs */ .prop-tabs { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 8px; } .prop-tab { flex: 1; padding: 6px 8px; background: none; color: var(--text-secondary); border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 12px; font-weight: 500; transition: all 0.15s; } .prop-tab:hover { color: var(--text-primary); background: var(--bg-tertiary); } .prop-tab.active { color: var(--accent); border-bottom-color: var(--accent); } .prop-panel { padding-top: 4px; } /* Dialogs */ dialog { background: var(--bg-secondary); Loading editor/html/js/properties.js +80 −16 Original line number Diff line number Diff line Loading @@ -54,6 +54,46 @@ var PropertiesPanel = (function() { return; } // Classify fields by target var desktopFields = []; var mobileFields = []; for (var i = 0; i < schema.length; i++) { var t = schema[i].target || 'all'; if (t === 'mobile') { mobileFields.push(schema[i]); } else { desktopFields.push(schema[i]); } } // Tab bar var tabBar = document.createElement('div'); tabBar.className = 'prop-tabs'; var tabs = [ { id: 'desktop', label: '\uD83D\uDDA5 Desktop' }, { id: 'mobile', label: '\uD83D\uDCF1 Mobile' } ]; var panels = {}; for (var ti = 0; ti < tabs.length; ti++) { var tabBtn = document.createElement('button'); tabBtn.type = 'button'; tabBtn.className = 'prop-tab' + (ti === 0 ? ' active' : ''); tabBtn.textContent = tabs[ti].label; tabBtn.setAttribute('data-tab', tabs[ti].id); tabBtn.addEventListener('click', function() { var allTabs = tabBar.querySelectorAll('.prop-tab'); for (var k = 0; k < allTabs.length; k++) allTabs[k].classList.remove('active'); this.classList.add('active'); var target = this.getAttribute('data-tab'); for (var pid in panels) { panels[pid].style.display = (pid === target) ? '' : 'none'; } }); tabBar.appendChild(tabBtn); } container.appendChild(tabBar); var form = document.createElement('form'); form.id = 'prop-form'; form.addEventListener('submit', function(e) { Loading @@ -61,8 +101,34 @@ var PropertiesPanel = (function() { saveProperties(); }); for (var i = 0; i < schema.length; i++) { var field = schema[i]; // Create panels var desktopPanel = document.createElement('div'); desktopPanel.className = 'prop-panel'; panels['desktop'] = desktopPanel; var mobilePanel = document.createElement('div'); mobilePanel.className = 'prop-panel'; mobilePanel.style.display = 'none'; panels['mobile'] = mobilePanel; _renderFieldsInto(desktopPanel, desktopFields, properties, form); _renderFieldsInto(mobilePanel, mobileFields, properties, form); form.appendChild(desktopPanel); form.appendChild(mobilePanel); var saveBtn = document.createElement('button'); saveBtn.type = 'submit'; saveBtn.className = 'prop-save-btn'; saveBtn.textContent = 'Speichern'; form.appendChild(saveBtn); container.appendChild(form); } function _renderFieldsInto(panel, fields, properties, form) { for (var i = 0; i < fields.length; i++) { var field = fields[i]; var group = document.createElement('div'); group.className = 'prop-group'; Loading @@ -81,9 +147,6 @@ var PropertiesPanel = (function() { } else { var label = document.createElement('label'); label.textContent = field.label; if (field.target && field.target !== 'all') { label.textContent += ' (' + field.target + ')'; } group.appendChild(label); var input; Loading Loading @@ -143,7 +206,7 @@ var PropertiesPanel = (function() { wrapper.appendChild(input); wrapper.appendChild(browseBtn); group.appendChild(wrapper); form.appendChild(group); panel.appendChild(group); continue; } else { input = document.createElement('input'); Loading @@ -156,16 +219,8 @@ var PropertiesPanel = (function() { group.appendChild(input); } form.appendChild(group); panel.appendChild(group); } var saveBtn = document.createElement('button'); saveBtn.type = 'submit'; saveBtn.className = 'prop-save-btn'; saveBtn.textContent = 'Speichern'; form.appendChild(saveBtn); container.appendChild(form); } function saveProperties() { Loading @@ -185,7 +240,16 @@ var PropertiesPanel = (function() { } } EditorApi.setProperties(currentUuid, properties).then(function() { EditorApi.setProperties(currentUuid, properties).then(function(resp) { // Update form fields with computed dimensions from blog if (resp && resp.real_width) { var rwInput = form.querySelector('input[name="real_width"]'); if (rwInput) rwInput.value = resp.real_width; } if (resp && resp.real_height) { var rhInput = form.querySelector('input[name="real_height"]'); if (rhInput) rhInput.value = resp.real_height; } // Refresh preview after property change if (typeof Preview !== 'undefined') { Preview.refresh(); Loading editor/src/webedit_api.cpp +46 −7 Original line number Diff line number Diff line Loading @@ -712,18 +712,18 @@ void webedit::Api::handleSetProperties(libhttppp::HttpRequest &curreq, // If properties contain media_id + real_width/real_height, register // the preview size with the blog so media/getimage allows it. // When only one dimension is given, compute the other from the aspect ratio. json_object *propsObj = nullptr; if (json_object_object_get_ex(reqJson, "properties", &propsObj)) { json_object *midObj = nullptr, *rwObj = nullptr, *rhObj = nullptr; if (json_object_object_get_ex(propsObj, "media_id", &midObj) && json_object_object_get_ex(propsObj, "real_width", &rwObj)) { std::string mediaId = json_object_get_string(midObj); int rw = json_object_get_int(rwObj); int rh = 0; if (json_object_object_get_ex(propsObj, "media_id", &midObj)) { std::string mediaId = midObj ? json_object_get_string(midObj) : ""; int rw = 0, rh = 0; if (json_object_object_get_ex(propsObj, "real_width", &rwObj)) rw = json_object_get_int(rwObj); if (json_object_object_get_ex(propsObj, "real_height", &rhObj)) rh = json_object_get_int(rhObj); if (!mediaId.empty() && (rw > 0 || rh > 0)) { // Fire-and-forget: register preview size via first active connection std::string authid, blogUrl; { std::lock_guard<std::mutex> clk(_connSessionMtx); Loading @@ -747,7 +747,46 @@ void webedit::Api::handleSetProperties(libhttppp::HttpRequest &curreq, json_object_array_add(apiArr, cmd); json_object *apiResp = blogApiCall(blogUrl, apiArr); json_object_put(apiArr); if (apiResp) json_object_put(apiResp); if (apiResp) { // Extract actual dimensions from blog response and // update the widget if a dimension was missing. if (json_object_is_type(apiResp, json_type_array)) { size_t len = json_object_array_length(apiResp); for (size_t i = 0; i < len; ++i) { json_object *item = json_object_array_get_idx(apiResp, i); json_object *aw = nullptr, *ah = nullptr; json_object_object_get_ex(item, "width", &aw); json_object_object_get_ex(item, "height", &ah); if (aw && ah) { int actualW = json_object_get_int(aw); int actualH = json_object_get_int(ah); if ((rw <= 0 || rh <= 0) && actualW > 0 && actualH > 0) { // Update widget with computed dimensions json_object *fixReq = json_object_new_object(); json_object_object_add(fixReq, "action", json_object_new_string("modify_bulk")); json_object *fixProps = json_object_new_object(); json_object_object_add(fixProps, "real_width", json_object_new_int(actualW)); json_object_object_add(fixProps, "real_height", json_object_new_int(actualH)); json_object_object_add(fixReq, "properties", fixProps); json_object *fixResp = json_object_new_object(); el->JsonApi(fixReq, fixResp); // Include computed dimensions in response json_object_object_add(respJson, "real_width", json_object_new_int(actualW)); json_object_object_add(respJson, "real_height", json_object_new_int(actualH)); json_object_put(fixReq); json_object_put(fixResp); } break; } } } json_object_put(apiResp); } } catch (...) { // Best effort — don't fail the property save } Loading Loading
editor/html/css/editor.css +34 −0 Original line number Diff line number Diff line Loading @@ -376,6 +376,40 @@ body { background: var(--accent-hover); } /* Property panel tabs */ .prop-tabs { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 8px; } .prop-tab { flex: 1; padding: 6px 8px; background: none; color: var(--text-secondary); border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 12px; font-weight: 500; transition: all 0.15s; } .prop-tab:hover { color: var(--text-primary); background: var(--bg-tertiary); } .prop-tab.active { color: var(--accent); border-bottom-color: var(--accent); } .prop-panel { padding-top: 4px; } /* Dialogs */ dialog { background: var(--bg-secondary); Loading
editor/html/js/properties.js +80 −16 Original line number Diff line number Diff line Loading @@ -54,6 +54,46 @@ var PropertiesPanel = (function() { return; } // Classify fields by target var desktopFields = []; var mobileFields = []; for (var i = 0; i < schema.length; i++) { var t = schema[i].target || 'all'; if (t === 'mobile') { mobileFields.push(schema[i]); } else { desktopFields.push(schema[i]); } } // Tab bar var tabBar = document.createElement('div'); tabBar.className = 'prop-tabs'; var tabs = [ { id: 'desktop', label: '\uD83D\uDDA5 Desktop' }, { id: 'mobile', label: '\uD83D\uDCF1 Mobile' } ]; var panels = {}; for (var ti = 0; ti < tabs.length; ti++) { var tabBtn = document.createElement('button'); tabBtn.type = 'button'; tabBtn.className = 'prop-tab' + (ti === 0 ? ' active' : ''); tabBtn.textContent = tabs[ti].label; tabBtn.setAttribute('data-tab', tabs[ti].id); tabBtn.addEventListener('click', function() { var allTabs = tabBar.querySelectorAll('.prop-tab'); for (var k = 0; k < allTabs.length; k++) allTabs[k].classList.remove('active'); this.classList.add('active'); var target = this.getAttribute('data-tab'); for (var pid in panels) { panels[pid].style.display = (pid === target) ? '' : 'none'; } }); tabBar.appendChild(tabBtn); } container.appendChild(tabBar); var form = document.createElement('form'); form.id = 'prop-form'; form.addEventListener('submit', function(e) { Loading @@ -61,8 +101,34 @@ var PropertiesPanel = (function() { saveProperties(); }); for (var i = 0; i < schema.length; i++) { var field = schema[i]; // Create panels var desktopPanel = document.createElement('div'); desktopPanel.className = 'prop-panel'; panels['desktop'] = desktopPanel; var mobilePanel = document.createElement('div'); mobilePanel.className = 'prop-panel'; mobilePanel.style.display = 'none'; panels['mobile'] = mobilePanel; _renderFieldsInto(desktopPanel, desktopFields, properties, form); _renderFieldsInto(mobilePanel, mobileFields, properties, form); form.appendChild(desktopPanel); form.appendChild(mobilePanel); var saveBtn = document.createElement('button'); saveBtn.type = 'submit'; saveBtn.className = 'prop-save-btn'; saveBtn.textContent = 'Speichern'; form.appendChild(saveBtn); container.appendChild(form); } function _renderFieldsInto(panel, fields, properties, form) { for (var i = 0; i < fields.length; i++) { var field = fields[i]; var group = document.createElement('div'); group.className = 'prop-group'; Loading @@ -81,9 +147,6 @@ var PropertiesPanel = (function() { } else { var label = document.createElement('label'); label.textContent = field.label; if (field.target && field.target !== 'all') { label.textContent += ' (' + field.target + ')'; } group.appendChild(label); var input; Loading Loading @@ -143,7 +206,7 @@ var PropertiesPanel = (function() { wrapper.appendChild(input); wrapper.appendChild(browseBtn); group.appendChild(wrapper); form.appendChild(group); panel.appendChild(group); continue; } else { input = document.createElement('input'); Loading @@ -156,16 +219,8 @@ var PropertiesPanel = (function() { group.appendChild(input); } form.appendChild(group); panel.appendChild(group); } var saveBtn = document.createElement('button'); saveBtn.type = 'submit'; saveBtn.className = 'prop-save-btn'; saveBtn.textContent = 'Speichern'; form.appendChild(saveBtn); container.appendChild(form); } function saveProperties() { Loading @@ -185,7 +240,16 @@ var PropertiesPanel = (function() { } } EditorApi.setProperties(currentUuid, properties).then(function() { EditorApi.setProperties(currentUuid, properties).then(function(resp) { // Update form fields with computed dimensions from blog if (resp && resp.real_width) { var rwInput = form.querySelector('input[name="real_width"]'); if (rwInput) rwInput.value = resp.real_width; } if (resp && resp.real_height) { var rhInput = form.querySelector('input[name="real_height"]'); if (rhInput) rhInput.value = resp.real_height; } // Refresh preview after property change if (typeof Preview !== 'undefined') { Preview.refresh(); Loading
editor/src/webedit_api.cpp +46 −7 Original line number Diff line number Diff line Loading @@ -712,18 +712,18 @@ void webedit::Api::handleSetProperties(libhttppp::HttpRequest &curreq, // If properties contain media_id + real_width/real_height, register // the preview size with the blog so media/getimage allows it. // When only one dimension is given, compute the other from the aspect ratio. json_object *propsObj = nullptr; if (json_object_object_get_ex(reqJson, "properties", &propsObj)) { json_object *midObj = nullptr, *rwObj = nullptr, *rhObj = nullptr; if (json_object_object_get_ex(propsObj, "media_id", &midObj) && json_object_object_get_ex(propsObj, "real_width", &rwObj)) { std::string mediaId = json_object_get_string(midObj); int rw = json_object_get_int(rwObj); int rh = 0; if (json_object_object_get_ex(propsObj, "media_id", &midObj)) { std::string mediaId = midObj ? json_object_get_string(midObj) : ""; int rw = 0, rh = 0; if (json_object_object_get_ex(propsObj, "real_width", &rwObj)) rw = json_object_get_int(rwObj); if (json_object_object_get_ex(propsObj, "real_height", &rhObj)) rh = json_object_get_int(rhObj); if (!mediaId.empty() && (rw > 0 || rh > 0)) { // Fire-and-forget: register preview size via first active connection std::string authid, blogUrl; { std::lock_guard<std::mutex> clk(_connSessionMtx); Loading @@ -747,7 +747,46 @@ void webedit::Api::handleSetProperties(libhttppp::HttpRequest &curreq, json_object_array_add(apiArr, cmd); json_object *apiResp = blogApiCall(blogUrl, apiArr); json_object_put(apiArr); if (apiResp) json_object_put(apiResp); if (apiResp) { // Extract actual dimensions from blog response and // update the widget if a dimension was missing. if (json_object_is_type(apiResp, json_type_array)) { size_t len = json_object_array_length(apiResp); for (size_t i = 0; i < len; ++i) { json_object *item = json_object_array_get_idx(apiResp, i); json_object *aw = nullptr, *ah = nullptr; json_object_object_get_ex(item, "width", &aw); json_object_object_get_ex(item, "height", &ah); if (aw && ah) { int actualW = json_object_get_int(aw); int actualH = json_object_get_int(ah); if ((rw <= 0 || rh <= 0) && actualW > 0 && actualH > 0) { // Update widget with computed dimensions json_object *fixReq = json_object_new_object(); json_object_object_add(fixReq, "action", json_object_new_string("modify_bulk")); json_object *fixProps = json_object_new_object(); json_object_object_add(fixProps, "real_width", json_object_new_int(actualW)); json_object_object_add(fixProps, "real_height", json_object_new_int(actualH)); json_object_object_add(fixReq, "properties", fixProps); json_object *fixResp = json_object_new_object(); el->JsonApi(fixReq, fixResp); // Include computed dimensions in response json_object_object_add(respJson, "real_width", json_object_new_int(actualW)); json_object_object_add(respJson, "real_height", json_object_new_int(actualH)); json_object_put(fixReq); json_object_put(fixResp); } break; } } } json_object_put(apiResp); } } catch (...) { // Best effort — don't fail the property save } Loading