|
|
|
@ -1283,7 +1283,11 @@ HTML = """<!doctype html>
|
|
|
|
.config-toolbar button, .config-restore-panel button { min-height: 26px; padding: 0 8px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-muted); font: 700 10px/1 ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: border-color .18s ease, color .18s ease, background .18s ease, transform .18s ease; }
|
|
|
|
.config-toolbar button, .config-restore-panel button { min-height: 26px; padding: 0 8px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-muted); font: 700 10px/1 ui-sans-serif, system-ui, sans-serif; cursor: pointer; transition: border-color .18s ease, color .18s ease, background .18s ease, transform .18s ease; }
|
|
|
|
.config-toolbar button:hover, .config-toolbar button:focus-visible, .config-restore-panel button:hover, .config-restore-panel button:focus-visible { outline: 0; border-color: var(--ui-accent); color: var(--ui-accent); transform: translateY(-1px); }
|
|
|
|
.config-toolbar button:hover, .config-toolbar button:focus-visible, .config-restore-panel button:hover, .config-restore-panel button:focus-visible { outline: 0; border-color: var(--ui-accent); color: var(--ui-accent); transform: translateY(-1px); }
|
|
|
|
.config-layout-state { min-width: 0; color: var(--ui-muted); font-size: 10px; }
|
|
|
|
.config-layout-state { min-width: 0; color: var(--ui-muted); font-size: 10px; }
|
|
|
|
.config-restore-panel { display: grid; gap: 8px; padding: 8px 10px; border-bottom: 1px solid var(--ui-border); background: color-mix(in srgb, var(--ui-input) 45%, transparent); }
|
|
|
|
.config-workspace { display: grid; grid-template-columns: minmax(0, 1fr); min-width: 0; min-height: 0; }
|
|
|
|
|
|
|
|
.config-workspace.has-structure { grid-template-columns: minmax(190px, 260px) minmax(0, 1fr); align-items: start; }
|
|
|
|
|
|
|
|
.config-workspace.has-structure > .config-restore-panel { grid-column: 1; border-right: 1px solid var(--ui-border); border-bottom: 0; }
|
|
|
|
|
|
|
|
.config-workspace.has-structure > .config-canvas { grid-column: 2; }
|
|
|
|
|
|
|
|
.config-restore-panel { display: grid; gap: 8px; min-width: 0; max-height: calc(100vh - 128px); overflow: auto; padding: 8px 10px; border-bottom: 1px solid var(--ui-border); background: color-mix(in srgb, var(--ui-input) 45%, transparent); }
|
|
|
|
.config-restore-title { color: var(--ui-muted); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
|
|
|
.config-restore-title { color: var(--ui-muted); font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
|
|
|
.config-structure-create { display: flex; align-items: end; flex-wrap: wrap; gap: 6px; }
|
|
|
|
.config-structure-create { display: flex; align-items: end; flex-wrap: wrap; gap: 6px; }
|
|
|
|
.config-structure-create label { display: grid; gap: 3px; min-width: 150px; color: var(--ui-muted); font-size: 10px; }
|
|
|
|
.config-structure-create label { display: grid; gap: 3px; min-width: 150px; color: var(--ui-muted); font-size: 10px; }
|
|
|
|
@ -1291,12 +1295,17 @@ HTML = """<!doctype html>
|
|
|
|
.config-structure-create input, .config-structure-create select { height: 27px; min-width: 0; padding: 0 7px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-text); font: inherit; }
|
|
|
|
.config-structure-create input, .config-structure-create select { height: 27px; min-width: 0; padding: 0 7px; border: 1px solid var(--ui-border); border-radius: 7px; background: var(--ui-input); color: var(--ui-text); font: inherit; }
|
|
|
|
.config-structure-create input:focus, .config-structure-create select:focus { outline: 0; border-color: var(--ui-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 10%, transparent); }
|
|
|
|
.config-structure-create input:focus, .config-structure-create select:focus { outline: 0; border-color: var(--ui-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ui-accent) 10%, transparent); }
|
|
|
|
.config-structure-hint { color: var(--ui-muted); font-size: 10px; }
|
|
|
|
.config-structure-hint { color: var(--ui-muted); font-size: 10px; }
|
|
|
|
.config-restore-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 5px; }
|
|
|
|
.config-restore-list { display: grid; grid-template-columns: 1fr; gap: 5px; }
|
|
|
|
.config-restore-item { display: flex; align-items: center; gap: 6px; min-width: 0; padding: 4px 5px 4px 8px; border: 1px solid var(--ui-border); border-radius: 7px; color: var(--ui-text); font-size: 11px; }
|
|
|
|
.config-restore-item { display: flex; align-items: center; gap: 6px; min-width: 0; padding: 4px 5px 4px 8px; border: 1px solid var(--ui-border); border-radius: 7px; color: var(--ui-text); font-size: 11px; }
|
|
|
|
.config-restore-item.is-hidden { opacity: .72; }
|
|
|
|
.config-restore-item.is-hidden { opacity: .72; }
|
|
|
|
.config-restore-item .config-item-title { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
.config-restore-item .config-item-title { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
.config-restore-item .config-item-state { color: var(--ui-muted); font-size: 9px; text-transform: uppercase; }
|
|
|
|
.config-restore-item .config-item-state { color: var(--ui-muted); font-size: 9px; text-transform: uppercase; }
|
|
|
|
.config-item-actions { display: inline-flex; gap: 3px; }
|
|
|
|
.config-item-actions { display: inline-flex; gap: 3px; }
|
|
|
|
|
|
|
|
.config-restore-item { cursor: pointer; }
|
|
|
|
|
|
|
|
.config-restore-item:hover, .config-restore-item.is-selected { border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent) 7%, var(--ui-panel)); }
|
|
|
|
|
|
|
|
.config-deleted-toggle { width: 100%; }
|
|
|
|
|
|
|
|
.config-deleted-list { display: grid; gap: 5px; padding-top: 4px; border-top: 1px solid var(--ui-border); }
|
|
|
|
|
|
|
|
.config-deleted-list[hidden] { display: none !important; }
|
|
|
|
.config-restore-panel[hidden], .config-trash[hidden] { display: none !important; }
|
|
|
|
.config-restore-panel[hidden], .config-trash[hidden] { display: none !important; }
|
|
|
|
.config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; }
|
|
|
|
.config-canvas { position: relative; display: block; min-height: 760px; padding: 8px 6px 18px; overflow: auto; overscroll-behavior: contain; }
|
|
|
|
.config-canvas > .config-block { position: absolute; box-sizing: border-box; container: config-block / inline-size; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; }
|
|
|
|
.config-canvas > .config-block { position: absolute; box-sizing: border-box; container: config-block / inline-size; margin: 0; min-width: 240px; min-height: 110px; border: 1px solid var(--ui-border) !important; border-radius: 10px; background: var(--ui-panel) !important; box-shadow: 0 6px 18px rgba(0, 0, 0, .1); overflow: auto; transition: border-color .2s ease, box-shadow .2s ease, opacity .2s ease; }
|
|
|
|
@ -1334,6 +1343,9 @@ HTML = """<!doctype html>
|
|
|
|
@media (max-width: 760px) {
|
|
|
|
@media (max-width: 760px) {
|
|
|
|
.config-toolbar { align-items: stretch; }
|
|
|
|
.config-toolbar { align-items: stretch; }
|
|
|
|
.config-toolbar-title, .config-layout-state { flex-basis: 100%; }
|
|
|
|
.config-toolbar-title, .config-layout-state { flex-basis: 100%; }
|
|
|
|
|
|
|
|
.config-workspace.has-structure { grid-template-columns: 1fr; }
|
|
|
|
|
|
|
|
.config-workspace.has-structure > .config-restore-panel, .config-workspace.has-structure > .config-canvas { grid-column: 1; }
|
|
|
|
|
|
|
|
.config-workspace.has-structure > .config-restore-panel { border-right: 0; border-bottom: 1px solid var(--ui-border); max-height: 42vh; }
|
|
|
|
.config-canvas { min-height: 900px; padding: 8px 4px 24px; }
|
|
|
|
.config-canvas { min-height: 900px; padding: 8px 4px 24px; }
|
|
|
|
.config-canvas > .config-block { min-width: 220px; max-width: calc(100% - 8px); }
|
|
|
|
.config-canvas > .config-block { min-width: 220px; max-width: calc(100% - 8px); }
|
|
|
|
.config-structure-create { align-items: stretch; }
|
|
|
|
.config-structure-create { align-items: stretch; }
|
|
|
|
@ -1436,6 +1448,7 @@ HTML = """<!doctype html>
|
|
|
|
<button id="configRestoreToggle" type="button" aria-expanded="false">структура</button>
|
|
|
|
<button id="configRestoreToggle" type="button" aria-expanded="false">структура</button>
|
|
|
|
<span id="configLayoutState" class="config-layout-state">перетаскивайте блоки за ⠿</span>
|
|
|
|
<span id="configLayoutState" class="config-layout-state">перетаскивайте блоки за ⠿</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="configWorkspace" class="config-workspace">
|
|
|
|
<div id="configRestorePanel" class="config-restore-panel" hidden>
|
|
|
|
<div id="configRestorePanel" class="config-restore-panel" hidden>
|
|
|
|
<span class="config-restore-title">Структура canvas</span>
|
|
|
|
<span class="config-restore-title">Структура canvas</span>
|
|
|
|
<div class="config-structure-create">
|
|
|
|
<div class="config-structure-create">
|
|
|
|
@ -1452,8 +1465,10 @@ HTML = """<!doctype html>
|
|
|
|
</label>
|
|
|
|
</label>
|
|
|
|
<button id="configCreateBlock" type="button">создать блок</button>
|
|
|
|
<button id="configCreateBlock" type="button">создать блок</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<span class="config-structure-hint">Перетаскивайте заголовок. Граница меняет размер. Клик поднимает блок. Перетащите в корзину для удаления.</span>
|
|
|
|
<span class="config-structure-hint">Клик по строке фокусирует блок. Перетаскивайте на canvas, меняйте размер границей, удаляйте через корзину.</span>
|
|
|
|
<div id="configRestoreList" class="config-restore-list"></div>
|
|
|
|
<div id="configRestoreList" class="config-restore-list"></div>
|
|
|
|
|
|
|
|
<button id="configDeletedToggle" class="config-deleted-toggle" type="button" hidden>корзина</button>
|
|
|
|
|
|
|
|
<div id="configDeletedList" class="config-deleted-list" hidden></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<form id="controlForm" class="control-form config-canvas">
|
|
|
|
<form id="controlForm" class="control-form config-canvas">
|
|
|
|
<details class="settings-group source-group" data-section="source" open>
|
|
|
|
<details class="settings-group source-group" data-section="source" open>
|
|
|
|
@ -1646,6 +1661,7 @@ HTML = """<!doctype html>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</details>
|
|
|
|
</details>
|
|
|
|
</form>
|
|
|
|
</form>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<div id="configTrash" class="config-trash" role="button" tabindex="0" aria-label="Перетащите блок сюда для удаления" hidden>
|
|
|
|
<div id="configTrash" class="config-trash" role="button" tabindex="0" aria-label="Перетащите блок сюда для удаления" hidden>
|
|
|
|
<span class="config-trash-icon" aria-hidden="true">⌫</span>
|
|
|
|
<span class="config-trash-icon" aria-hidden="true">⌫</span>
|
|
|
|
<span>отпустите здесь, чтобы удалить блок</span>
|
|
|
|
<span>отпустите здесь, чтобы удалить блок</span>
|
|
|
|
@ -1894,6 +1910,7 @@ HTML = """<!doctype html>
|
|
|
|
let configBlocks = [];
|
|
|
|
let configBlocks = [];
|
|
|
|
let configLayout = {};
|
|
|
|
let configLayout = {};
|
|
|
|
let restorePanelOpen = false;
|
|
|
|
let restorePanelOpen = false;
|
|
|
|
|
|
|
|
let deletedPanelOpen = false;
|
|
|
|
let configZIndex = 1;
|
|
|
|
let configZIndex = 1;
|
|
|
|
function readConfigLayout() {
|
|
|
|
function readConfigLayout() {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
@ -2019,52 +2036,89 @@ HTML = """<!doctype html>
|
|
|
|
applyStoredConfigLayout();
|
|
|
|
applyStoredConfigLayout();
|
|
|
|
renderConfigRestorePanel();
|
|
|
|
renderConfigRestorePanel();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function renderConfigRestorePanel() {
|
|
|
|
function focusConfigBlock(block, item) {
|
|
|
|
const panel = $('configRestorePanel');
|
|
|
|
if (!block || configBlockState(block)) return;
|
|
|
|
const list = $('configRestoreList');
|
|
|
|
configBlocks.forEach(other => other.classList.remove('is-selected'));
|
|
|
|
const toggle = $('configRestoreToggle');
|
|
|
|
document.querySelectorAll('.config-restore-item.is-selected').forEach(node => node.classList.remove('is-selected'));
|
|
|
|
if (!panel || !list || !toggle) return;
|
|
|
|
block.classList.add('is-selected');
|
|
|
|
const hidden = configBlocks.filter(block => configBlockState(block));
|
|
|
|
item?.classList.add('is-selected');
|
|
|
|
list.innerHTML = '';
|
|
|
|
bringConfigBlockFront(block);
|
|
|
|
configBlocks.forEach(block => {
|
|
|
|
block.scrollIntoView?.({behavior: 'smooth', block: 'nearest', inline: 'nearest'});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderConfigItem(list, block, deleted = false) {
|
|
|
|
const state = configBlockState(block);
|
|
|
|
const state = configBlockState(block);
|
|
|
|
const item = document.createElement('div');
|
|
|
|
const item = document.createElement('div');
|
|
|
|
item.className = `config-restore-item${state ? ' is-hidden' : ''}`;
|
|
|
|
item.className = `config-restore-item${state ? ' is-hidden' : ''}`;
|
|
|
|
|
|
|
|
item.title = 'Нажмите, чтобы показать блок на canvas';
|
|
|
|
const label = document.createElement('span');
|
|
|
|
const label = document.createElement('span');
|
|
|
|
label.className = 'config-item-title';
|
|
|
|
label.className = 'config-item-title';
|
|
|
|
label.textContent = configTitle(block);
|
|
|
|
label.textContent = configTitle(block);
|
|
|
|
const stateNode = document.createElement('span');
|
|
|
|
const stateNode = document.createElement('span');
|
|
|
|
stateNode.className = 'config-item-state';
|
|
|
|
stateNode.className = 'config-item-state';
|
|
|
|
stateNode.textContent = state === 'deleted' ? 'удалён' : state === 'hidden' ? 'скрыт' : block.dataset.custom === 'true' ? 'свой' : 'активен';
|
|
|
|
stateNode.textContent = deleted ? 'в корзине' : state === 'hidden' ? 'скрыт' : block.dataset.custom === 'true' ? 'свой' : 'активен';
|
|
|
|
const actions = document.createElement('span');
|
|
|
|
const actions = document.createElement('span');
|
|
|
|
actions.className = 'config-item-actions';
|
|
|
|
actions.className = 'config-item-actions';
|
|
|
|
const visibility = document.createElement('button');
|
|
|
|
const visibility = document.createElement('button');
|
|
|
|
visibility.type = 'button';
|
|
|
|
visibility.type = 'button';
|
|
|
|
visibility.textContent = state ? 'вернуть' : 'скрыть';
|
|
|
|
visibility.textContent = deleted || state ? 'вернуть' : 'скрыть';
|
|
|
|
visibility.setAttribute('aria-label', `${visibility.textContent} блок ${configTitle(block)}`);
|
|
|
|
visibility.setAttribute('aria-label', `${visibility.textContent} блок ${configTitle(block)}`);
|
|
|
|
visibility.addEventListener('click', () => setConfigBlockState(block, state ? '' : 'hidden'));
|
|
|
|
visibility.addEventListener('click', event => {
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
setConfigBlockState(block, deleted || state ? '' : 'hidden');
|
|
|
|
|
|
|
|
});
|
|
|
|
const duplicate = document.createElement('button');
|
|
|
|
const duplicate = document.createElement('button');
|
|
|
|
duplicate.type = 'button';
|
|
|
|
duplicate.type = 'button';
|
|
|
|
duplicate.textContent = 'дубль';
|
|
|
|
duplicate.textContent = 'дубль';
|
|
|
|
duplicate.setAttribute('aria-label', `создать копию блока ${configTitle(block)}`);
|
|
|
|
duplicate.setAttribute('aria-label', `создать копию блока ${configTitle(block)}`);
|
|
|
|
duplicate.addEventListener('click', () => duplicateConfigBlock(block));
|
|
|
|
duplicate.addEventListener('click', event => {
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
duplicateConfigBlock(block);
|
|
|
|
|
|
|
|
});
|
|
|
|
actions.append(visibility, duplicate);
|
|
|
|
actions.append(visibility, duplicate);
|
|
|
|
if (block.dataset.custom === 'true' && !state) {
|
|
|
|
if (block.dataset.custom === 'true' && !state && !deleted) {
|
|
|
|
const remove = document.createElement('button');
|
|
|
|
const remove = document.createElement('button');
|
|
|
|
remove.type = 'button';
|
|
|
|
remove.type = 'button';
|
|
|
|
remove.textContent = 'удалить';
|
|
|
|
remove.textContent = 'удалить';
|
|
|
|
remove.addEventListener('click', () => setConfigBlockState(block, 'deleted'));
|
|
|
|
remove.addEventListener('click', event => {
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
setConfigBlockState(block, 'deleted');
|
|
|
|
|
|
|
|
});
|
|
|
|
actions.appendChild(remove);
|
|
|
|
actions.appendChild(remove);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
item.append(label, stateNode, actions);
|
|
|
|
item.append(label, stateNode, actions);
|
|
|
|
|
|
|
|
item.addEventListener('click', () => focusConfigBlock(block, item));
|
|
|
|
list.appendChild(item);
|
|
|
|
list.appendChild(item);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderConfigRestorePanel() {
|
|
|
|
|
|
|
|
const panel = $('configRestorePanel');
|
|
|
|
|
|
|
|
const list = $('configRestoreList');
|
|
|
|
|
|
|
|
const toggle = $('configRestoreToggle');
|
|
|
|
|
|
|
|
if (!panel || !list || !toggle) return;
|
|
|
|
|
|
|
|
const deleted = configBlocks.filter(block => configBlockState(block) === 'deleted');
|
|
|
|
|
|
|
|
const hidden = configBlocks.filter(block => configBlockState(block) === 'hidden');
|
|
|
|
|
|
|
|
const workspace = $('configWorkspace');
|
|
|
|
|
|
|
|
const deletedToggle = $('configDeletedToggle');
|
|
|
|
|
|
|
|
const deletedList = $('configDeletedList');
|
|
|
|
|
|
|
|
list.innerHTML = '';
|
|
|
|
|
|
|
|
configBlocks.filter(block => configBlockState(block) !== 'deleted').forEach(block => renderConfigItem(list, block));
|
|
|
|
|
|
|
|
if (deletedList) {
|
|
|
|
|
|
|
|
deletedList.innerHTML = '';
|
|
|
|
|
|
|
|
deleted.forEach(block => renderConfigItem(deletedList, block, true));
|
|
|
|
|
|
|
|
deletedList.hidden = !deletedPanelOpen || !deleted.length;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deletedToggle) {
|
|
|
|
|
|
|
|
deletedToggle.hidden = !deleted.length;
|
|
|
|
|
|
|
|
deletedToggle.textContent = deleted.length ? `корзина · ${deleted.length}` : 'корзина';
|
|
|
|
|
|
|
|
deletedToggle.setAttribute('aria-expanded', String(deletedPanelOpen && !!deleted.length));
|
|
|
|
|
|
|
|
}
|
|
|
|
toggle.disabled = false;
|
|
|
|
toggle.disabled = false;
|
|
|
|
toggle.textContent = hidden.length ? `структура · ${hidden.length} скрыто` : 'структура';
|
|
|
|
toggle.textContent = hidden.length ? `структура · ${hidden.length} скрыто` : 'структура';
|
|
|
|
toggle.setAttribute('aria-expanded', String(restorePanelOpen));
|
|
|
|
toggle.setAttribute('aria-expanded', String(restorePanelOpen));
|
|
|
|
panel.hidden = !restorePanelOpen;
|
|
|
|
panel.hidden = !restorePanelOpen;
|
|
|
|
const visible = configBlocks.length - hidden.length;
|
|
|
|
workspace?.classList.toggle('has-structure', restorePanelOpen);
|
|
|
|
setText('configLayoutState', `${visible} блоков · ${hidden.length} скрыто`);
|
|
|
|
const visible = configBlocks.length - hidden.length - deleted.length;
|
|
|
|
|
|
|
|
setText('configLayoutState', `${visible} блоков · ${hidden.length} скрыто · ${deleted.length} в корзине`);
|
|
|
|
|
|
|
|
if (configCanvas) applyStoredConfigLayout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function nextCustomConfigKey() {
|
|
|
|
function nextCustomConfigKey() {
|
|
|
|
let index = 1;
|
|
|
|
let index = 1;
|
|
|
|
@ -2326,6 +2380,10 @@ HTML = """<!doctype html>
|
|
|
|
restorePanelOpen = !restorePanelOpen;
|
|
|
|
restorePanelOpen = !restorePanelOpen;
|
|
|
|
renderConfigRestorePanel();
|
|
|
|
renderConfigRestorePanel();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
$('configDeletedToggle')?.addEventListener('click', () => {
|
|
|
|
|
|
|
|
deletedPanelOpen = !deletedPanelOpen;
|
|
|
|
|
|
|
|
renderConfigRestorePanel();
|
|
|
|
|
|
|
|
});
|
|
|
|
$('configCreateBlock')?.addEventListener('click', () => {
|
|
|
|
$('configCreateBlock')?.addEventListener('click', () => {
|
|
|
|
const templateKey = $('configTemplateSelect')?.value || 'source';
|
|
|
|
const templateKey = $('configTemplateSelect')?.value || 'source';
|
|
|
|
const title = String($('configBlockTitle')?.value || '').trim().slice(0, 48) || 'Новый блок';
|
|
|
|
const title = String($('configBlockTitle')?.value || '').trim().slice(0, 48) || 'Новый блок';
|
|
|
|
|