📝 docs(webchat): добавить статью о классах виджета

This commit is contained in:
Andrey committed 2026-10-01 12:54:24 +03:00
1 parent 29e2232fc3
commit 52524287ae
5 files changed
+174 -4

No files matched your search

@@ -0,0 +1,29 @@
---
id: T-024
title: Статья справки «Классы виджета»
milestone: M04
status: done
depends_on:
- T-022
order: 6
spec: "0021"
created: 2026-09-29
branch: skaro/T-024-statya-spravki-klassy-vidzheta
---
## Цель
Администратор знает, какие классы можно стилизовать своим CSS (R-9).
## Критерии приёмки
- [x] В справке опубликована и доступна по адресу /articles/klassy-vidzheta статья со списком классов виджета, назначением и примером CSS; проверка ссылки из раздела «Оформление» относится к T-023 и не блокирует приёмку T-024.
- [x] Статья совпадает со списком классов в коде.
## Заметки
Статьи справки: docs/portal-curation/chatballs.json (там же статья veb-vidzhet); ссылки — shared/help helpArticleUrl.
## Итог
Статья «Классы виджета» опубликована и доступна в действующем центре помощи по /articles/klassy-vidzheta; применены подготовленные порядок и связи статей. Браузерная проверка и реальный API подтвердили таблицу, назначение и CSS-пример, а опубликованный текст и все 26 классов совпадают с исходниками. Оба актуальных критерия T-024 проверены и выполнены; изменения репозитория находятся в коммите b8654529.
+22 -2
View File
@@ -1,8 +1,28 @@
# Редакционный порядок центра помощи Chatballs
`chatballs.json` задаёт порядок опубликованных статей внутри каждого раздела и
направленные связи между статьями. Карта охватывает 72 русскоязычные статьи и
170 переходов. Она не меняет тексты, категории и статусы публикации.
направленные связи между статьями. Карта охватывает 73 русскоязычные статьи и
172 перехода. Она не меняет тексты, категории и статусы публикации.
`articles/klassy-vidzheta.yml` содержит статью «Классы виджета» в штатном
формате импорта статей портала. Её текст сверяется с
`apps/web-chat/src/widgetClasses.ts` тестом `widgetClassesDocumentation.test.ts`:
```sh
npx vitest run docs/portal-curation/widgetClassesDocumentation.test.ts
```
При реализации раздела «Оформление» ссылка «Классы виджета» должна получить
адрес `helpArticleUrl("klassy-vidzheta")` из `shared/help`. В текущей версии
`WebIntegrationPage` этот раздел отключён; подключение ссылки и проверка
перехода зависят от реализации раздела в T-023.
Добавление YAML в репозиторий само по себе не публикует статью. Перед
применением обновлённой карты нужно импортировать YAML через библиотеку
статей центра помощи в существующий раздел «Точки входа» и опубликовать
ревизию. Изменения действующего портала выполняются только после
согласования с владельцем. До публикации новой статьи проверка карты
завершится ошибкой состава статей.
Команда `apply_portal_curation` сначала проверяет, что карта полностью совпадает
с опубликованными статьями выбранного портала: состав, разделы, язык и ссылки.
@@ -0,0 +1,78 @@
articles:
- slug: "klassy-vidzheta"
categoryPath:
- "Точки входа"
locale: "ru"
title: "Классы виджета"
summary: "Какие части окна чата можно оформить своим CSS: классы, их назначение и пример стилей."
content: |
Свои CSS-стили меняют оформление внутри окна чата. Откройте настройки веб-подключения, раздел **Оформление**, и вставьте правила в поле **Свои CSS-стили**. Сохраните изменения: код виджета на сайте менять не требуется.
Стили применяются внутри iframe чата и не влияют на страницу сайта. Цвет, иконку, положение, размер и форму кнопки открытия задавайте настройками раздела «Оформление»: перечисленные ниже классы относятся к окну чата, а не к этой кнопке.
## Классы окна чата
В CSS перед именем класса ставьте точку: например, `.cb-header`.
| Класс | Назначение |
| --- | --- |
| `cb-widget` | Корневой контейнер окна чата. |
| `cb-header` | Шапка окна чата. |
| `cb-header-icon` | Иконка в шапке: стандартный знак или загруженное изображение. Отсутствует, если иконка отключена. |
| `cb-header-title` | Заголовок в шапке. |
| `cb-header-button` | Кнопки разворачивания и сворачивания окна в шапке. |
| `cb-body` | Прокручиваемая область чата. |
| `cb-message` | Контейнер сообщения клиента, агента или специалиста. |
| `cb-message--client` | Дополнительный класс контейнера сообщения клиента. |
| `cb-message-content` | Обёртка содержимого сообщения, задающая его максимальную ширину. |
| `cb-bubble` | Пузырь сообщения клиента, агента или специалиста. |
| `cb-bubble--client` | Дополнительный класс пузыря сообщения клиента. |
| `cb-bubble--agent` | Дополнительный класс пузыря ответа агента или специалиста. |
| `cb-bubble--operator` | Дополнительный класс пузыря ответа специалиста; применяется вместе с `cb-bubble--agent`. |
| `cb-bubble--audio` | Дополнительный класс пузыря голосового сообщения. |
| `cb-message-time` | Время или состояние отправки под сообщением клиента. |
| `cb-composer` | Область ввода сообщения, включая режим записи голосового. |
| `cb-composer-input` | Текстовое поле ввода сообщения. |
| `cb-composer-button` | Кнопки в области ввода: вложение, запись, отправка и отмена записи. |
| `cb-send-button` | Дополнительный класс кнопки отправки текста, вложения или голосового сообщения. |
| `cb-record-button` | Дополнительный класс кнопки начала записи голосового сообщения. |
| `cb-start-footer` | Область с кнопкой начала чата. |
| `cb-start-button` | Кнопка согласия и начала чата. |
| `cb-form` | Форма передачи номера телефона по запросу в чате. |
| `cb-form-field` | Поле номера телефона в этой форме. |
| `cb-form-submit` | Кнопка отправки номера телефона. |
| `cb-brand` | Нижняя область с подписью Chatballs. |
Классы с `--` дополняют основной класс на том же элементе. Например, сообщение клиента имеет классы `cb-bubble` и `cb-bubble--client`. Общее правило для `.cb-bubble` затронет все пузыри; правило для `.cb-bubble--client` — только сообщения клиента.
Чтобы изменить только ответы агента, исключите ответы специалиста: `.cb-bubble--agent:not(.cb-bubble--operator)`.
## Пример CSS
Этот пример меняет шрифт шапки, скругление сообщений клиента и регистр текста кнопки начала чата:
```css
.cb-header {
font-family: "PT Sans", sans-serif;
}
.cb-bubble--client {
border-radius: 18px 4px 18px 18px;
}
.cb-start-button {
text-transform: uppercase;
}
```
Если PT Sans не установлен у посетителя, браузер использует шрифт без засечек. Шрифт, подключённый только на странице сайта, не становится доступным внутри iframe.
Базовые стили этих классов имеют низкую специфичность, а свои CSS-стили добавляются после них. Для обычного изменения оформления достаточно селектора класса без `!important`. Вложенные элементы могут иметь собственные стили: например, правило для контейнера не меняет автоматически цвета всех его потомков.
## Ограничения и сброс
Размер своих CSS-стилей — до 10 КБ. При сохранении удаляются `@import`, внешние адреса в `url(...)` и `expression`. Подключать таким способом внешние шрифты или изображения нельзя.
Чтобы вернуться к базовому оформлению, нажмите **Сбросить** возле поля CSS и сохраните настройки.
О подключении чата к сайту читайте в статье [Чат на сайте](/articles/veb-vidzhet).
+3 -2
View File
@@ -5,7 +5,7 @@
"start": ["chto-takoe-chatballs", "pervyj-den", "demo-dannye", "roli-i-prava"],
"installation": ["pervyj-zapusk", "adres-i-https", "pochta-smtp", "yazyk-ustanovki", "hranilishche-fajlov", "rezervnoe-kopirovanie", "obnovlenie", "diagnostika", "zvonki-relay"],
"organization": ["organizaciya-nastrojki", "ai-provajder", "gruppy", "kogda-zvat-na-pomoshch", "neskolko-organizacij"],
"entry-points": ["tochki-vhoda-obzor", "telegram", "token-telegram", "max", "token-max", "veb-vidzhet", "pochtovyj-yashchik", "vkontakte", "golosovye-i-zvonki", "bot-uvedomlenij"],
"entry-points": ["tochki-vhoda-obzor", "telegram", "token-telegram", "max", "token-max", "veb-vidzhet", "klassy-vidzheta", "pochtovyj-yashchik", "vkontakte", "golosovye-i-zvonki", "bot-uvedomlenij"],
"agents": ["kartochka-agenta", "model-i-provajder", "instrukcii-agenta", "znaniya-agenta", "zapusk-i-ostanovka", "agent-ne-znaet-otveta", "yazyk-otveta"],
"knowledge": ["material-i-vlozheniya", "kategorii-znanij", "kak-agent-nahodit-otvet", "import-yaml-znaniya", "massovye-dejstviya"],
"portals": ["sozdanie-portala", "kategorii-portala", "statya-portala", "revizii-i-publikaciya", "oformlenie-portala", "svoj-domen-portala", "import-statej", "portal-i-agent"],
@@ -39,7 +39,8 @@
"token-telegram": ["telegram", "bot-uvedomlenij"],
"max": ["token-max", "telegram"],
"token-max": ["max", "token-telegram"],
"veb-vidzhet": ["golosovye-i-zvonki", "oformlenie-portala", "tochki-vhoda-obzor"],
"veb-vidzhet": ["klassy-vidzheta", "golosovye-i-zvonki", "oformlenie-portala", "tochki-vhoda-obzor"],
"klassy-vidzheta": ["veb-vidzhet"],
"pochtovyj-yashchik": ["pochta-smtp", "tochki-vhoda-obzor"],
"vkontakte": ["tochki-vhoda-obzor", "kartochka-agenta"],
"golosovye-i-zvonki": ["fajly-golosovye-zvonki", "zvonki-relay"],
@@ -0,0 +1,42 @@
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import articleYaml from "./articles/klassy-vidzheta.yml?raw";
import plan from "./chatballs.json";
import { widgetClasses } from "../../apps/web-chat/src/widgetClasses";
import { helpArticleUrl } from "../../apps/internal-ui/src/shared/help";
import { parseArticleYaml } from "../../apps/internal-ui/src/features/support-portals/parseArticleYaml";
import { MarkdownContent, parseMarkdown } from "../../apps/internal-ui/src/features/help-center/MarkdownContent";
const { articles: [article] } = parseArticleYaml(articleYaml);
describe("widget classes help article", () => {
it("documents every stable class exactly once with a purpose", () => {
const rows = [...article.content.matchAll(/^\| `(cb-[a-z-]+)` \| (.+) \|$/gm)];
expect(rows.map((row) => row[1]).sort()).toEqual(Object.values(widgetClasses).sort());
expect(rows.every((row) => row[2].trim().length > 0)).toBe(true);
const html = renderToStaticMarkup(createElement(MarkdownContent, { content: article.content }));
expect(html).toContain("<table>");
expect(html).toContain('class="language-css"');
const sample = parseMarkdown(article.content).blocks.find((block) => block.kind === "code");
if (!sample) throw new Error("Missing CSS example");
const sampleClasses = [...sample.text.matchAll(/\.(cb-[a-z-]+)\s*\{/g)].map((match) => match[1]);
expect(sampleClasses.length).toBeGreaterThan(0);
for (const name of sampleClasses) expect(Object.values(widgetClasses)).toContain(name);
});
it("registers the importable article next to web widget help with reciprocal links", () => {
expect(article.title).toBe("Классы виджета");
expect(article.locale).toBe(plan.locale);
expect(article.categoryPath).toEqual(["Точки входа"]);
const entries: string[] = plan.order["entry-points"];
expect(entries[entries.indexOf("veb-vidzhet") + 1]).toBe(article.slug);
expect(Object.values(plan.order).flat().filter((slug) => slug === article.slug)).toHaveLength(1);
expect(plan.related["veb-vidzhet"]).toContain(article.slug);
const related: Record<string, string[]> = plan.related;
expect(related[article.slug]).toContain("veb-vidzhet");
expect(helpArticleUrl(article.slug)).toBe("https://chatballs.com.edevs.tech/articles/klassy-vidzheta");
});
});