Современная форма добавления сообщений на форуме uCoz плюс BB редактор

Юрий Герук 2025-12-27 97
Современная форма добавления сообщений на форуме uCoz плюс BB редактор

Введение

Форум uCoz живучий как старая добрая классика, но редактор сообщений часто выглядит так, будто его не трогали со времен динозавров интернета. Это не критично, пока пользователи терпят. Но как только они видят серые кнопки, табличные отступы и визуальный хаос, мотивация писать сообщения падает.

Ниже готовое решение, где мы кастомизируем форму добавления сообщений форума uCoz только стилями и скриптом: делаем карточку, иконки вместо стандартных кнопок, аккуратную загрузку файлов и фикс повторных нажатий.

Что именно улучшает решение

После установки у вас будет:

  • Аккуратная карточка редактора с нормальными отступами, рамкой и легкой тенью.
  • Панель BB инструментов в виде иконок Bootstrap Icons вместо стандартных инпутов.
  • Оригинальные кнопки спрятаны, но не удалены, логика uCoz сохраняется.
  • Исправлено поведение, когда кнопка срабатывает один раз, а потом перестает.
  • Поле ввода нормальной высоты, плюс автоувеличение по мере ввода текста.
  • Кастомная кнопка выбора файла, рядом показывается имя выбранного файла.
  • Нижние кнопки отправки, предпросмотра и сброса выглядят современно и ровно.

Где это работает

Решение подходит для:

  • Страницы добавления сообщения на форуме.
  • Страницы ответа в теме.
  • Формы редактирования сообщений, если там используется такая же разметка.

Нюанс uCoz в том, что разметка формы часто завязана на конкретные id блоков: frM53, frM58 и так далее. Это нормально, просто имейте в виду:

  • Если у вас другой шаблон, id могут отличаться.
  • В таком случае нужно поменять только селекторы в CSS и места поиска элементов в JS.
  • Логика кода не меняется.

Установка в uCoz

  • Откройте панель управления сайтом.
  • Перейдите в управление дизайном.
  • Выберите модуль форум
  • Найдите шаблон формы добавления сообщения.
  • Вставьте код целиком в шаблон, удобнее ближе к низу, чтобы форма уже была в DOM.
  • Сохраните изменения и обновите страницу форума.

Плюс такого подхода:

  • Код не разъезжается по сайту, потому что привязан к конкретным id.
  • Никаких случайных украшений других форм.

Полный код решения

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">

<style>
/* КАРТОЧКА РЕДАКТОРА */
#frM53{
 display:block !important;
 background:#fff !important;
 border:1px solid #e2e8f0 !important;
 border-radius:12px !important;
 box-shadow:0 4px 15px rgba(0,0,0,0.05) !important;
 max-width:1000px;
 margin:10px auto !important;
 overflow:hidden !important;
 font-family:-apple-system, system-ui, sans-serif !important;
}
#frM53 tbody, #frM53 tr, #frM53 td{
 display:block !important;
 width:100% !important;
 border:none !important;
 box-sizing:border-box !important;
}

/* СКРЫВАЕМ ХЛАМ И ЛЕВУЮ КОЛОНКУ */
#frM57{ display:none !important; }
.gTopCornerRight{ display:none !important; }

/* Если хочешь смайлы полностью скрыть оставь как есть. Если нет, убери две строки ниже */
.smilesPart{ display:none !important; }
.smiles-grid{ display:none !important; }

/* ШАПКА */
#frM55.gTableTop{
 padding:12px 20px !important;
 background:#f8fafc !important;
 border-bottom:1px solid #e2e8f0 !important;
 font-weight:800 !important;
 color:#1e293b !important;
}

/* ПАНЕЛЬ ИНСТРУМЕНТОВ */
#frM58{ padding:15px !important; }
#frM58 > div:first-of-type{
 display:flex !important;
 flex-wrap:wrap !important;
 gap:6px !important;
 background:#f8fafc !important;
 padding:10px !important;
 border-radius:8px 8px 0 0 !important;
 border:1px solid #cbd5e1 !important;
 border-bottom:none !important;
 align-items:center !important;
}

/* Селекты */
#frM58 select.codeButtons{
 height:32px !important;
 border:1px solid #cbd5e1 !important;
 border-radius:6px !important;
 background:#fff !important;
 color:#475569 !important;
 font-weight:700 !important;
 padding:0 10px !important;
}

/* Оригинальные input кнопки прячем, но не удаляем */
.ucf-orig-btn{
 position:absolute !important;
 left:-99999px !important;
 width:1px !important;
 height:1px !important;
 opacity:0 !important;
 pointer-events:none !important;
}

/* Наши красивые кнопки */
.ucf-btn{
 display:inline-flex !important;
 align-items:center !important;
 justify-content:center !important;
 height:32px !important;
 min-width:32px !important;
 padding:0 9px !important;
 border:1px solid #cbd5e1 !important;
 border-radius:6px !important;
 background:#fff !important;
 color:#475569 !important;
 cursor:pointer !important;
 transition:transform .14s ease, background .14s ease, border-color .14s ease, box-shadow .14s ease;
}
.ucf-btn:hover{
 background:#f1f5f9 !important;
 border-color:#3b82f6 !important;
 transform:translateY(-1px);
 box-shadow:0 10px 22px rgba(2,6,23,.10);
}
.ucf-btn:active{
 transform:translateY(0);
 box-shadow:0 6px 14px rgba(2,6,23,.10);
}
.ucf-btn i{
 font-size:16px;
 line-height:1;
}

/* ПОЛЕ ВВОДА */
#message.postTextFl{
 display:block !important;
 width:100% !important;
 min-height:250px !important;
 border:1px solid #cbd5e1 !important;
 border-radius:0 0 8px 8px !important;
 padding:15px !important;
 font-size:15px !important;
 outline:none !important;
}

/* ЗАГРУЗКА ФАЙЛОВ */
#imblock1{
 display:flex !important;
 align-items:center !important;
 gap:8px !important;
 padding:10px 0 !important;
 justify-content:flex-start !important;
 flex-wrap:nowrap !important;
}
#fln1{ display:none !important; }

#iplus button{
 background:#10b981 !important;
 color:#fff !important;
 border:none !important;
 width:34px !important;
 height:34px !important;
 border-radius:8px !important;
 font-size:18px !important;
 cursor:pointer !important;
 display:flex !important;
 align-items:center !important;
 justify-content:center !important;
 flex-shrink:0 !important;
}

.custom-file-btn{
 background:#fff !important;
 border:1px solid #cbd5e1 !important;
 border-radius:8px !important;
 padding:0 15px !important;
 height:34px !important;
 font-size:13px !important;
 font-weight:700 !important;
 color:#334155 !important;
 cursor:pointer !important;
 display:inline-flex !important;
 align-items:center !important;
 flex-shrink:0 !important;
 white-space:nowrap !important;
}
.file-name-text{
 font-size:13px;
 color:#94a3b8;
 font-style:italic;
 white-space:nowrap;
}

/* НИЖНИЕ КНОПКИ */
#frM60{
 display:flex !important;
 justify-content:flex-end !important;
 align-items:center !important;
 gap:12px !important;
 padding:20px !important;
 background:#f8fafc !important;
 border-top:1px solid #e2e8f0 !important;
}
.postSubmit, .postPreview, .postReset{
 display:inline-flex !important;
 align-items:center !important;
 justify-content:center !important;
 height:40px !important;
 min-width:120px !important;
 padding:0 20px !important;
 border-radius:9px !important;
 font-size:14px !important;
 font-weight:700 !important;
 cursor:pointer !important;
 border:none !important;
}
.postSubmit{ background:#2563eb !important; color:#fff !important; }
.postPreview{ background:#fff !important; border:1px solid #cbd5e1 !important; color:#475569 !important; }
.postReset{ background:#f1f5f9 !important; color:#94a3b8 !important; }
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
 const toolbar = document.querySelector('#frM58 > div:first-of-type');
 const ta = document.getElementById('message');
 if (!toolbar || !ta) return;

 // Храним каретку, чтобы повторные нажатия работали всегда
 const sel = { start: 0, end: 0 };
 function saveSel(){
 try { sel.start = ta.selectionStart; sel.end = ta.selectionEnd; } catch(e){}
 }
 function restoreSel(){
 try { ta.setSelectionRange(sel.start, sel.end); } catch(e){}
 }
 ta.addEventListener('keyup', saveSel);
 ta.addEventListener('mouseup', saveSel);
 ta.addEventListener('focus', saveSel);

 function iconByInput(inp){
 const id = (inp.id || '').toLowerCase();
 const val = (inp.value || '').trim().toLowerCase();
 const title = (inp.title || '').trim().toLowerCase();

 // Базовые
 if(id === 'b' || val === 'b') return {cls:'bi-type-bold', tip:'Жирный'};
 if(id === 'i' || val === 'i') return {cls:'bi-type-italic', tip:'Курсив'};
 if(id === 'u' || val === 'u') return {cls:'bi-type-underline', tip:'Подчеркнутый'};

 // Дополнительные
 if(val === 's') return {cls:'bi-type-strikethrough', tip:'Зачеркнутый'};
 if(val === 'hr') return {cls:'bi-dash-lg', tip:'Линия'};
 if(val === 'sup') return {cls:'bi-superscript', tip:'Верхний индекс'};
 if(val === 'sub') return {cls:'bi-subscript', tip:'Нижний индекс'};

 // Медиа
 if(val === 'video') return {cls:'bi-play-btn', tip:'Видео'};
 if(val === 'audio') return {cls:'bi-music-note-beamed', tip:'Аудио'};

 // Ссылки и картинки
 if(id === 'url' || val === 'http://') return {cls:'bi-link-45deg', tip:'Ссылка'};
 if(id === 'email' || val === '@') return {cls:'bi-envelope', tip:'Email'};
 if(id === 'img' || val === 'img') return {cls:'bi-image', tip:'Картинка'};

 // Блоки
 if(id === 'quote' || val === 'quote') return {cls:'bi-quote', tip:'Цитата'};
 if(id === 'codes' || val === 'code') return {cls:'bi-code-slash', tip:'Код'};
 if(id === 'spoiler' || val === 'spoiler') return {cls:'bi-box-arrow-in-down', tip:'Спойлер'};
 if(id === 'hide' || val === 'hide') return {cls:'bi-eye-slash', tip:'Хайд'};
 if(id === 'list' || val === 'list') return {cls:'bi-list-ul', tip:'Список'};

 // Выравнивание
 if(id === 'cdl' || title.includes('left')) return {cls:'bi-text-left', tip:'Влево'};
 if(id === 'cdc' || title.includes('center')) return {cls:'bi-text-center', tip:'Центр'};
 if(id === 'cdr' || title.includes('right')) return {cls:'bi-text-right', tip:'Вправо'};

 // Те самые две “зеленые”
 if(inp.classList.contains('codeCloseAll') || val === '/') return {cls:'bi-x-lg', tip:'Закрыть все'};
 if(val === '+' || title.includes('all codes')) return {cls:'bi-grid-3x3-gap', tip:'Все коды'};

 // Прочее
 if(title.includes('smiles')) return {cls:'bi-emoji-smile', tip:'Смайлы'};
 if(title.includes('keyboard') || val === '.::.') return {cls:'bi-keyboard', tip:'Клавиатура'};

 return null;
 }

 function invokeOriginal(inp){
 ta.focus();
 restoreSel();

 // Надежно вызываем оригинальный обработчик
 if (typeof inp.onclick === 'function') {
 inp.onclick.call(inp);
 } else {
 inp.click();
 }

 ta.focus();
 saveSel();
 }

 function upgradeInputs(){
 toolbar.querySelectorAll('input.codeButtons[type="button"]').forEach(inp => {
 if (inp.dataset.ucfUpgraded === '1') return;

 const meta = iconByInput(inp);
 if (!meta) return;

 inp.dataset.ucfUpgraded = '1';

 const btn = document.createElement('button');
 btn.type = 'button';
 btn.className = 'ucf-btn';
 btn.title = meta.tip;
 btn.innerHTML = '<i class="bi ' + meta.cls + '"></i>';

 // Фикс повторных нажатий: mousedown не уводит фокус
 btn.addEventListener('mousedown', function(e){
 e.preventDefault();
 e.stopPropagation();
 invokeOriginal(inp);
 });

 inp.classList.add('ucf-orig-btn');
 inp.parentNode.insertBefore(btn, inp);
 });
 }

 // Подхватываем все существующие кнопки и превращаем в иконки
 upgradeInputs();

 // Кастомный выбор файла
 const fileInput = document.getElementById('fln1');
 const imblock = document.getElementById('imblock1');
 if(fileInput && imblock && !imblock.querySelector('.custom-file-btn')){
 const btn = document.createElement('button');
 btn.type = 'button';
 btn.className = 'custom-file-btn';
 btn.innerHTML = '<i class="bi bi-paperclip" style="margin-right:8px"></i>Выберите файл';

 btn.addEventListener('click', function(e){
 e.preventDefault();
 fileInput.click();
 });

 const txt = document.createElement('span');
 txt.className = 'file-name-text';
 txt.textContent = ' Файл не выбран';

 imblock.appendChild(btn);
 imblock.appendChild(txt);

 fileInput.addEventListener('change', function(){
 txt.textContent = ' ' + (fileInput.files && fileInput.files[0] ? fileInput.files[0].name : 'Файл не выбран');
 });
 }

 // Автовысота
 ta.addEventListener('input', function() {
 this.style.height = 'auto';
 this.style.height = this.scrollHeight + 'px';
 });
});
</script>

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

Визуальная часть

  • Табличная разметка uCoz принудительно переводится в блочную, чтобы:
    • адаптивность стала нормальной;
    • отступы стали предсказуемыми;
    • карточка не разваливалась на мобильных.
  • Шапка и низ формы оформляются отдельными блоками:
    • сверху заголовок;
    • внизу кнопки действий.
  • Смайлы отключены:
    • если хотите вернуть, удалите строки со скрытием .smilesPart и .smiles-grid.

Иконки вместо стандартных BB кнопок

  • Оригинальные input кнопки:
    • не удаляются;
    • прячутся классом .ucf-orig-btn.
  • Рядом создаются новые кнопки button:
    • с иконками Bootstrap Icons;
    • с подсказками через title.
  • Нажатие по новой кнопке:
    • вызывает оригинальную кнопку;
    • значит uCoz вставляет BB коды привычным способом.

Фикс повторных нажатий

Проблема обычно в том, что при клике по кнопке:

  • фокус уходит с textarea;
  • каретка теряется;
  • второе нажатие не срабатывает как надо.

Здесь это решено так:

  • Позиция каретки сохраняется:
    • при вводе;
    • при кликах;
    • при фокусе.
  • При нажатии на кнопку:
    • фокус возвращается в textarea;
    • восстанавливается каретка;
    • вызывается оригинальная кнопка;
    • снова сохраняется каретка.

Дополнительно используется mousedown плюс preventDefault, чтобы кнопка не отбирала фокус.

Кастомная загрузка файла

  • Оригинальный input type="file" скрывается.
  • Добавляется кнопка:
    • “Выберите файл”.
  • Рядом добавляется текст:
    • “Файл не выбран”.
  • При выборе файла:
    • текст меняется на имя выбранного файла.

Что можно быстро настроить под себя

Если хотите подогнать под свой дизайн:

  • Изменить ширину карточки:
    • max-width:1000px.
  • Изменить высоту поля:
    • min-height:250px.
  • Вернуть смайлы:
    • удалить скрытие .smilesPart и .smiles-grid.
  • Если у вас другие id:
    • поменять #frM53, #frM58, #frM60 и message на ваши.

Заключение

Это решение не ломает uCoz и не переписывает его механику. Оно делает ровно то, что надо:

  • оставляет старую логику вставки BB кодов,
  • убирает визуальный стыд,
  • добавляет привычный современный интерфейс,
  • чинит повторные нажатия и делает форму приятной.

uCoz по классике остается собой, просто в нормальной одежде.

Оцените полезность материала!

Лицензия: CC BY-SA 4.0

Автор: Юрий Герук

Похожие материалы:

Комментарии