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

Правка на сайте: виджет и разметка data-crm

Обновлено:

API приносит контент на ваш сайт. А эта страница избавит вас от просьб «поправить одну запятую на главной» в пятницу в шесть вечера. Один тег <script>, несколько атрибутов data-crm-* в разметке — и те, кто отвечает за тексты, сами кликают по заголовку прямо на живом сайте, исправляют опечатку и сохраняют. Без задач в трекере и без выкладки.

Подключение — четыре шага, и думать придётся только на одном из них:

  1. Подключите скрипт виджета

    Один тег <script> на каждой странице.
  2. Разрешите CRM открывать сайт во фрейме

    Один заголовок ответа — и CRM сможет показать ваш сайт в режиме Live.
  3. Разметьте редактируемые элементы

    Атрибутами data-crm-* подскажите редактору, какой элемент показывает какой блок.
  4. Отдавайте свежий контент в режиме Live

    Пока сайт открыт в CRM, обходите свой кэш — и правки видны сразу.

Как это выглядит для редактора

С места редактора всё выглядит так:

  1. Он открывает ваш сайт в CRM в режиме Live. Это настоящий сайт, а не макет.
  2. У каждого размеченного элемента при наведении появляется рамка. Клик по нужному — заголовку, абзацу, картинке.
  3. Меняет текст или загружает новое изображение и сохраняет.
  4. Страница обновляется уже с новым содержимым. Всё — редактор кода никто не открывал.

Если элемент размечен маркером блока, которого в CRM ещё нет, редактор может создать этот блок прямо с сайта. Так что разметку можно выкатить заранее, а тексты команда заполнит потом.

Как это устроено

Ваша страница по-прежнему берёт контент из Content API — тут ничего не меняется. Атрибуты data-crm-* сами ничего не выводят: они лишь связывают элемент DOM с блоком в CRM, как бирка на ящике комода.

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

Шаг 1. Подключите скрипт виджета

Тег нужен на каждой странице, прямо перед </body>. Если у сайта общий шаблон, ставьте его туда — и один раз.

<!doctype html>
<html lang="ru">
  <head>…</head>
  <body>
    …ваша страница…

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

Со скриптом всё. Ни ключа, ни вызова init, ни объекта настроек — виджет сам понимает, открыт ли он внутри CRM.

Шаг 2. Разрешите CRM открывать сайт во фрейме

Режим Live показывает ваш сайт во фрейме на 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 …".
    # Если X-Frame-Options ставит само приложение за proxy_pass, срежьте его здесь:
    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.ru}</h1>
  <p data-crm-text="hero_subtitle">{hero.hero_subtitle.content.ru}</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>

Синтаксис пути: внутрь объектов и списков

У блоков-объектов и блоков-списков есть поля внутри, поэтому путь продолжается через точку. Первый сегмент — всегда маркер блока. Дальше идут маркеры полей объекта и числовые индексы элементов списка (с нуля).

ПутьКуда указывает
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=rujson
"faq_section": {
  "type": "object",
  "content": {
    "title": { "ru": "Частые вопросы" },
    "items": [
      {
        "question": { "ru": "Сколько идёт доставка?" },
        "answer": { "ru": "1–3 дня." }
      },
      {
        "question": { "ru": "Можно вернуть наушники?" },
        "answer": { "ru": "Да, в течение 14 дней." }
      }
    ]
  }
}

А вот разметка. Объект получает 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="ru" />

Списки внутри списков размечаются так же — чередуйте маркеры полей и индексы, например pricing.plans.1.features.0.text.

Шаг 4. Отдавайте свежий контент в режиме Live

У нас любая правка в CRM доходит до API мгновенно. Но у вашего сайта может быть собственный кэш: браузер держит ответы API до 60 секунд, у revalidate в Next.js своё окно. Посетитель этого не заметит. А вот редактор, который только что нажал «Сохранить» и видит старый текст, — заметит обязательно.

Решение простое: если страница открыта в режиме Live, запрашивайте контент с cache: 'no-store'. Понять, что вы в Live, можно по параметру crm_live в адресе или по тому, что страница открыта во фрейме. Наш эталонный сайт делает ровно так:

// 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;
    // Параметр может потеряться после перехода по внутренней ссылке — выручает проверка фрейма
    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.
  • На этой конкретной странице нет скрипта виджета — легко пропустить, когда шаблонов несколько.

Сохранили, а изменений не видно

  • Запрос к API кэшируется. В режиме Live используйте cache: 'no-store' (шаг 4).
  • Страница полностью статическая — собрана один раз при выкладке — и про новый контент узнает только при следующей сборке. Пусть она запрашивает данные при каждом запросе, хотя бы в режиме Live.
  • Элемент показывает текст, зашитый в код, или запасное значение, а не данные из API: атрибут на месте, а данных из CRM нет.
  • Путь указывает не туда, что выведено: например, элемент показывает элемент списка 1, а размечен как items.0.
  • Ключ принадлежит другому сайту или страница выводит не тот язык, который правят. См. Языки и запасной язык.

Виджет умеет больше

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