Expose typed content fields for first home blocks

This commit is contained in:
dcconstructions
2026-06-25 22:03:35 +03:00
parent aaae2da433
commit 69678a441e
10 changed files with 672 additions and 9 deletions
+35
View File
@@ -197,6 +197,37 @@ h2 {
gap: 14px;
}
.content-panel {
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(255, 255, 255, 0.02);
padding: 16px;
}
.section-title {
margin-bottom: 14px;
color: var(--text);
font-size: 14px;
font-weight: 800;
}
.content-fields {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.content-fields .wide {
grid-column: 1 / -1;
}
.content-fields .empty-note {
grid-column: 1 / -1;
color: var(--muted);
font-size: 14px;
line-height: 1.4;
}
label {
display: grid;
gap: 8px;
@@ -265,4 +296,8 @@ textarea:focus {
.field-grid {
grid-template-columns: 1fr;
}
.content-fields {
grid-template-columns: 1fr;
}
}
+70
View File
@@ -19,6 +19,7 @@ const el = {
template: document.querySelector("#block-template"),
anchor: document.querySelector("#block-anchor"),
enabled: document.querySelector("#block-enabled"),
contentFields: document.querySelector("#content-fields"),
json: document.querySelector("#block-json"),
moveUp: document.querySelector("#move-up"),
moveDown: document.querySelector("#move-down"),
@@ -36,6 +37,25 @@ function clone(value) {
return JSON.parse(JSON.stringify(value));
}
function getByPath(source, path) {
return path.split(".").reduce((value, key) => value?.[key], source);
}
function setByPath(source, path, nextValue) {
const keys = path.split(".");
const lastKey = keys.pop();
let target = source;
for (const key of keys) {
if (target[key] == null) {
target[key] = /^\d+$/.test(keys[0]) ? [] : {};
}
target = target[key];
}
target[lastKey] = nextValue;
}
function activeRegion() {
if (!state.selected) return null;
return state.page.regions[state.selected.regionIndex];
@@ -118,6 +138,7 @@ function renderEditor() {
el.template.value = block.template || "";
el.anchor.value = block.anchor || "";
el.enabled.checked = block.enabled !== false;
renderContentFields(block);
el.json.value = JSON.stringify(block, null, 2);
}
@@ -130,6 +151,7 @@ function syncBlockFromFields() {
const block = activeBlock();
if (!block) return;
syncContentFields();
block.id = el.id.value.trim();
block.adminLabel = el.label.value.trim();
block.anchor = el.anchor.value.trim() || null;
@@ -138,6 +160,54 @@ function syncBlockFromFields() {
renderRegions();
}
function renderContentFields(block) {
el.contentFields.innerHTML = "";
if (!block.editableFields?.length) {
const note = document.createElement("div");
note.className = "empty-note";
note.textContent = "Для этого блока typed-поля ещё не выделены. Пока доступно редактирование через JSON.";
el.contentFields.append(note);
return;
}
for (const editableField of block.editableFields) {
const label = document.createElement("label");
const value = getByPath(block, editableField.path) ?? "";
const isLong = editableField.kind === "html" || editableField.kind === "textarea";
const input = isLong ? document.createElement("textarea") : document.createElement("input");
label.className = isLong ? "wide" : "";
label.textContent = editableField.label || editableField.path;
input.dataset.path = editableField.path;
input.value = value;
if (!isLong) {
input.type = editableField.kind === "media" || editableField.kind === "url" ? "text" : "text";
input.autocomplete = "off";
}
input.addEventListener("input", () => {
const active = activeBlock();
if (!active) return;
setByPath(active, editableField.path, input.value);
el.json.value = JSON.stringify(active, null, 2);
});
label.append(input);
el.contentFields.append(label);
}
}
function syncContentFields() {
const block = activeBlock();
if (!block) return;
for (const input of el.contentFields.querySelectorAll("[data-path]")) {
setByPath(block, input.dataset.path, input.value);
}
}
function syncBlockFromJson() {
const region = activeRegion();
if (!region) return false;
+5
View File
@@ -71,6 +71,11 @@
<button id="delete-block" class="danger-btn" type="button">Удалить</button>
</div>
<section class="content-panel">
<div class="section-title">Контент блока</div>
<div id="content-fields" class="content-fields"></div>
</section>
<label class="json-field">
JSON выбранного блока
<textarea id="block-json" spellcheck="false"></textarea>