diff --git a/assets/css/style.css b/assets/css/style.css index ae91af5..f512f33 100755 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -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; } \ No newline at end of file diff --git a/assets/js/editor.js b/assets/js/editor.js index fbaa67c..00d5e76 100755 --- a/assets/js/editor.js +++ b/assets/js/editor.js @@ -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>)+/, '').replace(/(
<\/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() {