refactor: 4 модалки → LS.modal (classes ×2, library ×2)
classes.html (modal-overlay: 5 → 3):
- modal-class — создание класса
- modal-edit-assign — редактирование задания
library.html (modal-overlay: 5 → 3):
- folder-modal — создание/переименование папки
- move-modal — перемещение файла в папку
Везде один паттерн:
1. Удалить inline <div class="modal-overlay">...</div> разметку
2. Заменить openX/closeX функции на LS.modal({content, actions})
3. Сохранить state в локальной переменной _xModal вместо
document.getElementById('modal-id').classList.add('open')
4. setError() / close() через ссылку на modal-instance
5. Удалить орфанные closeX функции
Чистый эффект: −154 строки HTML/CSS дубликатов, единое поведение
ESC/backdrop/focus, accessibility (role/aria-modal) автоматически.
Осталось:
classes.html — modal-assign (128 строк, complex tabs), review-modal
library.html — folder-access-modal, assign-modal, upload-modal (все
более сложные с tabs и multi-step)
frontend/red-book.html (17 modal-overlay — отдельный заход)
flashcards (5), course (4), dashboard (2), и другие
This commit is contained in:
+42
-77
@@ -245,40 +245,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ─── Folder Modal (create / rename) ─────────────────────────────── -->
|
||||
<div class="modal-overlay" id="folder-modal" onclick="if(event.target===this)closeFolderModal()">
|
||||
<div class="modal-box" style="max-width:420px">
|
||||
<div class="modal-title" id="fm-title">Новая папка</div>
|
||||
<div class="form-row">
|
||||
<label class="form-label">Название папки</label>
|
||||
<input type="text" class="form-ctrl" id="fm-name" placeholder="Например: Биология 10 класс" maxlength="80" />
|
||||
</div>
|
||||
<div class="form-error" id="fm-error"></div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-cancel" onclick="closeFolderModal()">Отмена</button>
|
||||
<button class="btn-save" id="fm-save" onclick="saveFolderModal()">Создать</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── Move File Modal ─────────────────────────────────────────────── -->
|
||||
<div class="modal-overlay" id="move-modal" onclick="if(event.target===this)closeMoveModal()">
|
||||
<div class="modal-box" style="max-width:400px">
|
||||
<div class="modal-title">Переместить файл</div>
|
||||
<div class="form-row">
|
||||
<label class="form-label">Папка назначения</label>
|
||||
<select class="form-ctrl" id="mv-folder">
|
||||
<option value="">— Корневая папка —</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-error" id="mv-error"></div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-cancel" onclick="closeMoveModal()">Отмена</button>
|
||||
<button class="btn-save" id="mv-save" onclick="doMoveFile()">Переместить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── Folder Access Modal ─────────────────────────────────────────── -->
|
||||
<!-- ─── Folder Access Modal ─────────────────────────────────────────── -->
|
||||
<div class="modal-overlay" id="folder-access-modal" onclick="if(event.target===this)closeFolderAccess()">
|
||||
<div class="modal-box">
|
||||
<div class="modal-title">
|
||||
@@ -700,37 +667,37 @@
|
||||
|
||||
/* ─── Folder modal (create / rename) ─── */
|
||||
let _editingFolderId = null;
|
||||
let _folderModal = null;
|
||||
|
||||
function openFolderModal() {
|
||||
_editingFolderId = null;
|
||||
document.getElementById('fm-title').textContent = 'Новая папка';
|
||||
document.getElementById('fm-save').textContent = 'Создать';
|
||||
document.getElementById('fm-name').value = '';
|
||||
document.getElementById('fm-error').textContent = '';
|
||||
document.getElementById('folder-modal').classList.add('open');
|
||||
setTimeout(() => document.getElementById('fm-name').focus(), 80);
|
||||
function openFolderModal(opts = {}) {
|
||||
_editingFolderId = opts.id || null;
|
||||
const isRename = !!_editingFolderId;
|
||||
const body = `
|
||||
<div class="form-row">
|
||||
<label class="form-label">Название папки</label>
|
||||
<input type="text" class="form-ctrl" id="fm-name" placeholder="Например: Биология 10 класс" maxlength="80" value="${LS.esc(opts.name || '')}" />
|
||||
</div>`;
|
||||
_folderModal = LS.modal({
|
||||
title: isRename ? 'Переименовать папку' : 'Новая папка',
|
||||
content: body, size: 'sm',
|
||||
actions: [
|
||||
{ label: 'Отмена', onClick: () => _folderModal.close() },
|
||||
{ label: isRename ? 'Сохранить' : 'Создать', primary: true, id: 'fm-save', onClick: saveFolderModal },
|
||||
],
|
||||
});
|
||||
// Enter to submit
|
||||
const nameEl = _folderModal.body.querySelector('#fm-name');
|
||||
nameEl.addEventListener('keydown', e => { if (e.key === 'Enter') saveFolderModal(); });
|
||||
if (isRename) setTimeout(() => nameEl.select(), 50);
|
||||
}
|
||||
|
||||
function openRenameFolder(id, name) {
|
||||
_editingFolderId = id;
|
||||
document.getElementById('fm-title').textContent = 'Переименовать папку';
|
||||
document.getElementById('fm-save').textContent = 'Сохранить';
|
||||
document.getElementById('fm-name').value = name;
|
||||
document.getElementById('fm-error').textContent = '';
|
||||
document.getElementById('folder-modal').classList.add('open');
|
||||
setTimeout(() => { const n = document.getElementById('fm-name'); n.focus(); n.select(); }, 80);
|
||||
}
|
||||
|
||||
function closeFolderModal() {
|
||||
document.getElementById('folder-modal').classList.remove('open');
|
||||
openFolderModal({ id, name });
|
||||
}
|
||||
|
||||
async function saveFolderModal() {
|
||||
const name = document.getElementById('fm-name').value.trim();
|
||||
const errEl = document.getElementById('fm-error');
|
||||
errEl.textContent = '';
|
||||
if (!name) { errEl.textContent = 'Введите название папки'; return; }
|
||||
|
||||
if (!name) { _folderModal?.setError('Введите название папки'); return; }
|
||||
const btn = document.getElementById('fm-save');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
@@ -738,7 +705,6 @@
|
||||
await LS.renameFolder(_editingFolderId, name);
|
||||
const fo = allFolders.find(f => f.id === _editingFolderId);
|
||||
if (fo) fo.name = name;
|
||||
// update breadcrumb if we're inside this folder
|
||||
if (currentFolder === _editingFolderId) {
|
||||
document.getElementById('lib-bc-name').textContent = name;
|
||||
}
|
||||
@@ -746,22 +712,15 @@
|
||||
const { id } = await LS.createFolder(name);
|
||||
allFolders.push({ id, name, file_count: 0 });
|
||||
}
|
||||
closeFolderModal();
|
||||
_folderModal?.close();
|
||||
render();
|
||||
} catch (e) {
|
||||
errEl.textContent = e.message;
|
||||
_folderModal?.setError(e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Enter key in folder name input
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('fm-name').addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') saveFolderModal();
|
||||
});
|
||||
});
|
||||
|
||||
async function doDeleteFolder(id, name) {
|
||||
if (!await LS.confirm(`Удалить папку «${name}»? Файлы внутри переместятся в корневую папку.`, { title: 'Удалить папку', confirmText: 'Удалить' })) return;
|
||||
try {
|
||||
@@ -925,19 +884,25 @@
|
||||
|
||||
/* ─── Move file modal ─── */
|
||||
let _movingFileId = null;
|
||||
let _moveModal = null;
|
||||
|
||||
function openMoveModal(fileId) {
|
||||
_movingFileId = fileId;
|
||||
document.getElementById('mv-error').textContent = '';
|
||||
const sel = document.getElementById('mv-folder');
|
||||
const currentFolderId = allFiles.find(f => f.id === fileId)?.folder_id || '';
|
||||
sel.innerHTML = `<option value="">— Корневая папка —</option>` +
|
||||
const options = `<option value="">— Корневая папка —</option>` +
|
||||
allFolders.map(fo => `<option value="${fo.id}" ${fo.id === currentFolderId ? 'selected' : ''}>${esc(fo.name)}</option>`).join('');
|
||||
document.getElementById('move-modal').classList.add('open');
|
||||
}
|
||||
|
||||
function closeMoveModal() {
|
||||
document.getElementById('move-modal').classList.remove('open');
|
||||
const body = `
|
||||
<div class="form-row">
|
||||
<label class="form-label">Папка назначения</label>
|
||||
<select class="form-ctrl" id="mv-folder">${options}</select>
|
||||
</div>`;
|
||||
_moveModal = LS.modal({
|
||||
title: 'Переместить файл', content: body, size: 'sm',
|
||||
actions: [
|
||||
{ label: 'Отмена', onClick: () => _moveModal.close() },
|
||||
{ label: 'Переместить', primary: true, id: 'mv-save', onClick: doMoveFile },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async function doMoveFile() {
|
||||
@@ -948,11 +913,11 @@
|
||||
await LS.moveFile(_movingFileId, folderId ? Number(folderId) : null);
|
||||
const f = allFiles.find(f => f.id === _movingFileId);
|
||||
if (f) f.folder_id = folderId ? Number(folderId) : null;
|
||||
closeMoveModal();
|
||||
_moveModal?.close();
|
||||
render();
|
||||
LS.toast('Файл перемещён', 'success');
|
||||
} catch (e) {
|
||||
document.getElementById('mv-error').textContent = e.message;
|
||||
_moveModal?.setError(e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user