This commit is contained in:
2025-11-25 17:25:40 +08:00
parent f093791c14
commit 833d125f64
56 changed files with 9428 additions and 4334 deletions
Regular → Executable
View File
+41
View File
@@ -0,0 +1,41 @@
/* Увеличиваем специфичность для кнопок Quill */
.ql-toolbar .ql-picker-label,
.ql-toolbar button,
.ql-toolbar [role="button"] {
all: unset; /* Сбрасываем все стили Pico (background, border, padding и т.д.) */
display: inline-block; /* Восстанавливаем базовые стили Quill */
cursor: pointer;
padding: 0; /* Quill кнопки не имеют padding */
margin: 0;
border: none;
background: none;
color: inherit; /* Наследуем цвет от Quill */
font-size: inherit;
line-height: inherit;
text-decoration: none; /* Убираем подчёркивание, если это <a> */
}
/* Восстанавливаем hover/active стили Quill (если они сломались) */
.ql-toolbar button:hover,
.ql-toolbar [role="button"]:hover {
color: #06c; /* Пример из Quill snow theme; адаптируйте */
background: none; /* Без фона */
}
.ql-toolbar button.ql-active,
.ql-toolbar [role="button"].ql-active {
color: #06c;
background: none;
}
/* Для иконок (SVG в Quill) */
.ql-toolbar .ql-icon {
fill: currentColor; /* Убедимся, что иконки наследуют цвет */
}
/* Если Quill использует <span> для кнопок */
.ql-toolbar .ql-picker-item,
.ql-toolbar .ql-picker-options [role="button"] {
all: unset;
cursor: pointer;
}
+573 -644
View File
File diff suppressed because it is too large Load Diff
Regular → Executable
View File
+165 -41
View File
@@ -1,28 +1,69 @@
// assets/js/autosave.js
document.addEventListener('DOMContentLoaded', function() {
const contentTextarea = document.getElementById('content');
const titleInput = document.getElementById('title');
const statusSelect = document.getElementById('status');
// Ждем инициализации редактора
setTimeout(() => {
initializeAutoSave();
}, 1000);
});
function initializeAutoSave() {
console.log('AutoSave: Initializing...');
// Проверяем, что это редактирование существующей главы
const urlParams = new URLSearchParams(window.location.search);
const isEditMode = urlParams.has('id');
// Ищем активные редакторы Quill
const quillEditors = document.querySelectorAll('.ql-editor');
const textareas = document.querySelectorAll('textarea.writer-editor');
if (!contentTextarea || !isEditMode) {
console.log('Автосохранение отключено: создание новой главы');
if (quillEditors.length === 0 || textareas.length === 0) {
console.log('AutoSave: No Quill editors found, retrying in 1s...');
setTimeout(initializeAutoSave, 1000);
return;
}
console.log(`AutoSave: Found ${quillEditors.length} Quill editor(s)`);
// Для каждого редактора настраиваем автосейв
quillEditors.forEach((quillEditor, index) => {
const textarea = textareas[index];
if (!textarea) return;
setupAutoSaveForEditor(quillEditor, textarea, index);
});
}
function setupAutoSaveForEditor(quillEditor, textarea, editorIndex) {
let saveTimeout;
let isSaving = false;
let lastSavedContent = contentTextarea.value;
let lastSavedContent = textarea.value;
let changeCount = 0;
// Получаем экземпляр Quill из контейнера
const quillContainer = quillEditor.closest('.ql-container');
const quillInstance = quillContainer ? Quill.find(quillContainer) : null;
if (!quillInstance) {
console.error(`AutoSave: Could not find Quill instance for editor ${editorIndex}`);
return;
}
console.log(`AutoSave: Setting up for editor ${editorIndex}`);
function showSaveMessage(message) {
let messageEl = document.getElementById('autosave-message');
if (!messageEl) {
messageEl = document.createElement('div');
messageEl.id = 'autosave-message';
messageEl.style.cssText = 'position: fixed; top: 10px; right: 10px; padding: 8px 12px; background: #333; color: white; border-radius: 3px; z-index: 1000; font-size: 0.8rem;';
messageEl.style.cssText = `
position: fixed;
top: 70px;
right: 10px;
padding: 8px 12px;
background: #28a745;
color: white;
border-radius: 3px;
z-index: 10000;
font-size: 0.8rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
`;
document.body.appendChild(messageEl);
}
@@ -31,68 +72,151 @@ document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
messageEl.style.display = 'none';
}, 1500);
}, 2000);
}
function showError(message) {
let messageEl = document.getElementById('autosave-message');
if (!messageEl) {
messageEl = document.createElement('div');
messageEl.id = 'autosave-message';
messageEl.style.cssText = `
position: fixed;
top: 70px;
right: 10px;
padding: 8px 12px;
background: #dc3545;
color: white;
border-radius: 3px;
z-index: 10000;
font-size: 0.8rem;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
`;
document.body.appendChild(messageEl);
}
messageEl.textContent = message;
messageEl.style.background = '#dc3545';
messageEl.style.display = 'block';
setTimeout(() => {
messageEl.style.display = 'none';
messageEl.style.background = '#28a745';
}, 3000);
}
function autoSave() {
if (isSaving) return;
const currentContent = contentTextarea.value;
const currentTitle = titleInput ? titleInput.value : '';
const currentStatus = statusSelect ? statusSelect.value : 'draft';
if (currentContent === lastSavedContent) return;
if (isSaving) {
console.log('AutoSave: Already saving, skipping...');
return;
}
const currentContent = textarea.value;
// Проверяем, изменилось ли содержимое
if (currentContent === lastSavedContent) {
console.log('AutoSave: No changes detected');
return;
}
changeCount++;
console.log(`AutoSave: Changes detected (${changeCount}), saving...`);
isSaving = true;
// Показываем индикатор сохранения
showSaveMessage('Сохранение...');
const formData = new FormData();
formData.append('content', currentContent);
formData.append('title', currentTitle);
formData.append('status', currentStatus);
formData.append('autosave', 'true');
formData.append('csrf_token', document.querySelector('input[name="csrf_token"]').value);
fetch(window.location.href, {
// Добавляем title если есть
const titleInput = document.querySelector('input[name="title"]');
if (titleInput) {
formData.append('title', titleInput.value);
}
// Добавляем status если есть
const statusSelect = document.querySelector('select[name="status"]');
if (statusSelect) {
formData.append('status', statusSelect.value);
}
formData.append('autosave', 'true');
formData.append('csrf_token', document.querySelector('input[name="csrf_token"]')?.value || '');
const currentUrl = window.location.href;
fetch(currentUrl, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
lastSavedContent = currentContent;
showSaveMessage('Сохранено: ' + new Date().toLocaleTimeString());
showSaveMessage('Автосохранено: ' + new Date().toLocaleTimeString());
console.log('AutoSave: Successfully saved');
} else {
throw new Error(data.error || 'Unknown error');
}
})
.catch(error => {
console.error('Ошибка автосохранения:', error);
console.error('AutoSave Error:', error);
showError('Ошибка автосохранения: ' + error.message);
})
.finally(() => {
isSaving = false;
});
}
contentTextarea.addEventListener('input', function() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(autoSave, 2000);
// Слушаем изменения в Quill редакторе
quillInstance.on('text-change', function(delta, oldDelta, source) {
if (source === 'user') {
console.log('AutoSave: Text changed by user');
clearTimeout(saveTimeout);
saveTimeout = setTimeout(autoSave, 2000); // Сохраняем через 2 секунды после изменения
}
});
// Также слушаем изменения в title и status
const titleInput = document.querySelector('input[name="title"]');
if (titleInput) {
titleInput.addEventListener('input', function() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(autoSave, 2000);
});
}
const statusSelect = document.querySelector('select[name="status"]');
if (statusSelect) {
statusSelect.addEventListener('change', autoSave);
statusSelect.addEventListener('change', function() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(autoSave, 1000);
});
}
// Предупреждение при закрытии страницы с несохраненными изменениями
window.addEventListener('beforeunload', function(e) {
if (contentTextarea.value !== lastSavedContent) {
if (textarea.value !== lastSavedContent && !isSaving) {
e.preventDefault();
e.returnValue = 'У вас есть несохраненные изменения. Вы уверены, что хотите уйти?';
return e.returnValue;
}
});
//console.log('Автосохранение включено для редактирования главы');
});
// Периодическое сохранение каждые 30 секунд (на всякий случай)
setInterval(() => {
if (textarea.value !== lastSavedContent && !isSaving) {
console.log('AutoSave: Periodic save triggered');
autoSave();
}
}, 30000);
console.log(`AutoSave: Successfully set up for editor ${editorIndex}`);
}
+102
View File
@@ -0,0 +1,102 @@
// assets/js/editor.js
class WriterEditor {
constructor() {
this.editors = [];
this.init();
}
init() {
// Инициализируем редакторы для текстовых областей с классом .writer-editor
document.querySelectorAll('textarea.writer-editor').forEach(textarea => {
this.initEditor(textarea);
});
}
initEditor(textarea) {
// Создаем контейнер для Quill
const editorContainer = document.createElement('div');
editorContainer.className = 'writer-editor-container';
editorContainer.style.height = '500px';
editorContainer.style.marginBottom = '20px';
// Вставляем контейнер перед textarea
textarea.parentNode.insertBefore(editorContainer, textarea);
// Скрываем оригинальный textarea
textarea.style.display = 'none';
// Настройки Quill
const quill = new Quill(editorContainer, {
theme: 'snow',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'indent': '-1'}, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['link', 'image', 'video'],
['clean']
],
history: {
delay: 1000,
maxStack: 100,
userOnly: true
}
},
placeholder: 'Начните писать вашу главу...',
formats: [
'header', 'bold', 'italic', 'underline', 'strike',
'blockquote', 'code-block', 'list', 'bullet',
'script', 'indent', 'direction', 'size',
'color', 'background', 'font', 'align',
'link', 'image', 'video'
]
});
// Устанавливаем начальное содержимое
if (textarea.value) {
quill.root.innerHTML = textarea.value;
}
// Обновляем textarea при изменении содержимого
quill.on('text-change', () => {
textarea.value = quill.root.innerHTML;
});
// Сохраняем ссылку на редактор
this.editors.push({
quill: quill,
textarea: textarea
});
return quill;
}
// Метод для получения HTML содержимого
getContent(editorIndex = 0) {
if (this.editors[editorIndex]) {
return this.editors[editorIndex].quill.root.innerHTML;
}
return '';
}
// Метод для установки содержимого
setContent(content, editorIndex = 0) {
if (this.editors[editorIndex]) {
this.editors[editorIndex].quill.root.innerHTML = content;
}
}
}
// Инициализация редактора при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
window.writerEditor = new WriterEditor();
});
Regular → Executable
View File
-575
View File
@@ -1,575 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
const contentTextarea = document.getElementById('content');
const previewForm = document.getElementById('preview-form');
if (!contentTextarea) return;
let isFullscreen = false;
let originalStyles = {};
let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
function normalizeContent(text) {
// Заменяем множественные переносы на двойные
text = text.replace(/\n{3,}/g, '\n\n');
// Убираем пустые строки в начале и конце
return text.trim();
}
initEditor();
function initEditor() {
// Нормализуем контент при загрузке
if (contentTextarea.value) {
contentTextarea.value = normalizeContent(contentTextarea.value);
}
autoResize();
contentTextarea.addEventListener('input', autoResize);
contentTextarea.addEventListener('input', processDialogues);
contentTextarea.addEventListener('keydown', handleTab);
contentTextarea.addEventListener('input', updatePreviewContent);
updatePreviewContent();
addControlButtons();
// На мобильных устройствах добавляем обработчик изменения ориентации
if (isMobile) {
window.addEventListener('orientationchange', function() {
if (isFullscreen) {
setTimeout(adjustForMobileKeyboard, 300);
}
});
// Обработчик для виртуальной клавиатуры
window.addEventListener('resize', function() {
if (isFullscreen && isMobile) {
adjustForMobileKeyboard();
}
});
}
}
function autoResize() {
if (isFullscreen) return;
contentTextarea.style.height = 'auto';
contentTextarea.style.height = contentTextarea.scrollHeight + 'px';
}
function processDialogues() {
const lines = contentTextarea.value.split('\n');
let changed = false;
const processedLines = lines.map(line => {
if (line.trim().startsWith('- ') && line.trim().length > 2) {
const trimmed = line.trim();
const restOfLine = trimmed.substring(2);
if (/^[a-zA-Zа-яА-Я]/.test(restOfLine)) {
changed = true;
return line.replace(trimmed, `${restOfLine}`);
}
}
return line;
});
if (changed) {
const cursorPos = contentTextarea.selectionStart;
contentTextarea.value = processedLines.join('\n');
contentTextarea.setSelectionRange(cursorPos, cursorPos);
if (!isFullscreen) autoResize();
}
}
function handleTab(e) {
if (e.key === 'Tab') {
e.preventDefault();
const start = contentTextarea.selectionStart;
const end = contentTextarea.selectionEnd;
contentTextarea.value = contentTextarea.value.substring(0, start) + ' ' + contentTextarea.value.substring(end);
contentTextarea.selectionStart = contentTextarea.selectionEnd = start + 4;
if (!isFullscreen) autoResize();
}
}
function updatePreviewContent() {
if (previewForm) {
document.getElementById('preview-content').value = contentTextarea.value;
}
}
function adjustForMobileKeyboard() {
if (!isMobile || !isFullscreen) return;
// На мобильных устройствах уменьшаем высоту textarea, чтобы клавиатура не перекрывала контент
const viewportHeight = window.innerHeight;
const keyboardHeight = viewportHeight * 0.4; // Предполагаемая высота клавиатуры (40% экрана)
const availableHeight = viewportHeight - keyboardHeight - 80; // 80px для кнопок и отступов
contentTextarea.style.height = availableHeight + 'px';
contentTextarea.style.paddingBottom = '20px';
// Прокручиваем к курсору
setTimeout(() => {
const cursorPos = contentTextarea.selectionStart;
if (cursorPos > 0) {
scrollToCursor();
}
}, 100);
}
function scrollToCursor() {
const textarea = contentTextarea;
const cursorPos = textarea.selectionStart;
// Создаем временный элемент для измерения позиции курсора
const tempDiv = document.createElement('div');
tempDiv.style.cssText = `
position: absolute;
top: -1000px;
left: -1000px;
width: ${textarea.clientWidth}px;
padding: ${textarea.style.padding};
font: ${getComputedStyle(textarea).font};
line-height: ${textarea.style.lineHeight};
white-space: pre-wrap;
word-wrap: break-word;
visibility: hidden;
`;
const textBeforeCursor = textarea.value.substring(0, cursorPos);
tempDiv.textContent = textBeforeCursor;
document.body.appendChild(tempDiv);
const textHeight = tempDiv.offsetHeight;
document.body.removeChild(tempDiv);
// Прокручиваем так, чтобы курсор был виден
const lineHeight = parseInt(getComputedStyle(textarea).lineHeight) || 24;
const visibleHeight = textarea.clientHeight;
const cursorLine = Math.floor(textHeight / lineHeight);
const visibleLines = Math.floor(visibleHeight / lineHeight);
const targetScroll = Math.max(0, (cursorLine - Math.floor(visibleLines / 3)) * lineHeight);
textarea.scrollTop = targetScroll;
}
function addControlButtons() {
const container = contentTextarea.parentElement;
const controlsContainer = document.createElement('div');
controlsContainer.className = 'editor-controls';
const fullscreenBtn = createButton('🔲', 'Полноэкранный режим', toggleFullscreen);
const helpBtn = createButton('❓', 'Справка по Markdown', showHelp);
controlsContainer.appendChild(fullscreenBtn);
controlsContainer.appendChild(helpBtn);
container.insertBefore(controlsContainer, contentTextarea);
function toggleFullscreen() {
if (!isFullscreen) {
enterFullscreen();
} else {
exitFullscreen();
}
}
function enterFullscreen() {
originalStyles = {
position: contentTextarea.style.position,
top: contentTextarea.style.top,
left: contentTextarea.style.left,
width: contentTextarea.style.width,
height: contentTextarea.style.height,
zIndex: contentTextarea.style.zIndex,
backgroundColor: contentTextarea.style.backgroundColor,
border: contentTextarea.style.border,
borderRadius: contentTextarea.style.borderRadius,
fontSize: contentTextarea.style.fontSize,
padding: contentTextarea.style.padding,
margin: contentTextarea.style.margin
};
if (isMobile) {
// Для мобильных - адаптивный режим с учетом клавиатуры
const viewportHeight = window.innerHeight;
const availableHeight = viewportHeight - 100; // Оставляем место для кнопок
Object.assign(contentTextarea.style, {
position: 'fixed',
top: '50px',
left: '0',
width: '100vw',
height: availableHeight + 'px',
zIndex: '9998',
backgroundColor: 'white',
border: '2px solid #007bff',
borderRadius: '0',
fontSize: '18px',
padding: '15px',
margin: '0',
boxSizing: 'border-box',
resize: 'none'
});
// На мобильных устройствах фокусируем textarea сразу
setTimeout(() => {
contentTextarea.focus();
}, 300);
} else {
// Для ПК - классический полноэкранный режим
Object.assign(contentTextarea.style, {
position: 'fixed',
top: '5vh',
left: '5vw',
width: '90vw',
height: '90vh',
zIndex: '9998',
backgroundColor: 'white',
border: '2px solid #007bff',
borderRadius: '8px',
fontSize: '16px',
padding: '20px',
margin: '0',
boxSizing: 'border-box',
resize: 'none'
});
}
controlsContainer.style.display = 'none';
createFullscreenControls();
isFullscreen = true;
document.body.style.overflow = 'hidden';
}
function exitFullscreen() {
Object.assign(contentTextarea.style, originalStyles);
controlsContainer.style.display = 'flex';
removeFullscreenControls();
isFullscreen = false;
document.body.style.overflow = '';
autoResize();
}
function createFullscreenControls() {
const fullscreenControls = document.createElement('div');
fullscreenControls.id = 'fullscreen-controls';
const exitBtn = createButton('❌', 'Выйти из полноэкранного режима', exitFullscreen);
const helpBtnFullscreen = createButton('❓', 'Справка по Markdown', showHelp);
// Для мобильных увеличиваем кнопки и добавляем отступы
const buttonSize = isMobile ? '60px' : '50px';
const fontSize = isMobile ? '24px' : '20px';
const topPosition = isMobile ? '10px' : '15px';
[exitBtn, helpBtnFullscreen].forEach(btn => {
btn.style.cssText = `
width: ${buttonSize};
height: ${buttonSize};
border-radius: 50%;
border: 1px solid #ddd;
background: white;
cursor: pointer;
font-size: ${fontSize};
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
transition: all 0.3s ease;
color: #333333;
touch-action: manipulation;
`;
});
fullscreenControls.appendChild(helpBtnFullscreen);
fullscreenControls.appendChild(exitBtn);
fullscreenControls.style.cssText = `
position: fixed;
top: ${topPosition};
right: 10px;
z-index: 9999;
display: flex;
gap: 5px;
`;
document.body.appendChild(fullscreenControls);
// Предотвращаем всплытие событий от кнопок к textarea
fullscreenControls.addEventListener('touchstart', function(e) {
e.stopPropagation();
});
fullscreenControls.addEventListener('touchend', function(e) {
e.stopPropagation();
});
}
function removeFullscreenControls() {
const fullscreenControls = document.getElementById('fullscreen-controls');
if (fullscreenControls) {
fullscreenControls.remove();
}
}
// Выход по ESC
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && isFullscreen) {
exitFullscreen();
}
});
// На мобильных устройствах добавляем обработчик для выхода по тапу вне textarea
if (isMobile) {
document.addEventListener('touchstart', function(e) {
if (isFullscreen && !contentTextarea.contains(e.target) &&
!document.getElementById('fullscreen-controls')?.contains(e.target)) {
exitFullscreen();
}
});
}
// Обработчик фокуса для мобильных устройств
if (isMobile) {
contentTextarea.addEventListener('focus', function() {
if (isFullscreen) {
setTimeout(adjustForMobileKeyboard, 100);
}
});
}
}
function createButton(icon, title, onClick) {
const button = document.createElement('button');
button.innerHTML = icon;
button.title = title;
button.type = 'button';
const buttonSize = isMobile ? '50px' : '40px';
const fontSize = isMobile ? '20px' : '16px';
button.style.cssText = `
width: ${buttonSize};
height: ${buttonSize};
border-radius: 50%;
border: 1px solid #ddd;
background: white;
cursor: pointer;
font-size: ${fontSize};
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
transition: all 0.3s ease;
color: #333333;
touch-action: manipulation;
`;
button.addEventListener('mouseenter', function() {
this.style.transform = 'scale(1.1)';
this.style.backgroundColor = '#f8f9fa';
this.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = 'scale(1)';
this.style.backgroundColor = 'white';
this.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
});
button.addEventListener('click', onClick);
// Для мобильных устройств
button.addEventListener('touchstart', function(e) {
e.stopPropagation();
this.style.transform = 'scale(1.1)';
this.style.backgroundColor = '#f8f9fa';
this.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
});
button.addEventListener('touchend', function(e) {
e.stopPropagation();
this.style.transform = 'scale(1)';
this.style.backgroundColor = 'white';
this.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
onClick();
});
return button;
}
function showHelp() {
const helpContent = `
<div style="font-family: system-ui, sans-serif; line-height: 1.6; color: #333;">
<h1 style="color: #007bff; margin-top: 0; border-bottom: 2px solid #007bff; padding-bottom: 10px;">Справка по Markdown</h1>
<div style="margin-bottom: 20px;">
<h2 style="color: #555;">Основное форматирование</h2>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #007bff;">
<p><strong>Жирный текст:</strong> **текст** или __текст__</p>
<p><em>Наклонный текст:</em> *текст* или _текст_</p>
<p><u>Подчеркнутый текст:</u> &lt;u&gt;текст&lt;/u&gt;</p>
<p><del>Зачеркнутый текст:</del> ~~текст~~</p>
</div>
</div>
<div style="margin-bottom: 20px;">
<h2 style="color: #555;">Заголовки</h2>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #28a745;">
<h1 style="margin: 10px 0; font-size: 1.5em;">Заголовок 1 (# Заголовок)</h1>
<h2 style="margin: 10px 0; font-size: 1.3em;">Заголовок 2 (## Заголовок)</h2>
<h3 style="margin: 10px 0; font-size: 1.1em;">Заголовок 3 (### Заголовок)</h3>
</div>
</div>
<div style="margin-bottom: 20px;">
<h2 style="color: #555;">Цитаты</h2>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #ffc107;">
<blockquote style="margin: 0; padding-left: 15px; border-left: 3px solid #ddd; color: #666;">
> Это цитата
</blockquote>
<blockquote style="margin: 10px 0 0 20px; padding-left: 15px; border-left: 3px solid #ddd; color: #666;">
> > Вложенная цитата
</blockquote>
</div>
</div>
<div style="margin-bottom: 20px;">
<h2 style="color: #555;">Диалоги</h2>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #dc3545;">
<p><strong>Автоматическое преобразование:</strong></p>
<p><code>- Привет!</code> → <em>— Привет!</em></p>
<p style="font-size: 0.9em; color: #666; margin-top: 5px;">
Дефис в начале строки автоматически заменяется на тире с пробелом
</p>
</div>
</div>
<div style="margin-bottom: 20px;">
<h2 style="color: #555;">Списки</h2>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #6f42c1;">
<p><strong>Маркированный список:</strong></p>
<ul style="margin: 10px 0; padding-left: 20px;">
<li>- Элемент списка</li>
<li>- Другой элемент</li>
</ul>
<p><strong>Нумерованный список:</strong></p>
<ol style="margin: 10px 0; padding-left: 20px;">
<li>1. Первый элемент</li>
<li>2. Второй элемент</li>
</ol>
</div>
</div>
<div style="margin-bottom: 20px;">
<h2 style="color: #555;">Код</h2>
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; border-left: 4px solid #fd7e14;">
<p><strong>Код в строке:</strong></p>
<p><code>\`код в строке\`</code></p>
<p><strong>Блок кода:</strong></p>
<pre style="background: #e9ecef; padding: 10px; border-radius: 3px; overflow-x: auto; margin: 10px 0;">
\`\`\`
блок кода
многострочный
\`\`\`</pre>
</div>
</div>
<div style="background: #e7f3ff; padding: 15px; border-radius: 5px; border-left: 4px solid #007bff; margin-top: 20px;">
<p style="margin: 0; font-size: 0.9em;"><strong>💡 Подсказка:</strong> Используйте кнопку "👁️ Предпросмотр" чтобы увидеть как будет выглядеть готовый текст!</p>
</div>
</div>
`;
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border: 2px solid #007bff;
border-radius: 10px;
padding: 25px;
z-index: 10000;
width: 90%;
max-width: 700px;
max-height: 85vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
`;
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.title = 'Закрыть справку';
closeBtn.style.cssText = `
position: absolute;
top: 15px;
right: 15px;
background: #ff4444;
color: white;
border: none;
font-size: 18px;
cursor: pointer;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s ease;
`;
closeBtn.addEventListener('mouseenter', function() {
this.style.background = '#cc0000';
});
closeBtn.addEventListener('mouseleave', function() {
this.style.background = '#ff4444';
});
closeBtn.addEventListener('click', function() {
modal.remove();
overlay.remove();
});
modal.innerHTML = helpContent;
modal.appendChild(closeBtn);
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.5);
z-index: 9999;
`;
overlay.addEventListener('click', function() {
modal.remove();
overlay.remove();
});
document.body.appendChild(overlay);
document.body.appendChild(modal);
const closeHandler = function(e) {
if (e.key === 'Escape') {
modal.remove();
overlay.remove();
document.removeEventListener('keydown', closeHandler);
}
};
document.addEventListener('keydown', closeHandler);
}
});