diff --git a/admin/admin.css b/admin/admin.css index 7991d90..de808ed 100644 --- a/admin/admin.css +++ b/admin/admin.css @@ -1017,6 +1017,60 @@ body[data-file-picker-active="true"] .editor { font-weight: 850; } +.link-mode-field { + gap: 0.48rem; +} + +.link-mode-grid { + display: grid; + gap: 0.48rem; +} + +.link-mode-row { + display: grid; + grid-template-columns: auto minmax(4.5rem, 0.22fr) minmax(0, 1fr); + min-width: 0; + align-items: center; + gap: 0.55rem; + border-radius: var(--launcher-radius-control); + background: var(--field); + padding: 0.42rem 0.6rem; + color: var(--text-secondary); +} + +.link-mode-row input[type="checkbox"] { + width: 1.18rem; + height: 1.18rem; + margin: 0; + appearance: none; + border: 0.18rem solid rgba(255, 255, 255, 0.84); + border-radius: 50%; + background: transparent; +} + +.link-mode-row input[type="checkbox"]:checked { + box-shadow: inset 0 0 0 0.22rem #050506; + background: rgba(255, 255, 255, 0.94); +} + +.link-mode-row span { + overflow: hidden; + color: var(--text-muted); + font-size: 0.68rem; + font-weight: 850; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; +} + +.link-mode-row input[type="text"] { + min-width: 0; + min-height: 2rem; + border-radius: 0.78rem; + background: rgba(255, 255, 255, 0.04); + padding: 0 0.72rem; +} + .boolean-field input, .group-head-toggle input, .collection-visibility-toggle input { diff --git a/admin/admin.js b/admin/admin.js index 5af419f..4464e1f 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -10,6 +10,7 @@ const state = { mediaUploadActive: false, filePickerResetTimer: null, deleteConfirmResolve: null, + collectionOpenState: new Map(), }; const SECTIONS_REGION_ID = "sections"; @@ -62,26 +63,36 @@ const STATIC_FALLBACK = { logoAlt: "главная", brandLabel: "NODE.DC", links: [ - { label: "О продукте", href: "index.html#features", target: "" }, - { label: "Лист ожидания", href: "index.html#hero", target: "" }, - { label: "Документация", href: "https://docs.ssscript.app/", target: "_blank" }, - { label: "Тарифы", href: "index.html#pricing-wrap", target: "" }, + { enabled: true, label: "О продукте", href: "", target: "index.html#features", linkMode: "target", linkTarget: "" }, + { enabled: true, label: "Лист ожидания", href: "", target: "index.html#hero", linkMode: "target", linkTarget: "" }, + { + enabled: true, + label: "Документация", + href: "https://docs.ssscript.app/", + target: "", + linkMode: "href", + linkTarget: "_blank", + }, + { enabled: true, label: "Тарифы", href: "", target: "index.html#pricing-wrap", linkMode: "target", linkTarget: "" }, ], }, notifications: [ { + enabled: true, eyebrowHtml: 'ДАЛЕЕ — ИЮЛЬ 2026', eyebrowClass: "red-txt gray", title: "Открытая бета", body: "Открываем доступ всем. Будем собирать и отлаживать вместе.", }, { + enabled: true, eyebrowHtml: 'ДАЛЕЕ — ИЮЛЬ 2026', eyebrowClass: "red-txt gray", title: "Альфа для Founder-доступа", body: "На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа.", }, { + enabled: true, eyebrowHtml: "СЕЙЧАС", eyebrowClass: "red-txt", title: "Инвайты в закрытую бету уже отправлены.", @@ -135,11 +146,10 @@ const STATIC_FALLBACK = { "content.navigation.links", "Кнопки шапки", "Шапка: ссылки", - { label: "Новая ссылка", href: "index.html#", target: "" }, + { enabled: true, label: "Новая ссылка", href: "", target: "index.html#", linkMode: "target", linkTarget: "" }, [ field("label", "Текст кнопки", "text", "text", "Шапка: ссылки"), - field("href", "Адрес", "url", "attr", "Шапка: ссылки"), - field("target", "Target", "text", "attr", "Шапка: ссылки"), + field("linkMode", "Источник ссылки", "link-mode", "attr", "Шапка: ссылки"), ], ), collection( @@ -147,6 +157,7 @@ const STATIC_FALLBACK = { "Уведомления", "Уведомления", { + enabled: true, eyebrowHtml: "СЕЙЧАС", eyebrowClass: "red-txt gray", title: "Новое уведомление", @@ -235,6 +246,8 @@ const GROUP_COPY = { const MEDIA_ACCEPT = "image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv,.glb,.gltf,.svg,.avif,.webp,.woff,.woff2"; const mediaFilePicker = createMediaFilePicker(); +const collectionItemRuntimeKeys = new WeakMap(); +let collectionItemRuntimeKeyCounter = 0; function field(path, label, kind = "text", renderAs = kind === "html" ? "html" : "text", group = null, options = {}) { return { path, label, kind, renderAs, group: group || inferFieldGroup(path), ...options }; @@ -332,6 +345,48 @@ function setByPath(source, path, nextValue) { target[lastKey] = nextValue; } +const LINK_WINDOW_TARGETS = new Set(["_blank", "_self", "_parent", "_top"]); + +function isWindowTarget(value) { + return LINK_WINDOW_TARGETS.has(String(value || "").trim()); +} + +function isExternalHref(value) { + return /^(https?:)?\/\//i.test(String(value || "")) || /^(mailto:|tel:)/i.test(String(value || "")); +} + +function normalizeChoiceLink(item, fallback = {}) { + if (!item || typeof item !== "object") return; + + let href = item.href ?? fallback.href ?? ""; + let target = item.target ?? fallback.target ?? ""; + let linkTarget = item.linkTarget ?? fallback.linkTarget ?? ""; + + if (isWindowTarget(target)) { + linkTarget ||= target; + target = ""; + } + + const inferredMode = isExternalHref(href) ? "href" : "target"; + const linkMode = item.linkMode === "href" || item.linkMode === "target" ? item.linkMode : inferredMode; + + if (linkMode === "target" && !target && href && !isExternalHref(href)) { + target = href; + href = ""; + } + + item.enabled = item.enabled !== false; + item.href = href; + item.target = target; + item.linkMode = linkMode; + item.linkTarget = linkTarget; +} + +function normalizeChoiceLinks(items, fallbacks = []) { + if (!Array.isArray(items)) return; + items.forEach((item, index) => normalizeChoiceLink(item, fallbacks[index] || fallbacks[0] || {})); +} + function inferFieldGroup(path) { if (path.startsWith("content.navigation.")) return "Шапка"; if (path.startsWith("content.notifications.")) return "Уведомления"; @@ -433,8 +488,10 @@ function ensureStaticElements() { state.page.staticElements.content.navigation ||= clone(STATIC_FALLBACK.content.navigation); state.page.staticElements.content.navigation.logoSrc ||= STATIC_FALLBACK.content.navigation.logoSrc; state.page.staticElements.content.navigation.links ||= clone(STATIC_FALLBACK.content.navigation.links); + normalizeChoiceLinks(state.page.staticElements.content.navigation.links, STATIC_FALLBACK.content.navigation.links); state.page.staticElements.content.notifications ||= clone(STATIC_FALLBACK.content.notifications); state.page.staticElements.content.notifications.forEach((notification, index) => { + notification.enabled = notification.enabled !== false; notification.eyebrowClass ||= STATIC_FALLBACK.content.notifications[index]?.eyebrowClass || "red-txt gray"; }); state.page.staticElements.content.assets ||= clone(STATIC_FALLBACK.content.assets); @@ -1467,6 +1524,42 @@ function prepareCollectionItemForInsert(items, item) { return nextItem; } +function collectionInstanceKey(block, editableField) { + return `${block?.id || "static-elements"}::${editableField.path}`; +} + +function collectionItemKey(item, index) { + if (item && typeof item === "object") { + if (item.id) return `id:${item.id}`; + + let runtimeKey = collectionItemRuntimeKeys.get(item); + if (!runtimeKey) { + collectionItemRuntimeKeyCounter += 1; + runtimeKey = `runtime:${collectionItemRuntimeKeyCounter}`; + collectionItemRuntimeKeys.set(item, runtimeKey); + } + + return runtimeKey; + } + + return `value:${index}:${String(item)}`; +} + +function collectionOpenKeys(block, editableField, items) { + const key = collectionInstanceKey(block, editableField); + let openKeys = state.collectionOpenState.get(key); + + if (!openKeys) { + openKeys = new Set(); + if (items.length > 0 && items.length <= 8) { + openKeys.add(collectionItemKey(items[0], 0)); + } + state.collectionOpenState.set(key, openKeys); + } + + return openKeys; +} + function fieldInputValue(input) { if (input.type === "checkbox") return input.checked; @@ -1618,6 +1711,91 @@ function renderNumberField({ block, editableField, grid }) { grid.append(label); } +function siblingFieldPath(path, siblingKey) { + const keys = path.split("."); + keys[keys.length - 1] = siblingKey; + return keys.join("."); +} + +function renderLinkModeField({ block, field, grid }) { + const container = document.createElement("div"); + const labelRow = document.createElement("div"); + const labelText = document.createElement("span"); + const kind = document.createElement("span"); + const hiddenMode = document.createElement("input"); + const rows = document.createElement("div"); + const path = document.createElement("span"); + const hrefPath = siblingFieldPath(field.path, field.hrefPath || "href"); + const targetPath = siblingFieldPath(field.path, field.targetPath || "target"); + const currentMode = getByPath(block, field.path) === "href" ? "href" : "target"; + + function createRow({ mode, title, inputPath, placeholder }) { + const row = document.createElement("label"); + const check = document.createElement("input"); + const caption = document.createElement("span"); + const input = document.createElement("input"); + + row.className = "link-mode-row"; + check.type = "checkbox"; + check.checked = currentMode === mode; + check.setAttribute("aria-label", `${title}: использовать`); + caption.textContent = title; + input.dataset.path = inputPath; + input.type = "text"; + input.autocomplete = "off"; + input.placeholder = placeholder; + input.value = getByPath(block, inputPath) ?? ""; + + check.addEventListener("change", () => { + if (!check.checked) { + check.checked = true; + return; + } + + const active = activeBlock(); + if (!active) return; + hiddenMode.value = mode; + setByPath(active, field.path, mode); + rows.querySelectorAll('input[type="checkbox"]').forEach((candidate) => { + candidate.checked = candidate === check; + }); + el.json.value = JSON.stringify(active, null, 2); + markDirty(mode === "href" ? "Активен внешний адрес." : "Активен Target."); + }); + + input.addEventListener("input", () => { + const active = activeBlock(); + if (!active) return; + syncFieldValue(active, inputPath, input); + }); + + row.append(check, caption, input); + return row; + } + + container.className = "field-control wide link-mode-field"; + labelRow.className = "field-label-row"; + labelText.className = "field-label-text"; + labelText.textContent = field.label || "Источник ссылки"; + kind.className = "field-kind"; + kind.textContent = "link"; + labelRow.append(labelText, kind); + + hiddenMode.type = "hidden"; + hiddenMode.dataset.path = field.path; + hiddenMode.value = currentMode; + rows.className = "link-mode-grid"; + rows.append( + createRow({ mode: "href", title: "Адрес", inputPath: hrefPath, placeholder: "https://..." }), + createRow({ mode: "target", title: "Target", inputPath: targetPath, placeholder: "index.html#section" }), + ); + + path.className = "field-path"; + path.textContent = `${field.path} · ${hrefPath} · ${targetPath}`; + container.append(labelRow, hiddenMode, rows, path); + grid.append(container); +} + function renderHeaderBooleanToggle({ block, editableField, head }) { const label = document.createElement("label"); const input = document.createElement("input"); @@ -1640,6 +1818,11 @@ function renderHeaderBooleanToggle({ block, editableField, head }) { } function renderCollectionScalarField({ block, field, grid }) { + if (field.kind === "link-mode") { + renderLinkModeField({ block, field, grid }); + return; + } + if (field.kind === "boolean") { renderBooleanField({ block, editableField: field, grid }); return; @@ -1752,6 +1935,7 @@ function startCollectionPointerDrag(event, card, items, index, rerender) { function renderCollectionField({ block, editableField, grid }) { const items = collectionItems(block, editableField); + const openKeys = collectionOpenKeys(block, editableField, items); const container = document.createElement("section"); const head = document.createElement("div"); const title = document.createElement("div"); @@ -1774,7 +1958,9 @@ function renderCollectionField({ block, editableField, grid }) { addButton.setAttribute("aria-label", addButton.title); addButton.textContent = "+"; addButton.addEventListener("click", () => { - items.push(prepareCollectionItemForInsert(items, collectionItemTemplate(editableField))); + const nextItem = prepareCollectionItemForInsert(items, collectionItemTemplate(editableField)); + items.push(nextItem); + openKeys.add(collectionItemKey(nextItem, items.length - 1)); rerender(`Добавлен элемент в «${editableField.label || editableField.path}».`); }); head.append(title, addButton); @@ -1782,6 +1968,7 @@ function renderCollectionField({ block, editableField, grid }) { list.className = "collection-list"; items.forEach((item, index) => { + const itemKey = collectionItemKey(item, index); const details = document.createElement("details"); const summary = document.createElement("summary"); const summaryTitle = document.createElement("span"); @@ -1797,7 +1984,11 @@ function renderCollectionField({ block, editableField, grid }) { details.className = "collection-item-card"; details.classList.toggle("is-hidden", item?.enabled === false); - if (index === 0 && items.length <= 8) details.open = true; + details.open = openKeys.has(itemKey); + details.addEventListener("toggle", () => { + if (details.open) openKeys.add(itemKey); + else openKeys.delete(itemKey); + }); summary.className = "collection-summary"; summaryTitle.className = "collection-summary-title"; summaryTitle.textContent = collectionItemTitle(item, index); @@ -1851,12 +2042,15 @@ function renderCollectionField({ block, editableField, grid }) { rerender("Порядок элементов коллекции изменён локально."); }], ["⧉", "Дублировать", () => { - items.splice(index + 1, 0, prepareCollectionItemForInsert(items, item)); + const nextItem = prepareCollectionItemForInsert(items, item); + items.splice(index + 1, 0, nextItem); + if (details.open) openKeys.add(collectionItemKey(nextItem, index + 1)); rerender("Элемент коллекции продублирован локально."); }], ["⌫", "Удалить", async () => { const confirmed = await requestDeleteConfirmation(); if (!confirmed) return; + openKeys.delete(itemKey); items.splice(index, 1); rerender("Элемент коллекции удалён локально."); }], @@ -1947,6 +2141,11 @@ function renderContentFields(block) { continue; } + if (editableField.kind === "link-mode") { + renderLinkModeField({ block, field: editableField, grid }); + continue; + } + if (editableField.kind === "boolean") { renderBooleanField({ block, editableField, grid }); continue; diff --git a/content/block-templates/home.json b/content/block-templates/home.json index 5d12cd0..77d1040 100644 --- a/content/block-templates/home.json +++ b/content/block-templates/home.json @@ -970,6 +970,7 @@ "renderAs": "collection", "group": "FAQ", "itemTemplate": { + "enabled": true, "question": "Новый вопрос", "answerHtml": "
Новый ответ.
" }, @@ -2420,23 +2421,19 @@ "renderAs": "text" }, { - "path": "href", - "label": "Адрес", - "kind": "url", - "renderAs": "attr" - }, - { - "path": "target", - "label": "Target", - "kind": "text", + "path": "linkMode", + "label": "Источник ссылки", + "kind": "link-mode", "renderAs": "attr" } ], "itemTemplate": { + "enabled": true, "label": "Новая ссылка", - "href": "index.html#", - "target": "", - "enabled": true + "href": "", + "target": "index.html#", + "linkMode": "target", + "linkTarget": "" } }, { @@ -2460,23 +2457,19 @@ "renderAs": "text" }, { - "path": "href", - "label": "Адрес", - "kind": "url", - "renderAs": "attr" - }, - { - "path": "target", - "label": "Target", - "kind": "text", + "path": "linkMode", + "label": "Источник ссылки", + "kind": "link-mode", "renderAs": "attr" } ], "itemTemplate": { + "enabled": true, "label": "Новая ссылка", "href": "https://", - "target": "_blank", - "enabled": true + "target": "", + "linkMode": "href", + "linkTarget": "_blank" } }, { diff --git a/content/pages/home.json b/content/pages/home.json index 4b57122..0e77486 100644 --- a/content/pages/home.json +++ b/content/pages/home.json @@ -15,45 +15,67 @@ "links": [ { "label": "О продукте", - "href": "index.html#features", - "target": "" + "href": "", + "target": "index.html#features", + "enabled": true, + "linkMode": "target", + "linkTarget": "" }, { "label": "Запрос инвайта", - "href": "index.html#hero", - "target": "" + "href": "", + "target": "index.html#hero", + "enabled": true, + "linkMode": "target", + "linkTarget": "" }, { - "label": "Документация", - "href": "https://docs.ssscript.app/", - "target": "_blank" + "label": "Возможности", + "href": "https://nodedc.dctouch.ru/", + "target": "", + "enabled": true, + "linkMode": "href", + "linkTarget": "_blank" }, { "label": "Тарифы", - "href": "index.html#pricing-wrap", - "target": "" + "href": "", + "target": "index.html#pricing-wrap", + "enabled": false, + "linkMode": "target", + "linkTarget": "" } ], "logoSrc": "./assets/uploads/images/static-elements/dclogo_darck-mquq1l13-transparent.png" }, "notifications": [ { - "eyebrowHtml": "ДАЛЕЕ — ИЮЛЬ 2026", - "title": "Открытая бета", - "body": "Открываем доступ всем. Будем собирать и отлаживать вместе.", - "eyebrowClass": "red-txt gray" + "eyebrowHtml": "РАНЕЕ — МАЙ 2026", + "title": "Engineering Core выделен в отдельный модуль", + "body": "Единый контур инженерных моделей, версий, согласований и рабочих материалов.", + "eyebrowClass": "red-txt gray", + "enabled": true }, { - "eyebrowHtml": "ДАЛЕЕ — ИЮЛЬ 2026", - "title": "Альфа для Founder-доступа", - "body": "На второй фазе тестирования бета открыта для всех подписчиков Founder-тарифа.", - "eyebrowClass": "red-txt gray" + "eyebrowHtml": "РАНЕЕ — ИЮНЬ 2026", + "title": "Knowledge Graph добавлен в состав экосистемы", + "body": "Связи между задачами, агентами, кодовой базой, документами и инженерными сущностями собраны в общем графе.", + "eyebrowClass": "red-txt gray", + "enabled": true }, { "eyebrowHtml": "СЕЙЧАС", - "title": "Инвайты в закрытую бету уже отправлены.", - "body": "Сейчас тестируем базовую функциональность и ИИ-интеграцию. ", - "eyebrowClass": "red-txt" + "title": "Открыт alpha-доступ к Procurement Intelligence", + "body": "Агентный контур для закупок, сопоставления условий и контроля поставок.", + "eyebrowClass": "red-txt", + "enabled": true + }, + { + "eyebrowHtml": "ДАЛЕЕ — СЕНТЯБРЬ 2026", + "eyebrowClass": "red-txt", + "title": "Field Ops включён в план релиза", + "body": "Мобильный операционный модуль для выездных команд, инспекций, фотофиксации и полевых отчётов.", + "enabled": true } ], "dock": { @@ -124,9 +146,12 @@ "renderAs": "collection", "group": "Шапка: ссылки", "itemTemplate": { + "enabled": true, "label": "Новая ссылка", - "href": "index.html#", - "target": "" + "href": "", + "target": "index.html#", + "linkMode": "target", + "linkTarget": "" }, "itemFields": [ { @@ -137,16 +162,9 @@ "group": "Шапка: ссылки" }, { - "path": "href", - "label": "Адрес", - "kind": "url", - "renderAs": "attr", - "group": "Шапка: ссылки" - }, - { - "path": "target", - "label": "Target", - "kind": "text", + "path": "linkMode", + "label": "Источник ссылки", + "kind": "link-mode", "renderAs": "attr", "group": "Шапка: ссылки" } @@ -159,6 +177,7 @@ "renderAs": "collection", "group": "Уведомления", "itemTemplate": { + "enabled": true, "eyebrowHtml": "СЕЙЧАС", "eyebrowClass": "red-txt gray", "title": "Новое уведомление", @@ -1761,65 +1780,79 @@ "main": "и ответы" }, "introHtml": "Подробный обзор возможностей, логики работы и внутреннего устройства.", - "docsHref": "https://docs.ssscript.app/", - "docsLabel": "Полная документация", + "docsHref": "https://nodedc.dctouch.ru/", + "docsLabel": "Возможности ", "windowTitle": "FAQ", "faq": [ { "question": "Что входит в NODE.DC?", - "answerHtml": "NODE.DC включает Hub для входа и управления доступами, Engine для разработки workflow и прикладной логики, Ops для операционной работы, сквозного ассистента и подключаемые модули: 1C Assistant, Tender, Digital Twin, Model Viewer / Engineering Core и отраслевые приложения.
" + "answerHtml": "NODE.DC включает Hub для входа и управления доступами, Engine для разработки workflow и прикладной логики, Ops для операционной работы, сквозного ассистента и подключаемые модули: 1C Assistant, Tender, Digital Twin, Model Viewer / Engineering Core и отраслевые приложения.
", + "enabled": true }, { "question": "Зачем нужен Hub?", - "answerHtml": "Hub выполняет роль лаунчера и административной точки входа. В нем отображается каталог доступных приложений, рабочие пространства, роли, группы пользователей и права доступа к модулям платформы.
" + "answerHtml": "Hub выполняет роль лаунчера и административной точки входа. В нем отображается каталог доступных приложений, рабочие пространства, роли, группы пользователей и права доступа к модулям платформы.
", + "enabled": true }, { "question": "Как связаны Hub, Engine и Ops?", - "answerHtml": "Hub отвечает за вход, роли и доступ к приложениям. Engine используется для сборки workflow, прикладной логики, интеграций и интерфейсов. Ops принимает задачи, отчеты, статусы, результаты работы модулей и действия агентов в операционный контур.
" + "answerHtml": "Hub отвечает за вход, роли и доступ к приложениям. Engine используется для сборки workflow, прикладной логики, интеграций и интерфейсов. Ops принимает задачи, отчеты, статусы, результаты работы модулей и действия агентов в операционный контур.
", + "enabled": true }, { "question": "Что создается в Engine?", - "answerHtml": "В Engine создаются workflow, интеграционные сценарии, обработчики данных, прикладная логика, интерфейсные связки и сервисные контуры. Сборка может выполняться вручную в нодовом редакторе или через ассистента по текстовому заданию.
" + "answerHtml": "В Engine создаются workflow, интеграционные сценарии, обработчики данных, прикладная логика, интерфейсные связки и сервисные контуры. Сборка может выполняться вручную в нодовом редакторе или через ассистента по текстовому заданию.
", + "enabled": true }, { "question": "Что фиксируется в Ops?", - "answerHtml": "В Ops фиксируются задачи, статусы, карточки, отчеты, согласования, рабочие материалы, результаты агентов и данные из подключенных модулей. Слой предназначен для контроля процессов и операционной работы.
" + "answerHtml": "В Ops фиксируются задачи, статусы, карточки, отчеты, согласования, рабочие материалы, результаты агентов и данные из подключенных модулей. Слой предназначен для контроля процессов и операционной работы.
", + "enabled": true }, { "question": "Как работает сквозной ассистент?", - "answerHtml": "Ассистент работает как единый интерфейс к приложениям и данным платформы. В рамках прав доступа ему доступны задачи, процессы, кодовая база, проектные материалы, отчеты, инженерные модели и результаты работы агентов. Ассистент может отвечать на вопросы, декомпозировать задачи, готовить материалы и инициировать действия в связанных слоях.
" + "answerHtml": "Ассистент работает как единый интерфейс к приложениям и данным платформы. В рамках прав доступа ему доступны задачи, процессы, кодовая база, проектные материалы, отчеты, инженерные модели и результаты работы агентов. Ассистент может отвечать на вопросы, декомпозировать задачи, готовить материалы и инициировать действия в связанных слоях.
", + "enabled": true }, { "question": "Как работают прикладные модули?", - "answerHtml": "Прикладные модули подключаются к ролям, задачам, данным, рабочим пространствам и ассистенту. Модуль закрывает не отдельный экран, а рабочий контур: входные данные, обработку, действия, отчеты, согласования и передачу результата в Ops.
" + "answerHtml": "Прикладные модули подключаются к ролям, задачам, данным, рабочим пространствам и ассистенту. Модуль закрывает не отдельный экран, а рабочий контур: входные данные, обработку, действия, отчеты, согласования и передачу результата в Ops.
", + "enabled": true }, { "question": "Какие модули можно подключать?", - "answerHtml": "Базовый набор включает Hub, Engine и Ops. Дополнительно подключаются 1C Assistant, Tender, Digital Twin, Engineering Core, интеграционные сценарии, API-модули и специализированные приложения для подразделений.
" + "answerHtml": "Базовый набор включает Hub, Engine и Ops. Дополнительно подключаются 1C Assistant, Tender, Digital Twin, Engineering Core, интеграционные сценарии, API-модули и специализированные приложения для подразделений.
", + "enabled": true }, { "question": "Поддерживаются ли инженерные форматы?", - "answerHtml": "Для инженерных и производственных сценариев предусмотрена работа с цифровыми моделями и техническими материалами. Поддерживаются BIM, STEP, STL, 3DGS / Gaussian Splatting, LAS/LAZ-облака точек и другие форматы, связанные с задачами, версиями и проектным контекстом.
" + "answerHtml": "Для инженерных и производственных сценариев предусмотрена работа с цифровыми моделями и техническими материалами. Поддерживаются BIM, STEP, STL, 3DGS / Gaussian Splatting, LAS/LAZ-облака точек и другие форматы, связанные с задачами, версиями и проектным контекстом.
", + "enabled": true }, { "question": "Как настраиваются роли и права доступа?", - "answerHtml": "Права доступа задаются через Hub. Администратор может назначать роли, формировать группы, разделять контуры доступа по подразделениям и выдавать индивидуальные права на приложения, функции и рабочие области.
" + "answerHtml": "Права доступа задаются через Hub. Администратор может назначать роли, формировать группы, разделять контуры доступа по подразделениям и выдавать индивидуальные права на приложения, функции и рабочие области.
", + "enabled": true }, { "question": "Можно ли подключать корпоративные системы?", - "answerHtml": "NODE.DC рассчитан на подключение внешних сервисов, API, внутренних приложений, 1C, баз данных, файловых хранилищ и корпоративных систем. Интеграционные сценарии собираются в Engine и передают результаты в Ops, прикладные модули или пользовательские интерфейсы.
" + "answerHtml": "NODE.DC рассчитан на подключение внешних сервисов, API, внутренних приложений, 1C, баз данных, файловых хранилищ и корпоративных систем. Интеграционные сценарии собираются в Engine и передают результаты в Ops, прикладные модули или пользовательские интерфейсы.
", + "enabled": true }, { "question": "Где размещается NODE.DC?", - "answerHtml": "Доступны облачные, выделенные, гибридные и enterprise-варианты размещения. Итоговая конфигурация зависит от требований к безопасности, составу модулей, интеграциям и политике хранения данных.
" + "answerHtml": "Доступны облачные, выделенные, гибридные и enterprise-варианты размещения. Итоговая конфигурация зависит от требований к безопасности, составу модулей, интеграциям и политике хранения данных.
", + "enabled": true }, { "question": "Как запускается пилотный контур?", - "answerHtml": "Пилотный контур формируется по составу модулей, ролей, интеграций и рабочих сценариев. Обычно подключается Hub, настраиваются базовые роли, выбирается один или несколько процессов, после чего собираются workflow, рабочие представления и отчеты.
" + "answerHtml": "Пилотный контур формируется по составу модулей, ролей, интеграций и рабочих сценариев. Обычно подключается Hub, настраиваются базовые роли, выбирается один или несколько процессов, после чего собираются workflow, рабочие представления и отчеты.
", + "enabled": true }, { "question": "Чем NODE.DC отличается от набора отдельных приложений?", - "answerHtml": "NODE.DC связывает приложения в единую платформенную архитектуру: общий доступ, роли, сквозной ассистент, связанные задачи, данные, workflow и операционный слой. Это снижает разрыв между разработкой, управлением и исполнением процессов.
" + "answerHtml": "NODE.DC связывает приложения в единую платформенную архитектуру: общий доступ, роли, сквозной ассистент, связанные задачи, данные, workflow и операционный слой. Это снижает разрыв между разработкой, управлением и исполнением процессов.
", + "enabled": true } ], "sender": { @@ -1827,7 +1860,7 @@ "avatarAlt": "Аватар NODE.DC", "name": "NODE.DC", "replyLabel": "Ответить", - "sentLabel": "Отправлено с iPhone" + "sentLabel": "Отправлено с NODE.DC MANUAL CORE" }, "scroll": { "speed": 0.7 @@ -1936,6 +1969,7 @@ "renderAs": "collection", "group": "FAQ", "itemTemplate": { + "enabled": true, "question": "Новый вопрос", "answerHtml": "Новый ответ.
" }, @@ -2594,7 +2628,7 @@ "template": "screenshot-cards.html", "adminLabel": "Карточки скриншотов", "anchor": null, - "enabled": true, + "enabled": false, "content": { "galleryItems": [ { @@ -3155,7 +3189,7 @@ "template": "demo-video-folder.html", "adminLabel": "Иконка демо-видео в доке", "anchor": null, - "enabled": true, + "enabled": false, "content": { "target": "demo-video", "to": "features", @@ -3215,38 +3249,48 @@ "appIconAlt": "Иконка NODE.DC", "wordmarkSrc": "./assets/webflow/images/69ad96a28b06b7552a339137_logtype.svg?v=node-dc", "wordmarkAlt": "Логотип NODE.DC", - "copyrightHtml": "© 2026 — NODE.DC", + "copyrightHtml": "© 2026 — NODE.DC", "navTitle": "Навигация", "navLinks": [ { "label": "Документация", "href": "https://docs.ssscript.app/", - "target": "_blank", - "enabled": true + "target": "", + "enabled": false, + "linkMode": "href", + "linkTarget": "_blank" }, { "label": "Лист ожидания", - "href": "index.html#waitlist", - "target": "", - "enabled": true + "href": "", + "target": "index.html#waitlist", + "enabled": false, + "linkMode": "target", + "linkTarget": "" }, { "label": "Тарифы", - "href": "index.html#", - "target": "", - "enabled": true + "href": "", + "target": "index.html#", + "enabled": false, + "linkMode": "target", + "linkTarget": "" }, { "label": "Возможности", - "href": "index.html#features", - "target": "", - "enabled": true + "href": "https://nodedc.dctouch.ru/", + "target": "index.html#features", + "enabled": true, + "linkMode": "href", + "linkTarget": "" }, { "label": "Демо", - "href": "index.html#features", - "target": "", - "enabled": true + "href": "", + "target": "index.html#features", + "enabled": true, + "linkMode": "target", + "linkTarget": "" } ], "contactTitle": "Контакты", @@ -3254,30 +3298,38 @@ { "label": "DCTOUCH", "href": "https://www.dctouch.ru/", - "target": "_blank", - "enabled": true + "target": "", + "enabled": true, + "linkMode": "href", + "linkTarget": "_blank" }, { "label": "Telegram", "href": "https://t.me/dcconstructions", - "target": "_blank", - "enabled": true + "target": "", + "enabled": true, + "linkMode": "href", + "linkTarget": "_blank" }, { "label": "GitHub", "href": "https://www.federic.ooo/s/github", - "target": "_blank", - "enabled": false + "target": "", + "enabled": false, + "linkMode": "href", + "linkTarget": "_blank" }, { "label": "Портфолио", "href": "https://federic.ooo/", - "target": "_blank", - "enabled": false + "target": "", + "enabled": false, + "linkMode": "href", + "linkTarget": "_blank" } ], "policyHtml": "Политика конфиденциальности & Условия использования", - "policyEnabled": true + "policyEnabled": false } }, "editableFields": [ @@ -3337,23 +3389,19 @@ "renderAs": "text" }, { - "path": "href", - "label": "Адрес", - "kind": "url", - "renderAs": "attr" - }, - { - "path": "target", - "label": "Target", - "kind": "text", + "path": "linkMode", + "label": "Источник ссылки", + "kind": "link-mode", "renderAs": "attr" } ], "itemTemplate": { + "enabled": true, "label": "Новая ссылка", - "href": "index.html#", - "target": "", - "enabled": true + "href": "", + "target": "index.html#", + "linkMode": "target", + "linkTarget": "" } }, { @@ -3377,23 +3425,19 @@ "renderAs": "text" }, { - "path": "href", - "label": "Адрес", - "kind": "url", - "renderAs": "attr" - }, - { - "path": "target", - "label": "Target", - "kind": "text", + "path": "linkMode", + "label": "Источник ссылки", + "kind": "link-mode", "renderAs": "attr" } ], "itemTemplate": { + "enabled": true, "label": "Новая ссылка", "href": "https://", - "target": "_blank", - "enabled": true + "target": "", + "linkMode": "href", + "linkTarget": "_blank" } }, { diff --git a/templates/pages/home/blocks/faq.html b/templates/pages/home/blocks/faq.html index 9a8aa94..d5f8c35 100644 --- a/templates/pages/home/blocks/faq.html +++ b/templates/pages/home/blocks/faq.html @@ -1 +1 @@ -