diff --git a/.skaro/tasks/T-024-statya-spravki-klassy-vidzheta.md b/.skaro/tasks/T-024-statya-spravki-klassy-vidzheta.md new file mode 100644 index 0000000..c9ee54f --- /dev/null +++ b/.skaro/tasks/T-024-statya-spravki-klassy-vidzheta.md @@ -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. diff --git a/docs/portal-curation/README.md b/docs/portal-curation/README.md index d5af583..f78f493 100644 --- a/docs/portal-curation/README.md +++ b/docs/portal-curation/README.md @@ -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` сначала проверяет, что карта полностью совпадает с опубликованными статьями выбранного портала: состав, разделы, язык и ссылки. diff --git a/docs/portal-curation/articles/klassy-vidzheta.yml b/docs/portal-curation/articles/klassy-vidzheta.yml new file mode 100644 index 0000000..e3fb3fd --- /dev/null +++ b/docs/portal-curation/articles/klassy-vidzheta.yml @@ -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). diff --git a/docs/portal-curation/chatballs.json b/docs/portal-curation/chatballs.json index 72af03b..348896e 100644 --- a/docs/portal-curation/chatballs.json +++ b/docs/portal-curation/chatballs.json @@ -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"], diff --git a/docs/portal-curation/widgetClassesDocumentation.test.ts b/docs/portal-curation/widgetClassesDocumentation.test.ts new file mode 100644 index 0000000..d3bc5e7 --- /dev/null +++ b/docs/portal-curation/widgetClassesDocumentation.test.ts @@ -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(""); + 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 = plan.related; + expect(related[article.slug]).toContain("veb-vidzhet"); + expect(helpArticleUrl(article.slug)).toBe("https://chatballs.com.edevs.tech/articles/klassy-vidzheta"); + }); +});