# Руководство по работе с Vditor ## Содержание 1. [Введение](#введение) 2. [Инициализация редактора](#инициализация-редактора) 3. [Работа с API редактора](#работа-с-api-редактора) 4. [Сохранение и загрузка контента](#сохранение-и-загрузка-контента) 5. [Работа с HTML и Markdown](#работа-с-html-и-markdown) 6. [Отображение контента без редактора](#отображение-контента-без-редактора) 7. [Хранение в базе данных](#хранение-в-базе-данных) 8. [Примеры использования](#примеры-использования) ## Введение Vditor - это мощный WYSIWYG Markdown редактор с поддержкой LaTeX формул через KaTeX. Редактор предоставляет богатый API для работы с контентом. ## Инициализация редактора ### Базовый пример инициализации ```javascript 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 инициализирован'); } }); } ``` ### Русская локализация ```javascript // Русская локализация для подсказок 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 ```javascript // Получение текущего содержимого редактора 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 }; } ``` ### Обработка событий ```javascript // Добавление обработчиков событий 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 ```javascript // Пример сохранения контента на сервер 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; } } ``` ### Загрузка контента из базы данных ```javascript // Загрузка контента по 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 = `

Загрузка контента...

`; editorContainer.appendChild(loadingIndicator); try { const content = await loadContentFromServer(contentId); loadingIndicator.remove(); return content; } catch (error) { loadingIndicator.innerHTML = `
⚠️

Ошибка загрузки. Используется локальная версия.

`; setTimeout(() => { loadingIndicator.remove(); }, 3000); throw error; } } ``` ### Работа с черновиками ```javascript // Управление черновиками 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 ### Конвертация между форматами ```javascript // Конвертация 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 `
Ошибка формулы: ${formula}
`; } }).replace(/\\\\$(.*?)\\\\$/g, (match, formula) => { try { return katex.renderToString(formula, { displayMode: false }); } catch (e) { return `${formula}`; } }); return marked(processedMarkdown); } // Самый простой fallback return markdown .replace(/^# (.*$)/gim, '

$1

') .replace(/^## (.*$)/gim, '

$1

') .replace(/^### (.*$)/gim, '

$1

') .replace(/\\\\*\\\\*(.*?)\\\\*\\\\*/g, '$1') .replace(/\\\\*(.*?)\\\\*/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/\\ /g, '
'); } // Конвертация HTML в Markdown (упрощенная) function convertHTMLToMarkdown(html) { if (!html) return ''; // Простая конвертация основных тегов let markdown = html .replace(/

(.*?)<\\\\/h1>/gi, '# $1\\ \\ ') .replace(/

(.*?)<\\\\/h2>/gi, '## $1\\ \\ ') .replace(/

(.*?)<\\\\/h3>/gi, '### $1\\ \\ ') .replace(/(.*?)<\\\\/strong>/gi, '**$1**') .replace(/(.*?)<\\\\/b>/gi, '**$1**') .replace(/(.*?)<\\\\/em>/gi, '*$1*') .replace(/(.*?)<\\\\/i>/gi, '*$1*') .replace(/(.*?)<\\\\/code>/gi, '\`$1\`') .replace(/
(.*?)<\\\\/code><\\\\/pre>/gis, '\`\``\\
$1\\
```\\
')
        .replace(//gi, '\\
')
        .replace(/

(.*?)<\\\\/p>/gi, '$1\\ \\ ') .replace(/