// ==UserScript==
// @name BlackRussia Forum Quick Replies UNIVERSAL
// @namespace http://tampermonkey.net/
// @version 3.1
// @description Добавляет настраиваемые кнопки быстрых ответов на форум BlackRussia с улучшенной мобильной поддержкой, возможностью добавления новых ответов, префиксами к темам, случайной цветной окантовкой (опционально), автоматическим приветствием с панорамными цветами и встроенным редактором стиля текста с визуальным выбором цвета, а также кнопкой помощи с инструкцией.
// @author Maras Ageev Тех. [06]
// @match https://forum.blackrussia.online/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const storageKey = 'br_forum_quick_replies';
const greetingStorageKey = 'br_forum_greeting';
let quickRepliesConfig = GM_getValue(storageKey, [
{ label: 'Ответить', text: 'Благодарю за обращение!', prefix: '', color: '', size: '', font: '' }
]);
let customGreeting = GM_getValue(greetingStorageKey, 'Добро пожаловать на форум BlackRussia!');
GM_addStyle(`
/* Обновленные стили */
.quick-replies-container {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.quick-replies-container button {
background-color: #2C2F33;
color: #F0F0F0;
border-radius: 5px;
padding: 6px 10px;
cursor: pointer;
font-size: 12px;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
border: none;
display: flex; /* Для центрирования изображения и текста */
align-items: center;
gap: 5px;
}
.quick-replies-container button:hover {
background-color: #3A3D42;
}
.quick-replies-container button img {
max-height: 20px; /* Ограничьте высоту изображения */
}
.config-button-inline, .help-button-inline {
background-color: #7289da; /* Цветные иконки */
color: #FFFFFF;
border: 1px solid #7289da;
border-radius: 5px;
padding: 8px 12px; /* Увеличим отступы */
margin-left: 5px;
cursor: pointer;
font-size: 14px; /* Увеличим размер шрифта */
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
display: inline-flex; /* Для центрирования иконки */
align-items: center;
justify-content: center;
line-height: 1; /* Убираем лишнее пространство вокруг иконки */
}
.config-button-inline:hover, .help-button-inline:hover {
background-color: #5865F2;
border-color: #5865F2;
}
/* Увеличим размер иконки */
.config-button-inline::before {
content: '⚙️';
font-size: 1.2em;
margin-right: 3px; /* Небольшой отступ справа от иконки */
}
.help-button-inline::before {
content: '❓';
font-size: 1.2em;
margin-right: 3px; /* Небольшой отступ справа от иконки */
}
#quickReplyConfigModal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #23272a;
color: #dcddde;
padding: 15px;
border-radius: 5px;
z-index: 1000;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
max-width: 95%; /* Уменьшим немного для десктопа */
max-height: 90%; /* Увеличим высоту для мобильных */
overflow-y: auto;
}
#quickReplyConfigModal h2 {
margin-bottom: 10px;
font-size: 1.2em;
text-align: center; /* Центрируем заголовок */
}
#quickReplyConfigModal .config-item {
margin-bottom: 15px;
padding: 10px;
border: 1px solid #5865F2; /* Добавим границу блоку */
border-radius: 5px;
}
#quickReplyConfigModal .config-item > div {
margin-bottom: 8px; /* Отступ между строками */
}
#quickReplyConfigModal .config-item > div:last-child {
margin-bottom: 0;
}
#quickReplyConfigModal label {
font-size: 0.9em;
margin-right: 5px;
display: block; /* Занимает всю ширину на мобильных */
margin-bottom: 3px;
}
#quickReplyConfigModal input[type="text"],
#quickReplyConfigModal select,
#quickReplyConfigModal input[type="color"] { /* Добавим стиль для color input */
background-color: #36393f;
color: #dcddde;
border: 1px solid #7289da;
border-radius: 3px;
padding: 8px; /* Увеличим отступы для касания */
font-size: 14px; /* Увеличим размер шрифта */
width: calc(100% - 16px); /* Учтем отступы */
box-sizing: border-box;
margin-bottom: 8px; /* Увеличим отступ снизу */
}
#quickReplyConfigModal .style-controls {
display: flex;
flex-direction: column; /* Разместим элементы вертикально на мобильных */
gap: 10px;
align-items: stretch; /* Растянем элементы на всю ширину */
}
#quickReplyConfigModal .style-controls > div {
display: flex;
flex-direction: column; /* Внутри тоже вертикально */
align-items: stretch;
}
#quickReplyConfigModal button {
background-color: #7289da;
color: #fff;
border: none;
border-radius: 5px; /* Увеличим радиус */
padding: 10px 15px; /* Увеличим отступы */
cursor: pointer;
font-size: 14px; /* Увеличим размер шрифта */
margin-top: 5px;
}
#quickReplyConfigModal button.delete-button {
background-color: #f04747;
}
#quickReplyConfigModal button.add-button {
background-color: #43b581;
margin-bottom: 10px;
}
#quickReplyConfigModal .config-list {
margin-bottom: 10px;
}
#quickReplyConfigModal .modal-buttons {
display: flex;
gap: 10px;
justify-content: space-around; /* Распределим кнопки по ширине */
padding-top: 15px;
}
#quickReplyConfigModal .modal-buttons button {
flex-grow: 1; /* Кнопки занимают равную ширину */
}
/* Стиль для приветствия */
.forum-greeting {
width: 100%;
padding: 15px 0;
text-align: center;
font-size: 1.1em;
color: #fff;
background-image: linear-gradient(to right, #30475E, #F06A6A, #8E44AD, #27AE60, #F39C12);
background-size: 500% 100%;
animation: panoramic-gradient 10s linear infinite alternate;
margin-bottom: 10px;
}
@keyframes panoramic-gradient {
0% { background-position: 0% 50%; }
100% { background-position: 100% 50%; }
}
/* Стиль для поля ввода приветствия */
#greetingInput {
background-color: #36393f;
color: #dcddde;
border: 1px solid #7289da;
border-radius: 5px; /* Увеличим радиус */
padding: 10px; /* Увеличим отступы */
width: calc(100% - 20px); /* Учтем отступы */
margin-bottom: 10px;
font-size: 14px; /* Увеличим размер шрифта */
box-sizing: border-box;
}
/* Медиа запрос для мобильных устройств (ширина экрана до 600px) */
@media (max-width: 600px) {
#quickReplyConfigModal {
max-width: 98%; /* Занимает почти всю ширину */
top: 0;
left: 0;
transform: none;
width: 100%;
height: 100%;
border-radius: 0;
padding: 10px;
}
#quickReplyConfigModal .modal-buttons {
flex-direction: column; /* Кнопки в столбик на маленьких экранах */
}
#quickReplyConfigModal .modal-buttons button {
margin-bottom: 10px;
}
#quickReplyConfigModal .style-controls {
flex-direction: column; /* Размещаем элементы вертикально */
}
#quickReplyHelpModal {
max-width: 95%;
}
}
/* Стиль для модального окна помощи */
#quickReplyHelpModal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #23272a;
color: #dcddde;
padding: 20px;
border-radius: 5px;
z-index: 1001;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
max-width: 90%;
max-height: 80%;
overflow-y: auto;
text-align: left;
}
#quickReplyHelpModal h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 1.1em;
}
#quickReplyHelpModal p {
margin-bottom: 10px;
}
#quickReplyHelpModal button {
background-color: #7289da;
color: #fff;
border: none;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
margin-top: 15px;
display: block;
width: 100%;
box-sizing: border-box;
}
`);
function saveConfig() {
GM_setValue(storageKey, quickRepliesConfig);
GM_setValue(greetingStorageKey, customGreeting);
}
function createQuickReplyButton(config, targetElement) {
const button = document.createElement('button');
button.textContent = config.label;
button.addEventListener('click', function() {
const editorElement = targetElement.querySelector('.fr-wrapper .fr-element');
const textarea = targetElement.querySelector('textarea');
const bbWrapper = targetElement.querySelector('.bbWrapper');
let styledText = config.text;
let styles = [];
if (config.color) {
styles.push(`color: ${config.color}`);
}
if (config.size) {
styles.push(`font-size: ${config.size}`);
}
if (config.font) {
styles.push(`font-family: ${config.font}`);
}
if (styles.length > 0) {
styledText = `<span style="${styles.join(';')}">${styledText}</span>`;
}
if (editorElement) {
editorElement.innerHTML = styledText;
} else if (textarea) {
textarea.value = styledText;
} else if (bbWrapper) {
const editor = XenForo.getEditor(bbWrapper);
if (editor) {
editor.insertText(config.text);
} else {
textarea.value += config.text;
}
} else {
console.error('Не найдено поле ввода текста.');
}
// Применение префикса, если это новая тема
if (window.location.href.includes('/post-thread')) {
const titleInput = document.querySelector('input[name="title"]');
if (titleInput && config.prefix) {
titleInput.value = config.prefix + ' ' + titleInput.value;
}
}
});
return button;
}
let buttonsAdded = false;
let modalVisible = false;
let greetingShown = false;
let helpModalVisible = false;
function getRandomBorderColor() {
const colors = ['#ffba52', '#ffdd57', '#50a3ff', '#81ff52', '#5bff73', '#ff5252', '#ff5299', '#52ffbd', '#ff8952'];
return colors[Math.floor(Math.random() * colors.length)];
}
function showHelpModal() {
if (helpModalVisible) return;
helpModalVisible = true;
const helpModal = document.createElement('div');
helpModal.id = 'quickReplyHelpModal';
const title = document.createElement('h3');
title.textContent = 'Инструкция по использованию быстрых ответов BlackRussia Forum';
helpModal.appendChild(title);
const authorInfo = document.createElement('p');
authorInfo.textContent = 'Автор: Maras Ageev Тех. [06]';
helpModal.appendChild(authorInfo);
const description = document.createElement('p');
description.textContent = 'Этот скрипт добавляет настраиваемые кнопки быстрых ответов под формой ввода текста на форуме BlackRussia. Вы можете настроить текст, префикс (который будет добавляться к заголовку новой темы), цвет текста, размер шрифта и шрифт для каждой кнопки.';
helpModal.appendChild(description);
const configSectionTitle = document.createElement('h3');
configSectionTitle.textContent = 'Настройка кнопок:';
helpModal.appendChild(configSectionTitle);
const configSteps = document.createElement('ol');
const step1 = document.createElement('li');
step1.textContent = 'Нажмите кнопку с иконкой "⚙️", чтобы открыть панель конфигурации.';
configSteps.appendChild(step1);
const step2 = document.createElement('li');
step2.textContent = 'В панели конфигурации вы можете добавлять, редактировать и удалять быстрые ответы.';
configSteps.appendChild(step2);
const step3 = document.createElement('li');
step3.textContent = 'Для каждого ответа вы можете указать: название кнопки, текст ответа, префикс, цвет текста, размер шрифта и шрифт.';
configSteps.appendChild(step3);
const step4 = document.createElement('li');
step4.textContent = 'Нажмите "Сохранить", чтобы применить изменения.';
configSteps.appendChild(step4);
helpModal.appendChild(configSteps);
const usingButtonsTitle = document.createElement('h3');
usingButtonsTitle.textContent = 'Использование кнопок:';
helpModal.appendChild(usingButtonsTitle);
const usingButtonsSteps = document.createElement('ol');
const step5 = document.createElement('li');
step5.textContent = 'Просто нажмите на нужную кнопку быстрого ответа, и текст будет автоматически вставлен в поле ввода.';
usingButtonsSteps.appendChild(step5);
helpModal.appendChild(usingButtonsSteps);
const closeButton = document.createElement('button');
closeButton.textContent = 'Закрыть';
closeButton.addEventListener('click', () => {
helpModal.remove();
helpModalVisible = false;
});
helpModal.appendChild(closeButton);
document.body.appendChild(helpModal);
}
function addQuickReplyButtons(formElement) {
if (!formElement || buttonsAdded) {
return;
}
buttonsAdded = true;
const quickRepliesContainer = document.createElement('div');
quickRepliesContainer.style.marginTop = '10px';
quickRepliesContainer.classList.add('quick-replies-container');
quickRepliesConfig.forEach(config => {
const button = createQuickReplyButton(config, formElement);
if (button.textContent !== 'Настроить') {
button.style.border = '2px solid ' + getRandomBorderColor();
}
quickRepliesContainer.appendChild(button);
});
const configButtonInline = document.createElement('button');
configButtonInline.classList.add('config-button-inline');
configButtonInline.addEventListener('click', openConfigModal);
quickRepliesContainer.appendChild(configButtonInline);
const helpButtonInline = document.createElement('button');
helpButtonInline.title = 'Инструкция';
helpButtonInline.classList.add('help-button-inline');
helpButtonInline.addEventListener('click', showHelpModal);
quickRepliesContainer.appendChild(helpButtonInline);
const editorContainer = formElement.querySelector('.fr-wrapper'); // Froala Editor
if (editorContainer && editorContainer.parentNode) {
editorContainer.parentNode.insertBefore(quickRepliesContainer, editorContainer.nextSibling);
return;
}
const textarea = formElement.querySelector('textarea'); // Обычный textarea
if (textarea && textarea.parentNode) {
textarea.parentNode.insertBefore(quickRepliesContainer, textarea.nextSibling);
return;
}
const bbWrapper = formElement.querySelector('.bbWrapper'); // XenForo BBCode Editor
if (bbWrapper && bbWrapper.parentNode) {
bbWrapper.parentNode.insertBefore(quickRepliesContainer, bbWrapper.nextSibling);
return;
}
// Попробуем найти div с классом "message-editor" или "editor-container"
const editorDiv = formElement.querySelector('.message-editor') || formElement.querySelector('.editor-container');
if (editorDiv && editorDiv.parentNode) {
editorDiv.parentNode.insertBefore(quickRepliesContainer, editorDiv.nextSibling);
return;
}
// Если ничего не найдено, попробуем добавить кнопки в конец формы
formElement.appendChild(quickRepliesContainer);
}
function findAndAddButtons() {
if (buttonsAdded) return; // Чтобы не добавлять кнопки многократно
const replyForm = document.querySelector('form[action*="/post-reply"]'); // Поиск формы по action
if (replyForm) {
console.log("Найдена форма ответа по action.");
addQuickReplyButtons(replyForm);
return;
}
const quickReplyForm = document.querySelector('.block.message.reply form'); // Форма быстрого ответа
if (quickReplyForm) {
console.log("Найдена форма быстрого ответа по классу.");
addQuickReplyButtons(quickReplyForm);
return;
}
const formWithTextarea = document.querySelector('form:has(textarea)'); // Любая форма с textarea
if (formWithTextarea) {
console.log("Найдена форма с textarea.");
addQuickReplyButtons(formWithTextarea);
return;
}
const blockWithEditor = document.querySelector('.block:has(.fr-wrapper)') || document.querySelector('.block:has(.bbWrapper)'); // Блок с редактором
if (blockWithEditor && blockWithEditor.querySelector('form')) {
console.log("Найден блок с редактором и формой.");
addQuickReplyButtons(blockWithEditor.querySelector('form'));
return;
}
console.log("Не удалось найти форму ответа на странице.");
}
function addGreeting() {
if (greetingShown) return;
greetingShown = true;
const greetingDiv = document.createElement('div');
greetingDiv.classList.add('forum-greeting');
greetingDiv.textContent = customGreeting;
document.body.insertBefore(greetingDiv, document.body.firstChild);
}
function openConfigModal() {
if (modalVisible) return;
modalVisible = true;
const modal = document.createElement('div');
modal.id = 'quickReplyConfigModal';
const title = document.createElement('h2');
title.textContent = 'Настройка быстрых ответов';
modal.appendChild(title);
const greetingLabel = document.createElement('label');
greetingLabel.textContent = 'Приветствие:';
greetingLabel.style.display = 'block';
greetingLabel.style.marginBottom = '10px';
modal.appendChild(greetingLabel);
const greetingInput = document.createElement('input');
greetingInput.type = 'text';
greetingInput.id = 'greetingInput';
greetingInput.value = customGreeting;
greetingInput.addEventListener('change', (event) => {
customGreeting = event.target.value;
});
modal.appendChild(greetingInput);
const addButton = document.createElement('button');
addButton.textContent = 'Добавить ответ';
addButton.classList.add('add-button');
addButton.addEventListener('click', () => {
quickRepliesConfig.push({ label: '', text: '', prefix: '', color: '', size: '', font: '' });
renderConfigList(modal);
});
modal.appendChild(addButton);
const configList = document.createElement('div');
configList.classList.add('config-list');
modal.appendChild(configList);
function renderConfigList(modalElement) {
configList.innerHTML = '';
quickRepliesConfig.forEach((config, index) => {
const configItem = document.createElement('div');
configItem.classList.add('config-item');
// Первая строка: Название
const row1 = document.createElement('div');
const labelLabel = document.createElement('label');
labelLabel.textContent = 'Название:';
const labelInput = document.createElement('input');
labelInput.type = 'text';
labelInput.value = config.label;
labelInput.placeholder = 'Название';
labelInput.addEventListener('change', (event) => {
quickRepliesConfig[index].label = event.target.value;
});
row1.appendChild(labelLabel);
row1.appendChild(labelInput);
configItem.appendChild(row1);
// Вторая строка: Содержимое
const row2 = document.createElement('div');
const textLabel = document.createElement('label');
textLabel.textContent = 'Содержимое:';
const textInput = document.createElement('input');
textInput.type = 'text';
textInput.value = config.text;
textInput.placeholder = 'Содержимое';
textInput.addEventListener('change', (event) => {
quickRepliesConfig[index].text = event.target.value;
});
row2.appendChild(textLabel);
row2.appendChild(textInput);
configItem.appendChild(row2);
// Третья строка: Цвет, Шрифт, Размер
const row3 = document.createElement('div');
row3.classList.add('style-controls');
// Цвет (выбор цвета)
const colorDiv = document.createElement('div');
const colorLabel = document.createElement('label');
colorLabel.textContent = 'Цвет:';
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.value = config.color || '#ffffff'; // Default to white
colorInput.addEventListener('change', (event) => {
quickRepliesConfig[index].color = event.target.value;
});
colorDiv.appendChild(colorLabel);
colorDiv.appendChild(colorInput);
row3.appendChild(colorDiv);
// Шрифт
const fontDiv = document.createElement('div');
const fontLabel = document.createElement('label');
fontLabel.textContent = 'Шрифт:';
const fontSelect = document.createElement('select');
const fonts = ['', 'Arial', 'Verdana', 'Times New Roman', 'Georgia'];
fonts.forEach(font => {
const option = document.createElement('option');
option.value = font;
option.textContent = font ? font : 'По умолчанию';
option.selected = config.font === font;
fontSelect.appendChild(option);
});
fontSelect.addEventListener('change', (event) => {
quickRepliesConfig[index].font = event.target.value;
});
fontDiv.appendChild(fontLabel);
fontDiv.appendChild(fontSelect);
row3.appendChild(fontDiv);
// Размер
const sizeDiv = document.createElement('div');
const sizeLabel = document.createElement('label');
sizeLabel.textContent = 'Размер:';
const sizeSelect = document.createElement('select');
const sizes = ['', '12px', '14px', '16px', '18px', '20px'];
sizes.forEach(size => {
const option = document.createElement('option');
option.value = size;
option.textContent = size ? size : 'По умолчанию';
option.selected = config.size === size;
sizeSelect.appendChild(option);
});
sizeSelect.addEventListener('change', (event) => {
quickRepliesConfig[index].size = event.target.value;
});
sizeDiv.appendChild(sizeLabel);
sizeDiv.appendChild(sizeSelect);
row3.appendChild(sizeDiv);
configItem.appendChild(row3);
// Четвертая строка: Удалить
const row4 = document.createElement('div');
row4.style.display = 'flex';
row4.style.alignItems = 'center';
row4.style.gap = '10px';
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Удалить';
deleteButton.classList.add('delete-button');
deleteButton.addEventListener('click', () => {
quickRepliesConfig.splice(index, 1);
renderConfigList(modalElement);
});
row4.appendChild(deleteButton);
configItem.appendChild(row4);
configList.appendChild(configItem);
});
}
renderConfigList(modal);
const modalButtons = document.createElement('div');
modalButtons.classList.add('modal-buttons');
const saveButton = document.createElement('button');
saveButton.textContent = 'Сохранить';
saveButton.addEventListener('click', () => {
saveConfig();
const existingContainer = document.querySelector('.quick-replies-container');
if (existingContainer) {
existingContainer.remove();
}
buttonsAdded = false; // Сбрасываем флаг, чтобы кнопки добавились заново
findAndAddButtons();
addGreeting();
modal.remove();
modalVisible = false;
});
modalButtons.appendChild(saveButton);
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Отмена';
cancelButton.addEventListener('click', () => {
modal.remove();
modalVisible = false;
quickRepliesConfig = GM_getValue(storageKey, [
{ label: 'Ответить', text: 'Благодарю за обращение!', prefix: '', color: '', size: '', font: '' }
]);
customGreeting = GM_getValue(greetingStorageKey, 'Добро пожаловать на форум BlackRussia!');
const existingContainer = document.querySelector('.quick-replies-container');
if (existingContainer) {
existingContainer.remove();
}
buttonsAdded = false; // Сбрасываем флаг при отмене
findAndAddButtons();
addGreeting();
});
modalButtons.appendChild(cancelButton);
modal.appendChild(modalButtons);
document.body.appendChild(modal);
}
GM_registerMenuCommand("Настроить быстрые ответы", openConfigModal);
window.addEventListener('load', () => {
addGreeting();
findAndAddButtons();
});
const observer = new MutationObserver(mutationsList => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
findAndAddButtons();
}
}
});
const targetNode = document.body;
const observerConfig = { childList: true, subtree: true };
observer.observe(targetNode, observerConfig);
})();