Files
pyisu/veditor.md
2026-03-13 14:39:43 +08:00

36 KiB
Raw Permalink Blame History

Руководство по работе с Vditor

Содержание

  1. Введение
  2. Инициализация редактора
  3. Работа с API редактора
  4. Сохранение и загрузка контента
  5. Работа с HTML и Markdown
  6. Отображение контента без редактора
  7. Хранение в базе данных
  8. Примеры использования

Введение

Vditor - это мощный WYSIWYG Markdown редактор с поддержкой LaTeX формул через KaTeX. Редактор предоставляет богатый API для работы с контентом.

Инициализация редактора

Базовый пример инициализации

let vditorInstance = null;
function initVditor() {
        // Загрузка скрипта Vditor если не загружен
    if (typeof Vditor === 'undefined') {
            loadVditor().then(() => initVditorEditor());
    } else {
            initVditorEditor();
    }
}
function loadVditor() {
        return new Promise((resolve, reject) => {
            if (typeof Vditor !== 'undefined') {
                resolve();
            return;
        }

        const script = document.createElement('script');
        script.src = 'https://unpkg.com/vditor@3.10.4/dist/index.min.js';
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
    });
}
function initVditorEditor(initialContent = '') {
        vditorInstance = new Vditor('vditor-container', {
            height: 500,
        placeholder: 'Начните писать здесь...',
        theme: 'classic',
        icon: 'material',
        typewriterMode: true,
        lang: 'ru_RU',
        toolbar: [
                'emoji', 'headings', 'bold', 'italic', 'strike', 'link', '|',
            'list', 'ordered-list', 'check', 'outdent', 'indent', '|',
            'quote', 'line', 'code', 'inline-code', 'insert-before', 'insert-after', '|',
            'upload', 'table', '|',
            'undo', 'redo', '|',
            'edit-mode', 'both', 'preview', 'fullscreen', 'outline',
            'code-theme', 'content-theme', 'export', 'help'
        ],
        preview: {
                delay: 500,
            hljs: {
                    enable: true,
                style: 'github',
                lineNumber: true
            },
            math: {
                    engine: 'KaTeX',
                inlineDigit: true
            },
            markdown: {
                    toc: true,
                mark: true,
                footnotes: true,
                autoSpace: true
            }
        },
        value: initialContent, // Начальное содержимое
        cache: {
                enable: false, // Отключаем локальное кэширование
        },
        after: () => {
                console.log('Vditor инициализирован');
        }
    });
}

Русская локализация

// Русская локализация для подсказок
const ruLang = {
        hint: {
            emoji: 'Эмодзи',
        headings: 'Заголовки',
        bold: 'Жирный',
        italic: 'Курсив',
        strike: 'Зачеркнутый',
        link: 'Ссылка',
        'list': 'Маркированный список',
        'ordered-list': 'Нумерованный список',
        check: 'Список задач',
        outdent: 'Уменьшить отступ',
        indent: 'Увеличить отступ',
        quote: 'Цитата',
        line: 'Разделитель',
        code: 'Блок кода',
        'inline-code': 'Встроенный код',
        'insert-after': 'Вставить строку после',
        'insert-before': 'Вставить строку перед',
        upload: 'Загрузить',
        table: 'Таблица',
        undo: 'Отменить',
        redo: 'Повторить',
        'edit-mode': 'Режим редактирования',
        both: 'Редактирование и просмотр',
        preview: 'Только просмотр',
        fullscreen: 'Полный экран',
        outline: 'Оглавление',
        'code-theme': 'Тема кода',
        'content-theme': 'Тема контента',
        export: 'Экспорт',
        help: 'Помощь'
    },
    toolbar: {
            more: 'Еще'
    }
};
// Использование в конфигурации
vditorInstance = new Vditor('vditor-container', {
        // ... другие настройки
    lang: 'ru_RU',
    // ... остальные настройки
});

Работа с API редактора

Основные методы API

// Получение текущего содержимого редактора
function getEditorContent() {
        if (!vditorInstance) return '';
    return vditorInstance.getValue();
}
// Установка содержимого редактора
function setEditorContent(content) {
        if (!vditorInstance) return;
    vditorInstance.setValue(content);
}
// Получение HTML версии контента
function getEditorHTML() {
        if (!vditorInstance) return '';
    return vditorInstance.getHTML();
}
// Получение чистого Markdown (без HTML тегов)
function getEditorMarkdown() {
        if (!vditorInstance) return '';
    return vditorInstance.getValue();
}
// Переключение режима предпросмотра
function togglePreview() {
        if (!vditorInstance) return;
    vditorInstance.setPreviewMode(vditorInstance.vditor.preview.element.style.display === 'none');
}
// Получение выделенного текста
function getSelectedText() {
        if (!vditorInstance) return '';
    return vditorInstance.getSelection();
}
// Вставка текста в текущую позицию курсора
function insertTextAtCursor(text) {
        if (!vditorInstance) return;
    vditorInstance.insertValue(text);
}
// Очистка редактора
function clearEditor() {
        if (!vditorInstance) return;
    vditorInstance.setValue('');
}
// Получение информации о редакторе
function getEditorInfo() {
        if (!vditorInstance) return null;
    return {
            wordCount: vditorInstance.vditor.undo.undo.length,
        isPreview: vditorInstance.vditor.preview.element.style.display !== 'none',
        isFullscreen: document.fullscreenElement !== null,
        theme: vditorInstance.vditor.options.theme
    };
}

Обработка событий

// Добавление обработчиков событий
function setupEditorEvents() {
        if (!vditorInstance) return;

    // Событие изменения содержимого
    vditorInstance.vditor.undo.addListener('stack', (stack) => {
            console.log('Контент изменен:', stack);
        // Автосохранение или валидация
        autoSaveContent();
    });

    // Событие загрузки файлов
    vditorInstance.vditor.upload.element.addEventListener('change', (e) => {
            console.log('Файлы выбраны:', e.target.files);
    });

    // Событие переключения режима
    vditorInstance.vditor.preview.element.addEventListener('click', () => {
            console.log('Режим предпросмотра изменен');
    });
}
// Автосохранение контента
let autoSaveTimeout = null;
function autoSaveContent() {
        if (autoSaveTimeout) {
            clearTimeout(autoSaveTimeout);
    }

    autoSaveTimeout = setTimeout(() => {
            const content = getEditorContent();
        if (content.trim()) {
                saveToLocalStorage(content);
            console.log('Автосохранение выполнено');
        }
    }, 2000); // Сохраняем каждые 2 секунды после последнего изменения
}
function saveToLocalStorage(content) {
        const draft = {
            content: content,
        savedAt: new Date().toISOString(),
        wordCount: content.split(/\\\\s+/).length
    };
    localStorage.setItem('editor_draft', JSON.stringify(draft));
}

Сохранение и загрузка контента

Сохранение через API

// Пример сохранения контента на сервер
async function saveContentToServer() {
        if (!vditorInstance) {
            throw new Error('Редактор не инициализирован');
    }

    const content = {
            markdown: vditorInstance.getValue(),    // Markdown версия
        html: vditorInstance.getHTML(),         // HTML версия
        title: document.getElementById('title-input').value,
        metadata: {
                wordCount: vditorInstance.getValue().split(/\\\\s+/).length,
            created: new Date().toISOString(),
            updated: new Date().toISOString()
        }
    };

    try {
            // Отправка на сервер
        const response = await fetch('/api/content/save', {
                method: 'POST',
            headers: {
                    'Content-Type': 'application/json',
                'Authorization': `Bearer ${getAuthToken()}`
            },
            body: JSON.stringify(content)
        });

        if (!response.ok) {
                throw new Error(`Ошибка сохранения: ${response.status}`);
        }

        const result = await response.json();
        console.log('Контент сохранен:', result);
        return result;

    } catch (error) {
            console.error('Ошибка при сохранении:', error);
        throw error;
    }
}
// Сохранение с обработкой прогресса
async function saveWithProgress() {
        const saveButton = document.getElementById('save-btn');
    const originalText = saveButton.textContent;

    try {
            saveButton.disabled = true;
        saveButton.textContent = 'Сохранение...';

        const result = await saveContentToServer();

        saveButton.textContent = 'Сохранено!';
        setTimeout(() => {
                saveButton.textContent = originalText;
            saveButton.disabled = false;
        }, 2000);

        return result;

    } catch (error) {
            saveButton.textContent = 'Ошибка!';
        saveButton.classList.add('error');

        setTimeout(() => {
                saveButton.textContent = originalText;
            saveButton.classList.remove('error');
            saveButton.disabled = false;
        }, 3000);

        throw error;
    }
}

Загрузка контента из базы данных

// Загрузка контента по ID
async function loadContentFromServer(contentId) {
        try {
            const response = await fetch(`/api/content/${contentId}`, {
                method: 'GET',
            headers: {
                    'Authorization': `Bearer ${getAuthToken()}`
            }
        });

        if (!response.ok) {
                throw new Error(`Ошибка загрузки: ${response.status}`);
        }

        const content = await response.json();

        // Установка контента в редактор
        if (vditorInstance) {
                vditorInstance.setValue(content.markdown || content.content || '');

            // Обновление дополнительных полей если есть
            if (content.title) {
                    document.getElementById('title-input').value = content.title;
            }

            console.log('Контент загружен:', content);
        }

        return content;

    } catch (error) {
            console.error('Ошибка при загрузке:', error);

        // Загрузка из локального хранилища при ошибке
        const localDraft = localStorage.getItem(`content_draft_${contentId}`);
        if (localDraft && vditorInstance) {
                try {
                    const draft = JSON.parse(localDraft);
                vditorInstance.setValue(draft.content || '');
                console.log('Загружен локальный черновик');
            } catch (e) {
                    console.error('Ошибка загрузки черновика:', e);
            }
        }

        throw error;
    }
}

// Загрузка с индикацией прогресса
async function loadWithProgress(contentId) {
        const editorContainer = document.getElementById('vditor-container');
    const loadingIndicator = document.createElement('div');

    loadingIndicator.className = 'loading-indicator';
    loadingIndicator.innerHTML = `
        <div class=\\\"spinner\\\"></div>
        <p>Загрузка контента...</p>
    `;

    editorContainer.appendChild(loadingIndicator);

    try {
            const content = await loadContentFromServer(contentId);
        loadingIndicator.remove();
        return content;

    } catch (error) {
            loadingIndicator.innerHTML = `
            <div class=\\\"error-icon\\\">⚠️</div>
            <p>Ошибка загрузки. Используется локальная версия.</p>
        `;

        setTimeout(() => {
                loadingIndicator.remove();
        }, 3000);

        throw error;
    }
}

Работа с черновиками

// Управление черновиками
class DraftManager {
        constructor(editorInstance, draftKey = 'editor_draft') {
            this.editor = editorInstance;
        this.draftKey = draftKey;
        this.autoSaveInterval = null;
        this.isDirty = false;
    }

    // Включение автосохранения
    enableAutoSave(interval = 30000) { // 30 секунд
        if (this.autoSaveInterval) {
                clearInterval(this.autoSaveInterval);
        }

        this.autoSaveInterval = setInterval(() => {
                if (this.isDirty) {
                    this.saveDraft();
                this.isDirty = false;
            }
        }, interval);

        // Отслеживание изменений
        if (this.editor) {
                this.editor.vditor.undo.addListener('stack', () => {
                    this.isDirty = true;
            });
        }
    }

    // Отключение автосохранения
    disableAutoSave() {
            if (this.autoSaveInterval) {
                clearInterval(this.autoSaveInterval);
            this.autoSaveInterval = null;
        }
    }

    // Сохранение черновика
    saveDraft(additionalData = {}) {
            if (!this.editor) return null;

        const draft = {
                content: this.editor.getValue(),
            html: this.editor.getHTML(),
            savedAt: new Date().toISOString(),
            metadata: {
                    wordCount: this.editor.getValue().split(/\\\\s+/).length,
                ...additionalData
            }
        };

        localStorage.setItem(this.draftKey, JSON.stringify(draft));
        console.log('Черновик сохранен:', draft);
        return draft;
    }

    // Загрузка черновика
    loadDraft() {
            const draftJson = localStorage.getItem(this.draftKey);
        if (!draftJson) return null;

        try {
                const draft = JSON.parse(draftJson);

            if (this.editor && draft.content) {
                    this.editor.setValue(draft.content);
                console.log('Черновик загружен:', draft);
            }

            return draft;

        } catch (error) {
                console.error('Ошибка загрузки черновика:', error);
            return null;
        }
    }

    // Очистка черновика
    clearDraft() {
            localStorage.removeItem(this.draftKey);
        this.isDirty = false;
        console.log('Черновик очищен');
    }

    // Получение информации о черновике
    getDraftInfo() {
            const draftJson = localStorage.getItem(this.draftKey);
        if (!draftJson) return null;

        try {
                const draft = JSON.parse(draftJson);
            return {
                    exists: true,
                savedAt: new Date(draft.savedAt),
                wordCount: draft.metadata?.wordCount || 0,
                size: new Blob([draftJson]).size
            };
        } catch (error) {
                return null;
        }
    }
}
// Использование менеджера черновиков
const draftManager = new DraftManager(vditorInstance, 'course_content_draft');
// Включение автосохранения при инициализации
draftManager.enableAutoSave();
// Ручное сохранение
document.getElementById('save-draft-btn').addEventListener('click', () => {
        const title = document.getElementById('course-title').value;
    draftManager.saveDraft({ title: title });
});

Работа с HTML и Markdown

Конвертация между форматами

// Конвертация Markdown в HTML с поддержкой KaTeX
function convertMarkdownToHTML(markdown) {
        if (!markdown) return '';

    // Используем встроенные возможности Vditor для конвертации
    if (vditorInstance && vditorInstance.vditor) {
            try {
                // Vditor использует Lute для парсинга Markdown
            const html = vditorInstance.vditor.lute.Md2HTML(markdown);
            return html;
        } catch (error) {
                console.warn('Ошибка конвертации через Lute:', error);
        }
    }

    // Fallback: используем marked.js если Vditor не доступен
    if (typeof marked !== 'undefined') {
            // Настройка marked для поддержки LaTeX
        marked.setOptions({
                breaks: true,
            gfm: true,
            highlight: function(code, lang) {
                    if (lang && hljs.getLanguage(lang)) {
                        return hljs.highlight(code, { language: lang }).value;
                }
                return hljs.highlightAuto(code).value;
            }
        });

        // Обработка LaTeX формул
        const processedMarkdown = markdown.replace(/\\\\$\\\\$(.*?)\\\\$\\\\$/g, (match, formula) => {
                try {
                    return katex.renderToString(formula, { displayMode: true });
            } catch (e) {
                    return `<div class=\\\"latex-error\\\">Ошибка формулы: ${formula}</div>`;
            }
        }).replace(/\\\\$(.*?)\\\\$/g, (match, formula) => {
                try {
                    return katex.renderToString(formula, { displayMode: false });
            } catch (e) {
                    return `<span class=\\\"latex-error\\\">${formula}</span>`;
            }
        });

        return marked(processedMarkdown);
    }

    // Самый простой fallback
    return markdown
        .replace(/^# (.*$)/gim, '<h1>$1</h1>')
        .replace(/^## (.*$)/gim, '<h2>$1</h2>')
        .replace(/^### (.*$)/gim, '<h3>$1</h3>')
        .replace(/\\\\*\\\\*(.*?)\\\\*\\\\*/g, '<strong>$1</strong>')
        .replace(/\\\\*(.*?)\\\\*/g, '<em>$1</em>')
        .replace(/`(.*?)`/g, '<code>$1</code>')
        .replace(/\\
/g, '<br>');
}
// Конвертация HTML в Markdown (упрощенная)
function convertHTMLToMarkdown(html) {
        if (!html) return '';

    // Простая конвертация основных тегов
    let markdown = html
        .replace(/<h1>(.*?)<\\\\/h1>/gi, '# $1\\
\\
')
        .replace(/<h2>(.*?)<\\\\/h2>/gi, '## $1\\
\\
')
        .replace(/<h3>(.*?)<\\\\/h3>/gi, '### $1\\
\\
')
        .replace(/<strong>(.*?)<\\\\/strong>/gi, '**$1**')
        .replace(/<b>(.*?)<\\\\/b>/gi, '**$1**')
        .replace(/<em>(.*?)<\\\\/em>/gi, '*$1*')
        .replace(/<i>(.*?)<\\\\/i>/gi, '*$1*')
        .replace(/<code>(.*?)<\\\\/code>/gi, '\`$1\`')
        .replace(/<pre><code>(.*?)<\\\\/code><\\\\/pre>/gis, '\`\``\\
$1\\
```\\
')
        .replace(/<br\\\\s*\\\\/?>/gi, '\\
')
        .replace(/<p>(.*?)<\\\\/p>/gi, '$1\\
\\
')
        .replace(/<ul>(.*?)<\\\\/ul>/gis, (match, content) => {
                return content.replace(/<li>(.*?)<\\\\/li>/gi, '- $1\\
');
        })
        .replace(/<ol>(.*?)<\\\\/ol>/gis, (match, content) => {
                let counter = 1;
            return content.replace(/<li>(.*?)<\\\\/li>/gi, () => `${counter++}. $1\\
`);
        })
        .replace(/<a href=\\\"(.*?)\\\">(.*?)<\\\\/a>/gi, '[$2]($1)')
        .replace(/<[^>]*>/g, '') // Удаление остальных тегов
        .replace(/&nbsp;/g, ' ')
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&amp;/g, '&')
        .replace(/\\
{3,}/g, '\\
\\
'); // Удаление лишних переносов

    return markdown.trim();
}
// Получение обеих версий контента
function getContentInBothFormats() {
        if (!vditorInstance) return { markdown: '', html: '' };

    const markdown = vditorInstance.getValue();
    const html = vditorInstance.getHTML();

    return {
            markdown: markdown,
        html: html,
        stats: {
                markdownLength: markdown.length,
            htmlLength: html.length,
            wordCount: markdown.split(/\\\\s+/).length,
            lineCount: markdown.split('\\
').length
        }
    };
}
// Экспорт контента в разные форматы
function exportContent(format = 'both') {
        if (!vditorInstance) return null;

    const content = getContentInBothFormats();
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const filename = `content-${timestamp}`;

    switch (format) {
            case 'markdown':
            downloadFile(`${filename}.md`, content.markdown, 'text/markdown');
            break;

        case 'html':
            downloadFile(`${filename}.html`, `
                <!DOCTYPE html>
                <html>
                <head>
                    <meta charset=\\\"UTF-8\\\">
                    <title>Экспортированный контент</title>
                    <link rel=\\\"stylesheet\\\" href=\\\"https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css\\\">
                    <link rel=\\\"stylesheet\\\" href=\\\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css\\\">
                    <style>
                        body { font-family: Arial, sans-serif; line-height: 1.6; padding: 20px; max-width: 800px; margin: 0 auto; }
                        .latex-formula { overflow-x: auto; padding: 10px; background: #f5f5f5; }
                        pre code { border-radius: 4px; }
                    </style>
                </head>
                <body>
                    ${content.html}
                    <script src=\\\"https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js\\\"></script>
                    <script src=\\\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js\\\"></script>
                    <script>
                        document.addEventListener('DOMContentLoaded', function() {
                                // Рендеринг LaTeX формул
                            document.querySelectorAll('.language-math').forEach(el => {
                                    try {
                                        katex.render(el.textContent, el, {
                                            throwOnError: false,
                                        displayMode: el.classList.contains('display')
                                    });
                                } catch (e) {
                                        console.error('Ошибка рендеринга формулы:', e);
                                }
                            });

                            // Подсветка синтаксиса
                            hljs.highlightAll();
                        });
                    </script>
                </body>
                </html>
            `, 'text/html');
            break;

        case 'both':
            downloadFile(`${filename}.md`, content.markdown, 'text/markdown');
            downloadFile(`${filename}.html`, content.html, 'text/html');
            break;

        case 'json':
            downloadFile(`${filename}.json`, JSON.stringify(content, null, 2), 'application/json');
            break;
    }

    return content;
}
// Вспомогательная функция для скачивания файлов
function downloadFile(filename, content, mimeType) {
        const blob = new Blob([content], { type: mimeType });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');

    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
}

Обработка LaTeX формул

// Извлечение LaTeX формул из контента
function extractLatexFormulas(content) {
        const formulas = {
            inline: [],
        display: []
    };

    // Поиск inline формул: $...$
    const inlineRegex = /\\\\$(.*?)\\\\$/g;
    let match;
    while ((match = inlineRegex.exec(content)) !== null) {
            formulas.inline.push({
                formula: match[1],
            position: match.index,
            length: match[0].length
        });
    }

    // Поиск display формул: $$...$$
    const displayRegex = /\\\\$\\\\$(.*?)\\\\$\\\\$/g;
    while ((match = displayRegex.exec(content)) !== null) {
            formulas.display.push({
                formula: match[1],
            position: match.index,
            length: match[0].length
        });
    }

    return formulas;
}
// Валидация LaTeX формул
function validateLatexFormulas(content) {
        const formulas = extractLatexFormulas(content);
    const errors = [];

    formulas.inline.forEach((formula, index) => {
            try {
                // Проверка синтаксиса KaTeX
            katex.__parse(formula.formula);
        } catch (error) {
                errors.push({
                    type: 'inline',
                index: index,
                formula: formula.formula,
                error: error.message,
                position: formula.position
            });
        }
    });

    formulas.display.forEach((formula, index) => {
            try {
                katex.__parse(formula.formula);
        } catch (error) {
                errors.push({
                    type: 'display',
                index: index,
                formula: formula.formula,
                error: error.message,
                position: formula.position
            });
        }
    });

    return {
            valid: errors.length === 0,
        totalFormulas: formulas.inline.length + formulas.display.length,
        errors: errors
    };
}
// Автоматическое исправление распространенных ошибок LaTeX
function fixCommonLatexErrors(content) {
        let fixedContent = content;

    // Замена русских букв в математическом режиме
    fixedContent = fixedContent.replace(/\\\\$([^$]*?)[а-яА-Я]([^$]*?)\\\\$/g, (match, before, after) => {
            console.warn('Обнаружены русские буквы в формуле:', match);
        return `$${before}\\\\\\\\text{...}${after}$`;
    });

    // Добавление пропущенных обратных слешей
    fixedContent = fixedContent.replace(/\\\\$(.*?)(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega)(.*?)\\\\$/gi, 
        (match, before, greek, after) => `$${before}\\\\\\\\${greek}${after}$`);

    // Исправление дробей без frac
    fixedContent = fixedContent.replace(/\\\\$(.*?)(\\\\d+)\\\\/(\\\\d+)(.*?)\\\\$/g, (match, before, num, den, after) => {
            return `$${before}\\\\\\\\frac{${num}}{${den}}${after}$`;
    });

    return fixedContent;
}

Отображение контента без редактора

Рендеринг HTML с поддержкой KaTeX

// Функция для отображения контента без редактора
function renderContentWithoutEditor(content, containerId, options = {}) {
        const container = document.getElementById(containerId);
    if (!container) {
            console.error(`Контейнер ${containerId} не найден`);
        return;
    }

    const config = {
            enableMath: true,
        enableSyntaxHighlight: true,
        theme: 'light',
        ...options
    };

    // Конвертация Markdown в HTML если нужно
    let htmlContent;
    if (content.markdown) {
            htmlContent = convertMarkdownToHTML(content.markdown);
    } else if (content.html) {
            htmlContent = content.html;
    } else {
            htmlContent = convertMarkdownToHTML(content);
    }

    // Создание структуры для отображения
    container.innerHTML = `
        <div class=\\\"rendered-content ${config.theme}-theme\\\">
            <div class=\\\"content-body\\\">
                ${htmlContent}
            </div>
        </div>
    `;

    // Инициализация KaTeX для рендеринга формул
    if (config.enableMath && typeof katex !== 'undefined') {
            renderLatexFormulas(container);
    }

    // Подсветка синтаксиса
    if (config.enableSyntaxHighlight && typeof hljs !== 'undefined') {
            hljs.highlightAll();
    }

    // Добавление стилей если их нет
    addContentStyles();
}
// Рендеринг LaTeX формул в контейнере
function renderLatexFormulas(container) {
        if (!container || typeof katex === 'undefined') return;

    // Рендеринг display формул ($$...$$)
    container.querySelectorAll('.language-math.display, .katex-display').forEach(element => {
            try {
                const formula = element.textContent.trim();
            katex.render(formula, element, {
                    displayMode: true,
                throwOnError: false,
                errorColor: '#cc0000'
            });
        } catch (error) {
                console.error('Ошибка рендеринга display формулы:', error);
            element.innerHTML = `<div class=\\\"latex-error\\\">Ошибка формулы: ${element.textContent}</div>`;
        }
    });

    // Рендеринг inline формул ($...$)
    container.querySelectorAll('.language-math:not(.display), .katex').forEach(element => {
            try {
                const formula = element.textContent.trim();
            katex.render(formula, element, {
                    displayMode: false,
                throwOnError: false,
                errorColor: '#cc0000'
            });
        } catch (error) {
                console.error('Ошибка рендеринга inline формулы:', error);
            element.innerHTML = `<span class=\\\"latex-error\\\">${element.textContent}</span>`;
        }
    });

    // Обработка формул в тексте (если они не обернуты в теги)
    const textNodes = Array.from(container.childNodes).filter(node => 
        node.nodeType === Node.TEXT_NODE && 
        (node.textContent.includes('$') || node.textContent.includes('\\\\\\\\['))
    );

    textNodes.forEach(node => {
            const wrapper = document.createElement('span');
        wrapper.innerHTML = node.textContent
            .replace(/\\\\$\\\\$(.*?)\\\\$\\\\$/g, (match, formula) => {
                    try {
                        return katex.renderToString(formula, { displayMode: true });
                } catch (e) {
                        return `<div class=\\\"latex-error\\\">Ошибка: ${formula}</div>`;
                }
            })
            .replace(/\\\\$(.*?)\\\\$/g, (match, formula) => {
                    try {
                        return katex.renderToString(formula, { displayMode: false });
                } catch (e) {
                        return `<span class=\\\"latex-error\\\">${formula}</span>`;
                }
            });

        node.parentNode.replaceChild(wrapper, node);
    });
}
// Добавление необходимых стилей
function addContentStyles() {
        if (document.getElementById('content-render-styles')) return;

    const styles = `
        <style id=\\\"content-render-styles\\\">
            .rendered-content {
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
                line-height: 1.6;
                color: #333;
            }

            .rendered-content.light-theme {
                    background: #fff;
                color: #333;
            }

            .rendered-content.dark-theme {
                    background: #1a1a1a;
                color: #e0e0e0;
            }

            .content-body {
                    max-width: 800px;
                margin: 0 auto;
                padding: 20px;
            }

            .content-body h1, 
            .content-body h2, 
            .content-body h3, 
            .content-body h4 {
                    margin-top: 1.5em;
                margin-bottom: 0.5em;
                font-weight: 600;
                line-height: 1.25;
            }

            .content-body h1 { font-size: 2em; border-bottom: 2px solid #eee; padding-bottom: 0.3em; }
            .content-body h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
            .content-body h3 { font-size: 1.25em; }
            .content-body h4 { font-size: 1em; }

            .content-body p {
                    margin: 1em 0;
            }

            .content-body ul, 
            .content-body ol {
                    padding-left: 2em;
                margin: 1em 0;
            }

            .content-body li {
                    margin: 0.5em 0;
            }

            .content-body blockquote {
                    margin: 1em 0;
                padding: 0.5em 1em;
                border-left: 4px solid #ddd;
                background: #f9f9f9;
                color: #666;
            }

            .content-body pre {
                    background: #f6f8fa;
                border-radius: 6px;
                padding: 16px;
                overflow: auto;
                margin: 1em 0;
            }

            .content-body code {
                    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
                padding: 0.2em 0.4em;
                background: rgba(175, 184, 193, 0.2);
                border-radius: 3px;
                font-size: 0.9em;
            }

            .content-body pre code {
                    background: transparent;
                padding: 0;
            }

            .content-body table {
                    border-collapse: collapse;
                width: 100%;
                margin: 1em 0;
            }