
Привет, Хабр! Это Екатерина Саяпина, Product Owner платформы МТС Exolve. Сегодня покажу, как быстро добавить виджет обратного звонка на страницу, созданную с помощью MkDocs — статического генератора сайтов с уклоном в техническую документацию. Такое размещение виджета бывает нужно в справочных разделах сложных продуктов, где клиентам может потребоваться консультация или разъяснение каких-то технических моментов.
Для большей конкретики возьмем страницу с описанием S3 API вымышленного облачного провайдера — это типичный сценарий, где пользователю может потребоваться быстрая консультация специалиста.
Настройка MkDocs через Docker
Начнем с базовой установки MkDocs. Docker используется, чтобы упростить работу с зависимостями. В этом примере мы возьмем готовый образ squidfunk/mkdocs-material. Он содержит не только сам MkDocs, но и популярную тему Material, которая делает документацию красивой и удобной.
Одна команда — и вы получаете полностью настроенный проект с современным дизайном. Не нужно устанавливать Python, pip, возиться с виртуальным окружением — просто запустил контейнер, и все готово к работе:
mkdir cloudy-docs
cd cloudy-docs
docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material new .
После выполнения этой команды в текущей директории появится структура проекта MkDocs. Затем мы редактируем mkdocs.yml — это главный конфигурационный файл нашего проекта:
site_name: Cloudy Cloud Docs
theme:
name: material
palette:
primary: blue
extra_javascript:
- javascripts/exolve-callback.js
Тут мы задаем название сайта, выбираем тему Material и настраиваем синий цвет в качестве основного. Также мы добавляем ссылку на JavaScript-файл нашего виджета обратного звонка.
Файл конфигурации очень прост, но в реальных проектах он может быть гораздо более сложным, с настройками навигации, плагинов и т. д.
Создаем простую страницу документации
В этом примере мы создаем раздел на сайте с описанием S3 API, где можно найти информацию об основных операциях API для хранения данных в облаке. Использование Markdown упрощает работу с документацией — вы пишете текст с минимальной разметкой, а MkDocs превращает его в красивую HTML-страницу.
Содержимое нашей страницы для примера — в блоке ниже:
# Cloudy Cloud S3 API
Простой и надежный сервис для хранения данных в облаке.
## Аутентификация
Для доступа к API используйте ваши ключи доступа:
curl -X GET https://s3.cloudy.cloud/buckets \
-H "Authorization: Bearer YOUR_API_KEY"
## Основные операции
### Создание бакета
curl -X POST https://s3.cloudy.cloud/buckets \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "my-bucket", "region": "europe-west"}'
### Загрузка файла
curl -X PUT https://s3.cloudy.cloud/buckets/my-bucket/file.txt \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: text/plain" \
--data-binary @file.txt
### Скачивание файла
curl -X GET https://s3.cloudy.cloud/buckets/my-bucket/file.txt \
-H "Authorization: Bearer YOUR_API_KEY"
## Нужна помощь?
<div id="exolve-callback-container">
Возникли вопросы по API? Наши инженеры готовы помочь!
<div id="exolve-callback-button"></div>
</div>
Посмотрите на последний раздел «Нужна помощь?». Здесь будет наша форма для обратного звонка — для этого мы и создаем контейнер с идентификатором exolve-callback-container.
Добавляем виджет
Создадим директорию для JavaScript-файлов и добавим скрипт для виджета:
mkdir -p docs/javascripts
cd docs/javascripts
touch exolve-callback.js
JavaScript-файл exolve-callback.js — это сердце нашего виджета. Он отвечает за создание кнопки, открытие модального окна при клике на нее, обработку формы и отправку данных в сервис МТС Exolve.
А теперь перейдем к созданию виджета. Код начинается с ожидания загрузки DOM. Затем ищем контейнер для кнопки обратного звонка:
document.addEventListener('DOMContentLoaded', function() {
const callbackButton = document.getElementById('exolve-callback-button');
if (callbackButton) {
const button = document.createElement('button');
button.textContent = 'Получить консультацию';
button.className = 'exolve-button';
button.style.backgroundColor = '#2196f3';
button.style.color = '#ffffff';
button.style.padding = '8px 16px';
button.style.border = 'none';
button.style.borderRadius = '4px';
button.style.cursor = 'pointer';
button.style.marginTop = '10px';
button.addEventListener('click', function() {
createModal();
});
callbackButton.appendChild(button);
}
Здесь создается затемненный фон (overlay) на весь экран, само модальное окно, его заголовок и форма.
Модальное окно размещается по центру с белым фоном и тенью:
function createModal() {
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
overlay.style.zIndex = '1000';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
const modal = document.createElement('div');
modal.style.backgroundColor = '#fff';
modal.style.padding = '20px';
modal.style.borderRadius = '8px';
modal.style.maxWidth = '400px';
modal.style.width = '100%';
modal.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
const title = document.createElement('h3');
title.textContent = 'Запрос обратного звонка';
title.style.marginTop = '0';
title.style.color = '#333';
const form = document.createElement('form');
Следующий блок создает поля формы для ввода имени и телефона. Для каждого поля создается подпись (label) и само окно ввода (input) со стилями. Оба поля помечены как обязательные и заполняют всю ширину родительского элемента:
const nameLabel = document.createElement('label');
nameLabel.textContent = 'Ваше имя:';
nameLabel.style.display = 'block';
nameLabel.style.marginBottom = '5px';
nameLabel.style.fontWeight = 'bold';
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.required = true;
nameInput.style.width = '100%';
nameInput.style.padding = '8px';
nameInput.style.marginBottom = '15px';
nameInput.style.boxSizing = 'border-box';
nameInput.style.border = '1px solid #ddd';
nameInput.style.borderRadius = '4px';
const phoneLabel = document.createElement('label');
phoneLabel.textContent = 'Ваш номер телефона:';
phoneLabel.style.display = 'block';
phoneLabel.style.marginBottom = '5px';
phoneLabel.style.fontWeight = 'bold';
const phoneInput = document.createElement('input');
phoneInput.type = 'tel';
phoneInput.placeholder = '+7 (___) ___-__-__';
phoneInput.required = true;
phoneInput.style.width = '100%';
phoneInput.style.padding = '8px';
phoneInput.style.marginBottom = '15px';
phoneInput.style.boxSizing = 'border-box';
phoneInput.style.border = '1px solid #ddd';
phoneInput.style.borderRadius = '4px';
Далее создаются кнопки «отмена» и «отправить», которые размещаются в контейнере с гибкой разметкой. Затем вся структура собирается воедино: поля добавляются в форму, кнопки в контейнер, форма и заголовок в модальное окно, а оно в оверлей. Результат добавляется на страницу:
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'space-between';
buttonContainer.style.marginTop = '20px';
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Отмена';
cancelButton.type = 'button';
cancelButton.style.padding = '8px 16px';
cancelButton.style.backgroundColor = '#f5f5f5';
cancelButton.style.border = '1px solid #ddd';
cancelButton.style.borderRadius = '4px';
cancelButton.style.cursor = 'pointer';
const submitButton = document.createElement('button');
submitButton.textContent = 'Отправить';
submitButton.type = 'submit';
submitButton.style.padding = '8px 16px';
submitButton.style.backgroundColor = '#2196f3';
submitButton.style.color = '#fff';
submitButton.style.border = 'none';
submitButton.style.borderRadius = '4px';
submitButton.style.cursor = 'pointer';
form.appendChild(nameLabel);
form.appendChild(nameInput);
form.appendChild(phoneLabel);
form.appendChild(phoneInput);
buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(submitButton);
form.appendChild(buttonContainer);
modal.appendChild(title);
modal.appendChild(form);
overlay.appendChild(modal);
document.body.appendChild(overlay);
Теперь время добавить обработчики событий: отправки формы и закрытия модального окна при нажатии на кнопку «Отмена» или на затемненную область. При отправке проверяется заполнение полей, и если все в порядке, данные отправляются в сервис Exolve и показывается сообщение об успехе:
cancelButton.addEventListener('click', function() {
document.body.removeChild(overlay);
});
overlay.addEventListener('click', function(event) {
if (event.target === overlay) {
document.body.removeChild(overlay);
}
});
form.addEventListener('submit', function(event) {
event.preventDefault();
const name = nameInput.value.trim();
const phone = phoneInput.value.trim();
if (!name || !phone) {
alert('Пожалуйста, заполните все поля');
return;
}
sendDataToExolve(name, phone);
document.body.removeChild(overlay);
showSuccessMessage();
});
}
Теперь реализуем логику отправки данных. Функция sendDataToExolve очистит введенный телефон от нецифровых символов и проверит, загружен ли скрипт Exolve. Если нет, то она исправит это досадное недоразумение:
function sendDataToExolve(name, phone) {
const cleanPhone = phone.replace(/\D/g, '');
if (typeof window.ExolveCallbackInit !== 'function') {
const script = document.createElement('script');
script.src = 'https://widget.exolve.ru/callback/v1/js/button-loader.min.js';
script.async = true;
document.head.appendChild(script);
script.onload = function() {
makeExolveRequest(name, cleanPhone);
};
} else {
makeExolveRequest(name, cleanPhone);
}
}
Структура данных для API в текущей версии просто выводится в консоль с помощью функции makeExolveRequest. В реальном проекте здесь должен быть код отправки запроса на сервер:
function makeExolveRequest(name, phone) {
const requestData = {
callback_resource_id: 1234, // Замените на ваш ID из личного кабинета
number_code: phone,
client_name: name,
line_1: {
destinations: [
{ number: "74951234567" } // Замените на номер вашего оператора
]
},
line_2: {
destinations: [
{ number: phone }
]
}
};
console.log('Отправляем данные в Exolve:', requestData);
console.log('Запрос на обратный звонок отправлен');
}
Последняя функция — showSuccessMessage() — создает и показывает уведомление об успешной отправке запроса. Оно появляется в правом нижнем углу экрана как зеленый блок с текстом и автоматически исчезает через 5 секунд:
function showSuccessMessage() {
const messageDiv = document.createElement('div');
messageDiv.style.position = 'fixed';
messageDiv.style.bottom = '20px';
messageDiv.style.right = '20px';
messageDiv.style.backgroundColor = '#4CAF50';
messageDiv.style.color = 'white';
messageDiv.style.padding = '16px';
messageDiv.style.borderRadius = '4px';
messageDiv.style.zIndex = '1000';
messageDiv.textContent = 'Спасибо! Наш специалист скоро свяжется с вами.';
document.body.appendChild(messageDiv);
setTimeout(function() {
document.body.removeChild(messageDiv);
}, 5000);
}
Проверка работы
Когда все готово, мы стартуем локальный сервер для разработки. Опять же используем Docker, чтобы не устанавливать ничего на компьютер. Команда запускает контейнер с MkDocs и открывает порт 8 000 для доступа к сайту.
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material
Теперь мы можем открыть http://localhost:8000 в браузере и увидеть нашу страницу с виджетом:

Так можно все проверить перед публикацией на сайт.
Как работает виджет?

Когда пользователь нажимает на кнопку, виджет:
Пользователь нажимает на кнопку «Получить консультацию».
Открывается модальное окно с формой для ввода имени и номера телефона.
После заполнения формы и нажатия «Отправить» данные передаются в МТС Exolve.
Пользователь получает уведомление об успешной обработке запроса.
Сервис МТС Exolve инициирует звонок оператору и пользователю и соединяет их.
На серверной стороне это обрабатывается с помощью API МТС Exolve, которое принимает запрос в формате json:
{
"callback_resource_id": 1234,
"number_code": "79991234567",
"client_name": "Иван Петров",
"line_1": {
"destinations": [
{ "number": "74951234567" }
]
},
"line_2": {
"destinations": [
{ "number": "79991234567" }
]
}
}
Финальная сборка
Когда все готово и протестировано, можно собрать статическую версию документации для публикации. Опять же мы используем Docker:
docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material build
Команда build создает полностью статический сайт, который можно разместить на любом хостинге. Готовые файлы появляются в директории site/. Это удобно, так как вам не нужно устанавливать никакое серверное программное обеспечение — просто загрузите файлы, и все готово.
Подружить виджет обратного звонка в MkDocs достаточно легко. Этот пример показывает, что такой элемент можно встроить практически в любую веб-страницу: будь то документация, интернет-магазин или корпоративный сайт.
На этом все! Если у вас остались вопросы по интеграции виджета в ваши проекты, задавайте их в комментариях.