add fullscreen for editor

This commit is contained in:
mirivlad 2025-11-29 15:24:11 +03:00
parent cd382c5c7b
commit 7fd1fb20c4
2 changed files with 179 additions and 29 deletions

View File

@ -18,4 +18,28 @@
width: 16px;
height: 16px;
}
}
/* editor styles */
@media (max-width: 768px) {
.writer-editor-container .ql-container {
font-size: 16px !important; /* Предотвращает масштабирование в iOS */
}
.writer-editor-container .ql-toolbar {
padding: 8px !important;
}
.writer-editor-container .ql-toolbar .ql-formats {
margin-right: 8px !important;
}
}
/* Стили для полноэкранного режима */
.ql-toolbar.ql-snow {
transition: all 0.3s ease;
}
.writer-editor-container.fullscreen {
transition: all 0.3s ease;
}

View File

@ -1,3 +1,4 @@
// editor.js
class DialogueFormatter {
constructor(quill) {
this.quill = quill;
@ -19,7 +20,6 @@ class DialogueFormatter {
getLineStart(index) {
let lineStart = index;
// Безопасный поиск начала строки (защита от отрицательных индексов)
while (lineStart > 0) {
const prevChar = this.quill.getText(lineStart - 1, 1);
if (prevChar === '\n') break;
@ -30,65 +30,49 @@ class DialogueFormatter {
checkForDialogue() {
const selection = this.quill.getSelection();
// Защита от некорректных позиций курсора
if (!selection || selection.index < 2) return;
const lineStart = this.getLineStart(selection.index);
const charsFromStart = selection.index - lineStart;
// Работаем ТОЛЬКО когда:
// 1. Ровно 2 символа от начала строки до курсора
// 2. Эти символы - "- "
if (charsFromStart !== 2) return;
const text = this.quill.getText(lineStart, 2);
if (text !== '- ') return;
// Атомарная операция замены
this.quill.updateContents([
{ retain: lineStart },
{ delete: 2 }, // Удаляем "- "
{ insert: '— ' } // Вставляем "— "
{ delete: 2 },
{ insert: '— ' }
], 'user');
// Явно устанавливаем курсор ПОСЛЕ пробела
this.quill.setSelection(lineStart + 2, 0, 'silent');
}
// Простой метод для форматирования диалогов
formatSelectionAsDialogue() {
const range = this.quill.getSelection();
if (!range || range.length === 0) return;
const selectedText = this.quill.getText(range.index, range.length);
// Простая замена всех "- " на "— " в выделенном тексте
const formattedText = selectedText.replace(/(^|\n)- /g, '$1— ');
if (formattedText !== selectedText) {
this.quill.deleteText(range.index, range.length, 'user');
this.quill.insertText(range.index, formattedText, 'user');
// Восстанавливаем выделение
this.quill.setSelection(range.index, formattedText.length, 'silent');
}
}
// Простой метод для отмены форматирования диалогов
unformatSelectionAsDialogue() {
const range = this.quill.getSelection();
if (!range || range.length === 0) return;
const selectedText = this.quill.getText(range.index, range.length);
// Простая замена всех "— " на "- " в выделенном тексте
const unformattedText = selectedText.replace(/(^|\n)— /g, '$1- ');
if (unformattedText !== selectedText) {
this.quill.deleteText(range.index, range.length, 'user');
this.quill.insertText(range.index, unformattedText, 'user');
// Восстанавливаем выделение
this.quill.setSelection(range.index, unformattedText.length, 'silent');
}
}
@ -99,6 +83,8 @@ class WriterEditor {
this.form = document.querySelector(formSelector);
this.editorContainer = document.getElementById(editorContainerId);
this.textarea = document.getElementById(textareaId);
this.isFullscreen = false;
this.originalStyles = {};
this.init();
}
@ -123,6 +109,7 @@ class WriterEditor {
[{ 'font': [] }],
['link','image','video'],
['clean'],
['fullscreen'] // Добавляем кнопку полноэкранного режима
],
handlers: {
'dialogue': () => {
@ -134,6 +121,9 @@ class WriterEditor {
if (this.dialogueFormatter) {
this.dialogueFormatter.unformatSelectionAsDialogue();
}
},
'fullscreen': () => {
this.toggleFullscreen();
}
}
},
@ -156,20 +146,14 @@ class WriterEditor {
placeholder: 'Введите текст главы...'
});
// Добавляем кастомные кнопки в тулбар после инициализации Quill
this.addCustomButtonsToToolbar();
// Инициализируем автоформатер диалогов
this.dialogueFormatter = new DialogueFormatter(this.quill);
// Загружаем текст
const rawContent = this.editorContainer.dataset.content || '';
if (rawContent.trim()) this.quill.root.innerHTML = rawContent.trim();
// Обрабатываем уже существующие диалоги при загрузке
setTimeout(() => this.formatExistingDialogues(), 100);
// Синхронизация с textarea
const sync = () => {
let html = this.quill.root.innerHTML;
html = html.replace(/^(<p><br><\/p>)+/, '').replace(/(<p><br><\/p>)+$/, '');
@ -179,21 +163,32 @@ class WriterEditor {
this.quill.on('text-change', sync);
this.form.addEventListener('submit', sync);
// Делаем глобально доступным для автосейва
// Обработчик изменения ориентации для мобильных устройств
window.addEventListener('orientationchange', () => {
if (this.isFullscreen) {
setTimeout(() => this.adjustFullscreenHeight(), 300);
}
});
// Обработчик изменения размера окна
window.addEventListener('resize', () => {
if (this.isFullscreen) {
this.adjustFullscreenHeight();
}
});
window.quillEditorInstance = this.quill;
window.quillTextarea = this.textarea;
}
addCustomButtonsToToolbar() {
// Находим тулбар Quill
const toolbar = this.quill.container.previousSibling;
if (!toolbar) return;
// Находим кнопки по классам Quill
const dialogueBtn = toolbar.querySelector('.ql-dialogue');
const undoDialogueBtn = toolbar.querySelector('.ql-undodialogue');
const fullscreenBtn = toolbar.querySelector('.ql-fullscreen');
// Заменяем содержимое кнопок на наши символы
if (dialogueBtn) {
dialogueBtn.innerHTML = '—';
dialogueBtn.title = 'Форматировать диалоги (—)';
@ -205,6 +200,137 @@ class WriterEditor {
undoDialogueBtn.title = 'Убрать форматирование диалогов (-)';
undoDialogueBtn.style.fontWeight = 'bold';
}
if (fullscreenBtn) {
fullscreenBtn.innerHTML = '⛶';
fullscreenBtn.title = 'Полноэкранный режим';
}
}
toggleFullscreen() {
if (this.isFullscreen) {
this.exitFullscreen();
} else {
this.enterFullscreen();
}
}
enterFullscreen() {
this.isFullscreen = true;
// Сохраняем оригинальные стили
this.originalStyles = {
container: this.editorContainer.style.cssText,
body: document.body.style.cssText,
toolbar: this.quill.container.previousSibling.style.cssText
};
// Получаем тулбар
const toolbar = this.quill.container.previousSibling;
// Применяем стили для полноэкранного режима
document.body.style.overflow = 'hidden';
this.editorContainer.style.position = 'fixed';
this.editorContainer.style.top = '0';
this.editorContainer.style.left = '0';
this.editorContainer.style.width = '100vw';
this.editorContainer.style.zIndex = '9999';
this.editorContainer.style.background = 'white';
this.editorContainer.style.paddingTop = toolbar.offsetHeight + 'px';
// Стили для тулбара
toolbar.style.position = 'fixed';
toolbar.style.top = '0';
toolbar.style.left = '0';
toolbar.style.width = '100%';
toolbar.style.zIndex = '10000';
toolbar.style.background = 'white';
toolbar.style.borderBottom = '1px solid #ccc';
this.adjustFullscreenHeight();
// Добавляем обработчик ESC
this.escapeHandler = (e) => {
if (e.key === 'Escape') {
this.exitFullscreen();
}
};
document.addEventListener('keydown', this.escapeHandler);
// Добавляем кнопку выхода из полноэкранного режима
this.addFullscreenExitButton();
}
exitFullscreen() {
this.isFullscreen = false;
// Восстанавливаем оригинальные стили
this.editorContainer.style.cssText = this.originalStyles.container || '';
document.body.style.cssText = this.originalStyles.body || '';
const toolbar = this.quill.container.previousSibling;
if (toolbar) {
toolbar.style.cssText = this.originalStyles.toolbar || '';
}
// Удаляем обработчик ESC
if (this.escapeHandler) {
document.removeEventListener('keydown', this.escapeHandler);
}
// Удаляем кнопку выхода
this.removeFullscreenExitButton();
}
adjustFullscreenHeight() {
// Корректируем высоту с учетом видимой области и возможной клавиатуры
const visualViewport = window.visualViewport || window;
const height = visualViewport.height || window.innerHeight;
this.editorContainer.style.height = height + 'px';
// Пересчитываем размеры Quill
setTimeout(() => {
this.quill.root.style.height = '100%';
this.quill.root.querySelector('.ql-editor').style.height = '100%';
}, 50);
}
addFullscreenExitButton() {
// Создаем кнопку выхода из полноэкранного режима
this.exitButton = document.createElement('button');
this.exitButton.innerHTML = '✕';
this.exitButton.title = 'Выйти из полноэкранного режима (ESC)';
this.exitButton.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
z-index: 10001;
background: #dc3545;
color: white;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
`;
this.exitButton.addEventListener('click', () => {
this.exitFullscreen();
});
document.body.appendChild(this.exitButton);
}
removeFullscreenExitButton() {
if (this.exitButton && this.exitButton.parentNode) {
this.exitButton.parentNode.removeChild(this.exitButton);
}
}
formatExistingDialogues() {