fix editor for short_story and chapters
This commit is contained in:
parent
ce6fffaa57
commit
b69a110b97
|
|
@ -1,15 +1,22 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Поддержка WriterEditor (для глав и рассказов)
|
// Не запускать на странице создания книги
|
||||||
const writerEditor = window.writerEditor;
|
if (window.location.href.includes('/books/create')) {
|
||||||
if (writerEditor) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ждём инициализации writerEditor
|
||||||
|
function initAutosave() {
|
||||||
|
const writerEditor = window.writerEditor;
|
||||||
|
if (!writerEditor || !writerEditor.quill) {
|
||||||
|
// Пробуем снова через 500ms
|
||||||
|
setTimeout(initAutosave, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const textarea = writerEditor.textarea;
|
const textarea = writerEditor.textarea;
|
||||||
if (!textarea) return;
|
if (!textarea) return;
|
||||||
|
|
||||||
// Для рассказов textarea может быть hidden input
|
let lastSavedContent = textarea.value;
|
||||||
const contentField = textarea.tagName === 'TEXTAREA' ? textarea :
|
|
||||||
(document.getElementById('story-content') ? document.getElementById('story-content') : textarea);
|
|
||||||
|
|
||||||
let lastSavedContent = contentField.value;
|
|
||||||
let saveTimeout;
|
let saveTimeout;
|
||||||
|
|
||||||
function showMessage(message, isError = false) {
|
function showMessage(message, isError = false) {
|
||||||
|
|
@ -38,7 +45,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const autoSave = () => {
|
const autoSave = () => {
|
||||||
const currentContent = contentField.value;
|
const currentContent = textarea.value;
|
||||||
|
// Не сохранять пустой контент
|
||||||
|
if (!currentContent || currentContent.trim() === '' || currentContent === '<p><br></p>') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (currentContent === lastSavedContent) return;
|
if (currentContent === lastSavedContent) return;
|
||||||
|
|
||||||
const form = writerEditor.form;
|
const form = writerEditor.form;
|
||||||
|
|
@ -77,81 +88,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
saveTimeout = setTimeout(autoSave, 2000);
|
saveTimeout = setTimeout(autoSave, 2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Периодическая автосохранение
|
|
||||||
setInterval(autoSave, 30000);
|
|
||||||
} else {
|
|
||||||
// Старая поддержка через window.quillEditorInstance
|
|
||||||
const quill = window.quillEditorInstance;
|
|
||||||
const textarea = window.quillTextarea;
|
|
||||||
if (!quill || !textarea) return;
|
|
||||||
|
|
||||||
let lastSavedContent = textarea.value;
|
|
||||||
let saveTimeout;
|
|
||||||
|
|
||||||
function showMessage(message, isError = false) {
|
|
||||||
let msgEl = document.getElementById('autosave-message');
|
|
||||||
if (!msgEl) {
|
|
||||||
msgEl = document.createElement('div');
|
|
||||||
msgEl.id = 'autosave-message';
|
|
||||||
msgEl.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 70px;
|
|
||||||
right: 10px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: ${isError ? '#dc3545' : '#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(msgEl);
|
|
||||||
}
|
|
||||||
msgEl.textContent = message;
|
|
||||||
msgEl.style.background = isError ? '#dc3545' : '#28a745';
|
|
||||||
msgEl.style.display = 'block';
|
|
||||||
setTimeout(() => msgEl.style.display = 'none', 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const autoSave = () => {
|
|
||||||
const currentContent = textarea.value;
|
|
||||||
if (currentContent === lastSavedContent) return;
|
|
||||||
|
|
||||||
const form = document.getElementById('chapter-form');
|
|
||||||
const formData = new FormData(form);
|
|
||||||
formData.append('autosave', 'true');
|
|
||||||
formData.append('content', currentContent);
|
|
||||||
|
|
||||||
showMessage('Сохранение...');
|
|
||||||
|
|
||||||
fetch(window.location.href, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
lastSavedContent = currentContent;
|
|
||||||
showMessage('Автосохранено: ' + new Date().toLocaleTimeString());
|
|
||||||
} else {
|
|
||||||
throw new Error(data.error || 'Ошибка сервера');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
showMessage('Ошибка автосохранения: ' + err.message, true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
quill.on('text-change', () => {
|
|
||||||
clearTimeout(saveTimeout);
|
|
||||||
saveTimeout = setTimeout(autoSave, 2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Периодическая автосохранение
|
// Периодическая автосохранение
|
||||||
setInterval(autoSave, 30000);
|
setInterval(autoSave, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Запускаем
|
||||||
|
initAutosave();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ function toggleStoryEditor() {
|
||||||
if (!window.writerEditor) {
|
if (!window.writerEditor) {
|
||||||
const textarea = document.getElementById('content');
|
const textarea = document.getElementById('content');
|
||||||
if (textarea) {
|
if (textarea) {
|
||||||
window.writerEditor = new WriterEditor('#book-form', 'story-editor', 'content', true);
|
window.writerEditor = new WriterEditor('#book-form', 'quill-editor', 'content', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -109,11 +109,10 @@ class DialogueFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class WriterEditor {
|
class WriterEditor {
|
||||||
constructor(formSelector = '#chapter-form', editorContainerId = 'quill-editor', textareaId = 'content', isShortStory = false) {
|
constructor(formSelector = '#chapter-form', editorContainerId = 'quill-editor', textareaId = 'content') {
|
||||||
this.form = document.querySelector(formSelector);
|
this.form = document.querySelector(formSelector);
|
||||||
this.editorContainer = document.getElementById(editorContainerId);
|
this.editorContainer = document.getElementById(editorContainerId);
|
||||||
this.textarea = document.getElementById(textareaId);
|
this.textarea = document.getElementById(textareaId);
|
||||||
this.isShortStory = isShortStory;
|
|
||||||
this.isFullscreen = false;
|
this.isFullscreen = false;
|
||||||
this.originalStyles = {};
|
this.originalStyles = {};
|
||||||
this.init();
|
this.init();
|
||||||
|
|
@ -122,31 +121,23 @@ class WriterEditor {
|
||||||
init() {
|
init() {
|
||||||
if (!this.editorContainer || !this.textarea || !this.form) return;
|
if (!this.editorContainer || !this.textarea || !this.form) return;
|
||||||
|
|
||||||
// Упрощённый тулбар для рассказов
|
// Полный тулбар (одинаковый для глав и рассказов)
|
||||||
const toolbarOptions = this.isShortStory
|
const toolbarOptions = [
|
||||||
? [
|
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||||
[{ 'header': [1, 2, 3, false] }],
|
['bold','italic','underline','strike'],
|
||||||
['bold', 'italic', 'underline'],
|
[{ 'align': [] }],
|
||||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
[{ 'color': [] }, { 'background': [] }],
|
||||||
['link', 'blockquote', 'code-block'],
|
['blockquote','code-block'],
|
||||||
['clean']
|
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||||
]
|
[{ 'script': 'sub'}, { 'script': 'super' }],
|
||||||
: [
|
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
||||||
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
['dialogue', 'undodialogue'],
|
||||||
['bold','italic','underline','strike'],
|
[{ 'size': ['small', false, 'large', 'huge'] }],
|
||||||
[{ 'align': [] }],
|
[{ 'font': [] }],
|
||||||
[{ 'color': [] }, { 'background': [] }],
|
['link','image','video'],
|
||||||
['blockquote','code-block'],
|
['clean'],
|
||||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
['fullscreen']
|
||||||
[{ 'script': 'sub'}, { 'script': 'super' }],
|
];
|
||||||
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
|
||||||
['dialogue', 'undodialogue'],
|
|
||||||
[{ 'size': ['small', false, 'large', 'huge'] }],
|
|
||||||
[{ 'font': [] }],
|
|
||||||
['link','image','video'],
|
|
||||||
['clean'],
|
|
||||||
['fullscreen']
|
|
||||||
];
|
|
||||||
|
|
||||||
this.quill = new Quill(this.editorContainer, {
|
this.quill = new Quill(this.editorContainer, {
|
||||||
theme: 'snow',
|
theme: 'snow',
|
||||||
|
|
@ -184,18 +175,16 @@ class WriterEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, // Упрощённый тулбар для рассказов
|
},
|
||||||
placeholder: this.isShortStory ? 'Напишите здесь ваш рассказ...' : 'Введите текст главы...'
|
placeholder: 'Введите текст главы...'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dialogueFormatter = this.isShortStory ? null : new DialogueFormatter(this.quill);
|
this.dialogueFormatter = new DialogueFormatter(this.quill);
|
||||||
|
|
||||||
const rawContent = this.editorContainer.dataset.content || '';
|
const rawContent = this.editorContainer.dataset.content || '';
|
||||||
if (rawContent.trim()) this.quill.root.innerHTML = rawContent.trim();
|
if (rawContent.trim()) this.quill.root.innerHTML = rawContent.trim();
|
||||||
|
|
||||||
if (!this.isShortStory) {
|
setTimeout(() => this.formatExistingDialogues(), 100);
|
||||||
setTimeout(() => this.formatExistingDialogues(), 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
let html = this.quill.root.innerHTML;
|
let html = this.quill.root.innerHTML;
|
||||||
|
|
@ -397,8 +386,6 @@ class WriterEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomButtonsToToolbar() {
|
addCustomButtonsToToolbar() {
|
||||||
if (this.isShortStory) return;
|
|
||||||
|
|
||||||
const toolbar = this.quill.container.previousSibling;
|
const toolbar = this.quill.container.previousSibling;
|
||||||
if (!toolbar) return;
|
if (!toolbar) return;
|
||||||
|
|
||||||
|
|
@ -426,16 +413,23 @@ class WriterEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Проверяем, это редактор рассказа или главы
|
// Не создавать редактор на странице создания книги
|
||||||
const storyEditor = document.getElementById('story-editor');
|
if (window.location.href.includes('/books/create')) {
|
||||||
const quillEditor = document.getElementById('quill-editor');
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (storyEditor) {
|
const quillEditor = document.getElementById('quill-editor');
|
||||||
const textarea = document.getElementById('story-content');
|
const content = document.getElementById('content');
|
||||||
if (textarea) {
|
|
||||||
window.writerEditor = new WriterEditor('#book-form', 'story-editor', 'story-content', true);
|
if (quillEditor && content && !window.writerEditor) {
|
||||||
|
// Проверяем ТОЛЬКО нужные формы
|
||||||
|
const bookForm = document.getElementById('book-form');
|
||||||
|
const chapterForm = document.getElementById('chapter-form');
|
||||||
|
|
||||||
|
if (bookForm) {
|
||||||
|
window.writerEditor = new WriterEditor('#book-form', 'quill-editor', 'content');
|
||||||
|
} else if (chapterForm) {
|
||||||
|
window.writerEditor = new WriterEditor('#chapter-form', 'quill-editor', 'content');
|
||||||
}
|
}
|
||||||
} else if (quillEditor) {
|
|
||||||
window.writerEditor = new WriterEditor();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -5,7 +5,7 @@ include 'views/layouts/header.php';
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-10">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1 class="h2">Создание новой книги</h1>
|
<h1 class="h2">Создание новой книги</h1>
|
||||||
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-secondary">
|
<a href="<?= SITE_URL ?>/books" class="btn btn-outline-secondary">
|
||||||
|
|
@ -109,10 +109,10 @@ include 'views/layouts/header.php';
|
||||||
<h5 class="card-title mb-0">Содержание рассказа</h5>
|
<h5 class="card-title mb-0">Содержание рассказа</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="story-editor" style="height: 400px;" data-content="<?= htmlspecialchars($_POST['content'] ?? '', ENT_QUOTES) ?>">
|
<div id="quill-editor" style="height: 400px;" data-content="<?= htmlspecialchars($_POST['content'] ?? '', ENT_QUOTES) ?>">
|
||||||
<?= $_POST['content'] ?? '' ?>
|
<?= $_POST['content'] ?? '' ?>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="content" id="story-content" <?= !empty($_POST['content']) ? 'value="' . htmlspecialchars($_POST['content'], ENT_QUOTES) . '"' : '' ?>>
|
<input type="hidden" name="content" id="content" <?= !empty($_POST['content']) ? 'value="' . htmlspecialchars($_POST['content'], ENT_QUOTES) . '"' : '' ?>>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -141,18 +141,11 @@ function toggleStoryEditor() {
|
||||||
storyCard.style.display = isShortStory ? 'block' : 'none';
|
storyCard.style.display = isShortStory ? 'block' : 'none';
|
||||||
|
|
||||||
if (isShortStory && !window.writerEditor) {
|
if (isShortStory && !window.writerEditor) {
|
||||||
initQuillEditor(true);
|
window.writerEditor = new WriterEditor('#book-form', 'quill-editor', 'content');
|
||||||
} else if (!isShortStory && window.writerEditor) {
|
} else if (!isShortStory && window.writerEditor && window.writerEditor.quill) {
|
||||||
window.writerEditor.quill.destroy();
|
try { window.writerEditor.quill.destroy(); } catch(e) {}
|
||||||
window.writerEditor = null;
|
window.writerEditor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isShortStory && window.writerEditor && window.writerEditor.quill) {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.writerEditor.quill.root.style.height = '400px';
|
|
||||||
window.writerEditor.quill.container.style.height = '400px';
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,24 @@ include 'views/layouts/header.php';
|
||||||
rows="4"><?= e($book['description'] ?? '') ?></textarea>
|
rows="4"><?= e($book['description'] ?? '') ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cover_image" class="form-label">Обложка книги</label>
|
||||||
|
<?php if (!empty($book['cover_image'])): ?>
|
||||||
|
<div class="mb-2">
|
||||||
|
<img src="<?= COVERS_URL . e($book['cover_image']) ?>"
|
||||||
|
alt="Обложка"
|
||||||
|
class="img-thumbnail"
|
||||||
|
style="max-height: 150px;">
|
||||||
|
<div class="form-text">Текущая обложка</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" class="form-control" id="cover_image" name="cover_image"
|
||||||
|
accept="image/jpeg,image/png,image/gif,image/webp">
|
||||||
|
<div class="form-text">
|
||||||
|
Разрешены форматы: JPG, PNG, GIF, WebP. Максимальный размер: 5MB. Оставьте пустым, чтобы оставить текущую обложку.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="published" name="published" value="1"
|
<input class="form-check-input" type="checkbox" id="published" name="published" value="1"
|
||||||
|
|
@ -109,7 +127,7 @@ include 'views/layouts/header.php';
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="story-editor" style="height: 500px;" data-content="<?= htmlspecialchars($book['content'] ?? '', ENT_QUOTES) ?>"></div>
|
<div id="quill-editor" style="height: 500px;" data-content="<?= htmlspecialchars($book['content'] ?? '', ENT_QUOTES) ?>"></div>
|
||||||
<textarea name="content" id="content" style="display: none;"><?= htmlspecialchars($book['content'] ?? '', ENT_QUOTES) ?></textarea>
|
<textarea name="content" id="content" style="display: none;"><?= htmlspecialchars($book['content'] ?? '', ENT_QUOTES) ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -181,6 +199,7 @@ include 'views/layouts/header.php';
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script src="<?= SITE_URL ?>/assets/js/editor.js"></script>
|
<script src="<?= SITE_URL ?>/assets/js/editor.js"></script>
|
||||||
|
<script src="<?= SITE_URL ?>/assets/js/autosave.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue