Diil Docs
  1. Документація
  2. Віджет

Редагування на сайті: віджет і розмітка data-crm

Оновлено:

API доставляє контент на ваш сайт. А ця сторінка знімає редакторів з вашої шиї. Додайте один тег <script>, розсипте по розмітці кілька атрибутів data-crm-* — і люди, які відповідають за тексти, зможуть клацнути заголовок просто на живому сайті, виправити одруківку й натиснути «Зберегти». Без тікетів, без деплою і без «а можна поміняти одну кому на головній?» о шостій вечора в п’ятницю.

Налаштування — це чотири кроки, і думати доведеться лише на одному з них:

  1. Додайте скрипт віджета

    Один тег <script> на кожній сторінці.
  2. Дозвольте CRM вбудовувати ваш сайт

    Один заголовок відповіді, щоб CRM могла відкрити ваш сайт у режимі Live.
  3. Розмітьте редаговані елементи

    Підкажіть редактору атрибутами data-crm-*, який елемент показує який блок.
  4. Віддавайте свіжий контент у режимі Live

    Обходьте свій кеш, поки на сторінку дивиться редактор, — і зміни з’являтимуться миттєво.

Що отримують редактори

З крісла редактора редагування на сайті виглядає так:

  1. Він відкриває ваш сайт у режимі Live усередині CRM. Це ваш справжній сайт, а не макет.
  2. Кожен розмічений елемент при наведенні отримує рамку. Редактор клацає потрібний — заголовок, абзац, картинку.
  3. Змінює текст або завантажує нове зображення й натискає «Зберегти».
  4. Сторінка оновлюється з новим контентом. Готово. Редактор коду ніхто навіть не відкривав.

Якщо елемент розмічено маркером блока, якого в CRM ще немає, редактор може створити цей блок просто із сайту. Тож можна спершу викотити розмітку, а контент-команда заповнить її пізніше.

Як це працює під капотом

Ваша сторінка й далі виводить контент із Content API точнісінько як раніше. Атрибути data-crm-* нічого не рендерять — вони лише пов’язують DOM-елемент із блоком у CRM, як наліпка на шухляді.

  • Режим Live — це iframe. CRM завантажує ваш сайт у фреймі й додає до URL ?crm_live=1. Редактор вмикається лише в цій рамці: коли referrer веде на адресу CRM або в URL є ?crm_live, а referrer порожній чи з вашого ж сайту. Якщо ваш сайт вбудує у свій iframe хтось інший, редактор там не завантажиться.
  • Один скрипт підвантажує лише те, що потрібно. widget.js — єдиний тег, який ви додаєте. Сам редактор (widget.editor.js) завантажується лише тоді, коли сайт відкрито в режимі Live усередині CRM. Чат підтримки (widget.support.js) приїжджає, тільки якщо чат увімкнено в CRM. Вхід відвідувачів (widget.auth.js) — тільки якщо на сторінці є елементи data-crm-login чи data-crm-auth або атрибут data-crm-key.
  • Відвідувачі за це не платять. Поза CRM нічого редакторського не завантажується — ваші відвідувачі ніколи не качають редактор.
  • Збереження — це звичайна правка в CRM. CRM записує блок, версія контенту сайту зростає, і наступний запит до API повертає свіжі дані.
  • Випадково підключили тег двічі? Нічого страшного: другу копію буде проігноровано.

Крок 1. Додайте скрипт віджета

Поставте тег на кожну сторінку, просто перед </body>. Якщо на сайті є спільний layout, то саме там йому й місце.

<!doctype html>
<html lang="en">
  <head>…</head>
  <body>
    …ваша сторінка…

    <script src="https://widget.sitecog.com/widget.js" defer></script>
  </body>
</html>

Зі скриптом усе. Ні ключа, ні виклику init, ні об’єкта конфігурації — віджет сам розбереться, чи працює він усередині CRM.

Крок 2. Дозвольте CRM вбудовувати ваш сайт

Режим Live показує ваш сайт в iframe на https://sitecog.com. Браузери дозволяють це лише тоді, коли ваш сайт не проти. У ваших відповідях мають бути дві речі:

  • заголовок Content-Security-Policy з frame-ancestors 'self' https://sitecog.com;
  • жодного заголовка X-Frame-Options зі значенням DENY чи SAMEORIGIN — він переважує добрі наміри й блокує фрейм.

Оберіть свій сервер:

server {
    # …

    # Дозволяємо CRM Diil відкривати сайт у режимі Live
    add_header Content-Security-Policy "frame-ancestors 'self' https://sitecog.com" always;

    # Видаліть у цьому блоці server усі рядки "add_header X-Frame-Options …".
    # Якщо застосунок за proxy_pass сам ставить X-Frame-Options, приберіть його тут:
    proxy_hide_header X-Frame-Options;
}

Крок 3. Розмітьте редаговані елементи

Тепер підкажіть редактору, що де. Кожен атрибут каже: «цей елемент показує отой блок». Значення — це шлях, який починається з маркера блока, заданого в CRM.

Довідник атрибутів

АтрибутКуди ставитиЩо можуть редактори
data-crm-textНа будь-який елемент із текстом: h1, p, span, підпис кнопкиРедагувати текст текстового блока чи текстового поля
data-crm-imageНа <img>, що показує блок або поле із зображеннямЗавантажити або замінити картинку
data-crm-videoНа <video>, що показує блок або поле з відеоЗавантажити або замінити відео
data-crm-objectНа контейнер, що рендерить блок object (секцію, картку)Бачити групу полів як один блок
data-crm-arrayНа контейнер, що рендерить список — блок array або поле-масивБачити список як одне ціле

Простим блокам не потрібно нічого, крім маркера:

Блоки верхнього рівняtsx
<section>
  <h1 data-crm-text="hero_title">{hero.hero_title.content.en}</h1>
  <p data-crm-text="hero_subtitle">{hero.hero_subtitle.content.en}</p>
  <img data-crm-image="hero_image" src={hero.hero_image.content.file} alt="" />
  <video data-crm-video="hero_video" src={hero.hero_video.content.file} autoPlay muted loop />
</section>

Синтаксис шляхів: усередину об’єктів і масивів

У блоках object і array всередині є поля, тож шлях продовжується через крапку. Перший сегмент — завжди маркер блока. Далі йдуть маркери полів об’єкта й числові індекси масиву (від 0).

ШляхНа що вказує
hero_titleУвесь блок hero_title
faq_section.titleПоле title блока-об’єкта faq_section
faq_section.itemsПоле-масив items
faq_section.items.0.questionПоле question першого елемента
faq_section.items.2.answerПоле answer третього елемента

Правила вміщаються в чотири рядки:

  • сегменти розділяються крапками; кожен складається з латинських літер, цифр і підкреслень, від 1 до 40 символів;
  • перший сегмент, маркер блока, має щонайменше 2 символи (звичайні правила для маркерів);
  • крок усередину об’єкта — це маркер поля, крок усередину масиву — число;
  • маркери чутливі до регістру: Hero_title і hero_title — два різні блоки.

Повний приклад: секція FAQ

Ось дані: один блок object із заголовком і масивом питань.

faq_section з GET /v1/pages/home?lang=enjson
"faq_section": {
  "type": "object",
  "content": {
    "title": { "en": "FAQ" },
    "items": [
      {
        "question": { "en": "How long is delivery?" },
        "answer": { "en": "1–3 days." }
      },
      {
        "question": { "en": "Can I return the earbuds?" },
        "answer": { "en": "Yes, within 14 days." }
      }
    ]
  }
}

А ось розмітка. Об’єкт отримує data-crm-object, список — data-crm-array, а кожен текст усередині — повний шлях з індексом елемента:

type LangMap = Record<string, string>;
type FaqItem = { question: LangMap; answer: LangMap };
type FaqBlock = { content: { title: LangMap; items: FaqItem[] } };

export function Faq({ block, lang }: { block: FaqBlock; lang: string }) {
  const { title, items } = block.content;

  return (
    <section data-crm-object="faq_section">
      <h2 data-crm-text="faq_section.title">{title[lang]}</h2>

      <div data-crm-array="faq_section.items">
        {items.map((item, i) => (
          <details key={i}>
            <summary data-crm-text={`faq_section.items.${i}.question`}>
              {item.question[lang]}
            </summary>
            <p data-crm-text={`faq_section.items.${i}.answer`}>
              {item.answer[lang]}
            </p>
          </details>
        ))}
      </div>
    </section>
  );
}

// Використання: <Faq block={page.content.faq.content.faq_section} lang="en" />

Масиви всередині масивів працюють так само — просто чергуйте маркери полів та індекси, наприклад pricing.plans.1.features.0.text.

Крок 4. Віддавайте свіжий контент у режимі Live

З нашого боку кожна правка в CRM потрапляє в API миттєво. Але у вашого сайту може бути власний кеш: браузер може тримати відповіді API до 60 секунд, а revalidate у Next.js — протягом свого вікна. Відвідувачі цього не помітять. А от редактор, який щойно натиснув «Зберегти» й досі бачить старий текст, — помітить.

Рішення: коли сторінку відкрито в режимі Live, робіть запит із cache: 'no-store'. Режим Live можна впізнати за параметром URL crm_live або за тим, що сторінка працює всередині iframe. Наш власний еталонний сайт робить саме так:

// crm.ts — чи відкрито сторінку в режимі Live у CRM?
export function isCrmLive(): boolean {
  if (typeof window === 'undefined') return false;
  try {
    if (new URLSearchParams(window.location.search).has('crm_live')) return true;
    // Параметр може загубитися після внутрішнього посилання — перевірка на iframe це покриває
    return window.parent !== window;
  } catch {
    return false;
  }
}

export async function getPage(marker: string) {
  const res = await fetch(`https://back.sitecog.com/content/v1/pages/${marker}`, {
    headers: { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' },
    // Редактори завжди отримують свіжий контент, відвідувачі — швидкий із кешу
    cache: isCrmLive() ? 'no-store' : 'default',
  });
  if (!res.ok) throw new Error(`Content API: ${res.status}`);
  return res.json();
}

Що, де й скільки кешується, розказано на сторінці «Кеш і ETag».

Чекліст

  • Тег widget.js є на кожній сторінці, перед </body>.
  • Відповіді містять frame-ancestors 'self' https://sitecog.com.
  • Заголовка X-Frame-Options немає ніде — перевірте також панель хостингу, CDN і налаштування фреймворку за замовчуванням.
  • Редаговані елементи мають атрибути data-crm-*, а маркери збігаються з CRM літера в літеру.
  • Об’єкти й списки обгорнуто в data-crm-object / data-crm-array, а внутрішні шляхи мають правильні індекси.
  • У режимі Live сайт запитує контент із cache: 'no-store'.
  • Ви відкрили сайт у режимі Live, клацнули заголовок, змінили його й побачили зміну. 🎉

Якщо щось не так

«Сайт забороняє вбудовування»

CRM спробувала відкрити ваш сайт у фреймі, а браузер сказав «ні». Звичайні підозрювані:

  • директиви frame-ancestors немає або в ній немає https://sitecog.com;
  • щось і далі надсилає X-Frame-Options: панель хостингу, CDN, плагін безпеки, helmet в Express;
  • CSP задано через <meta>, а не заголовком, тож frame-ancestors ігнорується;
  • заголовок налаштовано для одного хоста, а сайт відкривається на іншому (з www чи без).

Перевірте, що насправді надсилає ваш сервер:

curl -sI https://your-site.com | grep -iE "content-security-policy|x-frame-options"

Елемент не клікається в режимі Live

  • Атрибута немає у відрендереному HTML. Дивіться сторінку в DevTools, а не вихідний код — деякі компоненти не передають невідомі пропси в DOM.
  • Шлях зіпсований: пробіл, дефіс, не латинська літера, крапка в кінці. Віджет пише попередження про формат маркера в консоль браузера.
  • Розмічено лише контейнер. data-crm-object і data-crm-array групують, а клікабельні частини — це тексти й картинки всередині, і їм потрібні власні data-crm-text / data-crm-image.
  • Скрипта віджета немає саме на цій сторінці — легко прогавити, коли на сайті кілька layout-ів.

Зберегли, але зміни не видно

  • Ваш запит кешується. У режимі Live використовуйте cache: 'no-store' (крок 4).
  • Сторінка повністю статична — зібрана один раз під час деплою, — тож про новий контент вона дізнається лише після наступної збірки. Нехай вона робить запит під час кожного звернення, бодай у режимі Live.
  • Елемент показує захардкоджений текст або запасне значення замість даних з API: атрибут є, а даних немає.
  • Шлях указує не на те, що відрендерено, — наприклад, елемент показує елемент 1, а розмічений як items.0.
  • Ключ сайту належить іншому сайту, або сторінка рендерить іншу мову, ніж ту, яку редагують. Див. «Мови та запасна мова».

Віджет уміє більше

Той самий тег widget.js рахує перегляди, надсилає ваші події через window.crmTrack(name, params), перетворює форми form[data-crm-lead] на заявки в CRM і показує онлайн-чат підтримки. Жодних зайвих скриптів — беріть, що потрібно: