Loading debian/changelog +2.69 KiB (22.5 KiB) File changed.No diff preview for this file type. View original file View changed file editor/html/index.html +4 −0 Changes for editor/html/index.html: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -296,6 +296,10 @@ <label>Album</label> <select id="media-album-select"><option value="">-- Album --</option></select> </div> <div class="set-field media-upload-row"> <input type="file" id="media-upload-input" multiple> <button type="button" id="btn-media-upload" data-i18n="Upload">Hochladen</button> </div> <div id="media-grid" class="media-grid"></div> <span id="media-status" class="set-status"></span> <div class="dialog-actions"> Loading editor/html/js/media-browser.js +53 −0 Changes for editor/html/js/media-browser.js: 53 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,8 @@ var MediaBrowser = (function() { var _albumSelect = null; var _grid = null; var _status = null; var _uploadInput = null; var _uploadBtn = null; var _bound = false; function _ensureElements() { Loading @@ -20,6 +22,8 @@ var MediaBrowser = (function() { _albumSelect = document.getElementById('media-album-select'); _grid = document.getElementById('media-grid'); _status = document.getElementById('media-status'); _uploadInput = document.getElementById('media-upload-input'); _uploadBtn = document.getElementById('btn-media-upload'); } function _bind() { Loading @@ -40,6 +44,55 @@ var MediaBrowser = (function() { var albumId = _albumSelect.value; if (connId && albumId) _loadMedia(connId, albumId); }); _uploadBtn.addEventListener('click', _uploadSelectedFiles); } function _fileToBase64(file) { return new Promise(function(resolve, reject) { var reader = new FileReader(); reader.onload = function() { // Strip the "data:<mime>;base64," prefix — server wants raw base64. var result = reader.result || ''; var idx = result.indexOf(','); resolve(idx >= 0 ? result.slice(idx + 1) : result); }; reader.onerror = reject; reader.readAsDataURL(file); }); } function _uploadSelectedFiles() { var connId = _connSelect.value; var albumId = _albumSelect.value; if (!connId || !albumId) { _status.textContent = 'Select a connection and album first'; _status.className = 'set-status error'; return; } var files = _uploadInput.files; if (!files || files.length === 0) return; _status.textContent = 'Uploading...'; _status.className = 'set-status'; var chain = Promise.resolve(); Array.prototype.forEach.call(files, function(file) { chain = chain.then(function() { return _fileToBase64(file).then(function(b64) { return EditorApi.mediaUpload(connId, albumId, file.name, b64); }); }); }); chain.then(function() { _status.textContent = ''; _uploadInput.value = ''; _loadMedia(connId, albumId); }).catch(function(err) { _status.textContent = 'Upload failed: ' + (err.error || 'Unknown'); _status.className = 'set-status error'; }); } function open(onSelect) { Loading editor/html/js/properties.js +83 −0 Changes for editor/html/js/properties.js: 83 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -691,6 +691,7 @@ var PropertiesPanel = (function() { var srcInput = form.querySelector('input[name="src"]'); if (srcInput) srcInput.value = mediaItem.url; } _seedNaturalSize(form, mediaItem.url); } inp.dispatchEvent(new Event('change')); }); Loading Loading @@ -801,6 +802,88 @@ var PropertiesPanel = (function() { panel.appendChild(group); } _applyConditionalVisibility(panel, fields); _bindAspectRatioFields(panel, fields); } // Fields carrying a "visibleWhen: {key, value}" schema entry (e.g. Button's // link_url only makes sense when link_type === "url") are hidden until the // watched field's live value matches, and re-evaluated on every change — // the backend has emitted visibleWhen for a while, this is the frontend // half that was never wired up. function _applyConditionalVisibility(panel, fields) { for (var i = 0; i < fields.length; i++) { var field = fields[i]; if (!field.visibleWhen || !field.visibleWhen.key) continue; var group = panel.querySelector('[name="' + field.key + '"]'); if (!group) continue; group = group.closest('.prop-group'); if (!group) continue; var watched = panel.querySelector('[name="' + field.visibleWhen.key + '"]'); if (!watched) continue; var expected = String(field.visibleWhen.value); var evaluate = function(watchedEl, groupEl, expectedVal) { var actual = watchedEl.type === 'checkbox' ? String(watchedEl.checked) : watchedEl.value; groupEl.style.display = (actual === expectedVal) ? '' : 'none'; }; (function(watchedEl, groupEl, expectedVal) { evaluate(watchedEl, groupEl, expectedVal); watchedEl.addEventListener('input', function() { evaluate(watchedEl, groupEl, expectedVal); }); watchedEl.addEventListener('change', function() { evaluate(watchedEl, groupEl, expectedVal); }); })(watched, group, expected); } } // Loads the picked image off-DOM to read its natural pixel size, then // seeds real_width/real_height so a freshly-picked image gets sane // dimensions immediately instead of waiting for a save round-trip. function _seedNaturalSize(form, url) { if (!url) return; var wInput = form.querySelector('[name="real_width"]'); var hInput = form.querySelector('[name="real_height"]'); if (!wInput || !hInput) return; var probe = new Image(); probe.onload = function() { wInput.value = probe.naturalWidth; hInput.value = probe.naturalHeight; wInput.setAttribute('data-prev', probe.naturalWidth); hInput.setAttribute('data-prev', probe.naturalHeight); wInput.dispatchEvent(new Event('input', {bubbles: true})); }; probe.src = url; } // If a widget's schema has both real_width and real_height (currently only // Image), keep them in proportion: editing one recalculates the other from // whatever aspect ratio the two fields' last known values describe. function _bindAspectRatioFields(panel, fields) { var wInput = panel.querySelector('[name="real_width"]'); var hInput = panel.querySelector('[name="real_height"]'); if (!wInput || !hInput) return; var syncing = false; function bind(source, target) { source.addEventListener('input', function() { if (syncing) return; var sVal = parseFloat(source.value), tVal = parseFloat(target.value); var sOld = parseFloat(source.getAttribute('data-prev') || sVal); if (!sVal || !tVal || !sOld) { source.setAttribute('data-prev', sVal || ''); return; } syncing = true; target.value = Math.round(tVal * (sVal / sOld)); syncing = false; source.setAttribute('data-prev', sVal); wInput.setAttribute('data-prev', wInput.value); hInput.setAttribute('data-prev', hInput.value); }); } wInput.setAttribute('data-prev', wInput.value); hInput.setAttribute('data-prev', hInput.value); bind(wInput, hInput); bind(hInput, wInput); } function saveProperties() { Loading editor/src/webedit_api.cpp +1 −14 Changes for editor/src/webedit_api.cpp: 1 added line, 14 removed lines. Original line number Diff line number Diff line Loading @@ -479,9 +479,6 @@ void webedit::Api::loadDocumentTreeFromXml(DocumentState &doc, const std::string auto *el = createPluginByName(typeName, doc, typeIdAttr ? std::string(typeIdAttr) : std::string()); std::cerr << "[llms-debug] load TOP <" << typeName << "> type_id=" << (typeIdAttr ? typeIdAttr : "(none)") << " -> " << (el ? "OK" : "FAILED, skipped") << std::endl; if (!el) continue; Loading Loading @@ -3903,8 +3900,6 @@ webedit::Api::PublishCoreResult webedit::Api::publishToConnectionCore( std::string html = renderTree(doc.root); std::string llmsTxt = generateLlmsTxt(doc.root); std::cerr << "[llms-debug] publish: html.size()=" << html.size() << " llmsTxt.size()=" << llmsTxt.size() << std::endl; // Build the publish command: // [{"authid":"..."}, {"command":"publish","target":"template","html":"...","params":{...}}] Loading Loading @@ -4717,12 +4712,7 @@ namespace { // have its children walked (e.g. Accordion's panel content). void collectLlmsBlocks(const blogi::webedit::EditPlugin *el, std::vector<std::string> &blocks) { for (const blogi::webedit::EditPlugin *cur = el; cur; cur = cur->nextElement()) { auto block = cur->toLlmsBlock(); std::cerr << "[llms-debug] visit " << cur->getName() << " block=" << (block ? std::to_string(block->size()) + "bytes" : "nullopt") << " child=" << (cur->getChildElement() ? "yes" : "no") << " next=" << (cur->nextElement() ? "yes" : "no") << std::endl; if (block) if (auto block = cur->toLlmsBlock()) appendBlock(blocks, *block); if (const blogi::webedit::EditPlugin *child = cur->getChildElement()) collectLlmsBlocks(child, blocks); Loading Loading @@ -4887,9 +4877,6 @@ void webedit::Api::loadChildrenFromXml(tinyxml2::XMLElement *parentXml, auto *childEl = createPluginByName(typeName, doc, typeIdAttr ? std::string(typeIdAttr) : std::string()); std::cerr << "[llms-debug] load child <" << typeName << "> type_id=" << (typeIdAttr ? typeIdAttr : "(none)") << " -> " << (childEl ? "OK" : "FAILED, skipped") << std::endl; if (!childEl) continue; childEl->fromXmlObject(childXml); Loading Loading
debian/changelog +2.69 KiB (22.5 KiB) File changed.No diff preview for this file type. View original file View changed file
editor/html/index.html +4 −0 Changes for editor/html/index.html: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -296,6 +296,10 @@ <label>Album</label> <select id="media-album-select"><option value="">-- Album --</option></select> </div> <div class="set-field media-upload-row"> <input type="file" id="media-upload-input" multiple> <button type="button" id="btn-media-upload" data-i18n="Upload">Hochladen</button> </div> <div id="media-grid" class="media-grid"></div> <span id="media-status" class="set-status"></span> <div class="dialog-actions"> Loading
editor/html/js/media-browser.js +53 −0 Changes for editor/html/js/media-browser.js: 53 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,8 @@ var MediaBrowser = (function() { var _albumSelect = null; var _grid = null; var _status = null; var _uploadInput = null; var _uploadBtn = null; var _bound = false; function _ensureElements() { Loading @@ -20,6 +22,8 @@ var MediaBrowser = (function() { _albumSelect = document.getElementById('media-album-select'); _grid = document.getElementById('media-grid'); _status = document.getElementById('media-status'); _uploadInput = document.getElementById('media-upload-input'); _uploadBtn = document.getElementById('btn-media-upload'); } function _bind() { Loading @@ -40,6 +44,55 @@ var MediaBrowser = (function() { var albumId = _albumSelect.value; if (connId && albumId) _loadMedia(connId, albumId); }); _uploadBtn.addEventListener('click', _uploadSelectedFiles); } function _fileToBase64(file) { return new Promise(function(resolve, reject) { var reader = new FileReader(); reader.onload = function() { // Strip the "data:<mime>;base64," prefix — server wants raw base64. var result = reader.result || ''; var idx = result.indexOf(','); resolve(idx >= 0 ? result.slice(idx + 1) : result); }; reader.onerror = reject; reader.readAsDataURL(file); }); } function _uploadSelectedFiles() { var connId = _connSelect.value; var albumId = _albumSelect.value; if (!connId || !albumId) { _status.textContent = 'Select a connection and album first'; _status.className = 'set-status error'; return; } var files = _uploadInput.files; if (!files || files.length === 0) return; _status.textContent = 'Uploading...'; _status.className = 'set-status'; var chain = Promise.resolve(); Array.prototype.forEach.call(files, function(file) { chain = chain.then(function() { return _fileToBase64(file).then(function(b64) { return EditorApi.mediaUpload(connId, albumId, file.name, b64); }); }); }); chain.then(function() { _status.textContent = ''; _uploadInput.value = ''; _loadMedia(connId, albumId); }).catch(function(err) { _status.textContent = 'Upload failed: ' + (err.error || 'Unknown'); _status.className = 'set-status error'; }); } function open(onSelect) { Loading
editor/html/js/properties.js +83 −0 Changes for editor/html/js/properties.js: 83 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -691,6 +691,7 @@ var PropertiesPanel = (function() { var srcInput = form.querySelector('input[name="src"]'); if (srcInput) srcInput.value = mediaItem.url; } _seedNaturalSize(form, mediaItem.url); } inp.dispatchEvent(new Event('change')); }); Loading Loading @@ -801,6 +802,88 @@ var PropertiesPanel = (function() { panel.appendChild(group); } _applyConditionalVisibility(panel, fields); _bindAspectRatioFields(panel, fields); } // Fields carrying a "visibleWhen: {key, value}" schema entry (e.g. Button's // link_url only makes sense when link_type === "url") are hidden until the // watched field's live value matches, and re-evaluated on every change — // the backend has emitted visibleWhen for a while, this is the frontend // half that was never wired up. function _applyConditionalVisibility(panel, fields) { for (var i = 0; i < fields.length; i++) { var field = fields[i]; if (!field.visibleWhen || !field.visibleWhen.key) continue; var group = panel.querySelector('[name="' + field.key + '"]'); if (!group) continue; group = group.closest('.prop-group'); if (!group) continue; var watched = panel.querySelector('[name="' + field.visibleWhen.key + '"]'); if (!watched) continue; var expected = String(field.visibleWhen.value); var evaluate = function(watchedEl, groupEl, expectedVal) { var actual = watchedEl.type === 'checkbox' ? String(watchedEl.checked) : watchedEl.value; groupEl.style.display = (actual === expectedVal) ? '' : 'none'; }; (function(watchedEl, groupEl, expectedVal) { evaluate(watchedEl, groupEl, expectedVal); watchedEl.addEventListener('input', function() { evaluate(watchedEl, groupEl, expectedVal); }); watchedEl.addEventListener('change', function() { evaluate(watchedEl, groupEl, expectedVal); }); })(watched, group, expected); } } // Loads the picked image off-DOM to read its natural pixel size, then // seeds real_width/real_height so a freshly-picked image gets sane // dimensions immediately instead of waiting for a save round-trip. function _seedNaturalSize(form, url) { if (!url) return; var wInput = form.querySelector('[name="real_width"]'); var hInput = form.querySelector('[name="real_height"]'); if (!wInput || !hInput) return; var probe = new Image(); probe.onload = function() { wInput.value = probe.naturalWidth; hInput.value = probe.naturalHeight; wInput.setAttribute('data-prev', probe.naturalWidth); hInput.setAttribute('data-prev', probe.naturalHeight); wInput.dispatchEvent(new Event('input', {bubbles: true})); }; probe.src = url; } // If a widget's schema has both real_width and real_height (currently only // Image), keep them in proportion: editing one recalculates the other from // whatever aspect ratio the two fields' last known values describe. function _bindAspectRatioFields(panel, fields) { var wInput = panel.querySelector('[name="real_width"]'); var hInput = panel.querySelector('[name="real_height"]'); if (!wInput || !hInput) return; var syncing = false; function bind(source, target) { source.addEventListener('input', function() { if (syncing) return; var sVal = parseFloat(source.value), tVal = parseFloat(target.value); var sOld = parseFloat(source.getAttribute('data-prev') || sVal); if (!sVal || !tVal || !sOld) { source.setAttribute('data-prev', sVal || ''); return; } syncing = true; target.value = Math.round(tVal * (sVal / sOld)); syncing = false; source.setAttribute('data-prev', sVal); wInput.setAttribute('data-prev', wInput.value); hInput.setAttribute('data-prev', hInput.value); }); } wInput.setAttribute('data-prev', wInput.value); hInput.setAttribute('data-prev', hInput.value); bind(wInput, hInput); bind(hInput, wInput); } function saveProperties() { Loading
editor/src/webedit_api.cpp +1 −14 Changes for editor/src/webedit_api.cpp: 1 added line, 14 removed lines. Original line number Diff line number Diff line Loading @@ -479,9 +479,6 @@ void webedit::Api::loadDocumentTreeFromXml(DocumentState &doc, const std::string auto *el = createPluginByName(typeName, doc, typeIdAttr ? std::string(typeIdAttr) : std::string()); std::cerr << "[llms-debug] load TOP <" << typeName << "> type_id=" << (typeIdAttr ? typeIdAttr : "(none)") << " -> " << (el ? "OK" : "FAILED, skipped") << std::endl; if (!el) continue; Loading Loading @@ -3903,8 +3900,6 @@ webedit::Api::PublishCoreResult webedit::Api::publishToConnectionCore( std::string html = renderTree(doc.root); std::string llmsTxt = generateLlmsTxt(doc.root); std::cerr << "[llms-debug] publish: html.size()=" << html.size() << " llmsTxt.size()=" << llmsTxt.size() << std::endl; // Build the publish command: // [{"authid":"..."}, {"command":"publish","target":"template","html":"...","params":{...}}] Loading Loading @@ -4717,12 +4712,7 @@ namespace { // have its children walked (e.g. Accordion's panel content). void collectLlmsBlocks(const blogi::webedit::EditPlugin *el, std::vector<std::string> &blocks) { for (const blogi::webedit::EditPlugin *cur = el; cur; cur = cur->nextElement()) { auto block = cur->toLlmsBlock(); std::cerr << "[llms-debug] visit " << cur->getName() << " block=" << (block ? std::to_string(block->size()) + "bytes" : "nullopt") << " child=" << (cur->getChildElement() ? "yes" : "no") << " next=" << (cur->nextElement() ? "yes" : "no") << std::endl; if (block) if (auto block = cur->toLlmsBlock()) appendBlock(blocks, *block); if (const blogi::webedit::EditPlugin *child = cur->getChildElement()) collectLlmsBlocks(child, blocks); Loading Loading @@ -4887,9 +4877,6 @@ void webedit::Api::loadChildrenFromXml(tinyxml2::XMLElement *parentXml, auto *childEl = createPluginByName(typeName, doc, typeIdAttr ? std::string(typeIdAttr) : std::string()); std::cerr << "[llms-debug] load child <" << typeName << "> type_id=" << (typeIdAttr ? typeIdAttr : "(none)") << " -> " << (childEl ? "OK" : "FAILED, skipped") << std::endl; if (!childEl) continue; childEl->fromXmlObject(childXml); Loading