Files
ave-cms/adminx/modules/Themes/assets/themes.js
T
2026-07-27 12:58:44 +03:00

397 lines
19 KiB
JavaScript

(function (window, document) {
'use strict';
var Adminx = window.Adminx || (window.Adminx = {});
Adminx.Themes = {
root: null,
currentRevisionId: 0,
draggedRow: null,
settingsDrag: null,
init: function () {
this.root = document.querySelector('[data-themes-root]');
if (!this.root) { return; }
this.bindThemePicker();
this.bindActions();
this.bindForms();
this.bindManifest();
this.bindSettings();
this.restoreTab();
},
base: function () { return this.root ? this.root.getAttribute('data-base') : Adminx.base(); },
theme: function () { return this.root ? this.root.getAttribute('data-theme') : ''; },
bindThemePicker: function () {
var select = document.querySelector('[data-theme-select]');
if (!select) { return; }
select.addEventListener('change', function () {
window.location.href = Adminx.base() + '/themes?theme=' + encodeURIComponent(select.value);
});
},
post: function (url, formData, options) {
options = options || {};
Adminx.Loader.show();
return Adminx.Ajax.post(url, formData).then(function (payload) {
Adminx.Loader.hide();
var data = payload.data || {};
if (!data.success) {
Adminx.Toast.show(data.message || 'Не удалось выполнить действие', 'error');
return null;
}
if (data.message) { Adminx.Toast.show(data.message, 'success'); }
if (data.redirect) { window.location.href = data.redirect; return data; }
if (options.reload) { window.location.reload(); }
return data;
}).catch(function () {
Adminx.Loader.hide();
Adminx.Toast.show('Сервер недоступен', 'error');
return null;
});
},
formData: function (values) {
var data = new FormData();
Object.keys(values || {}).forEach(function (key) { data.set(key, values[key]); });
return data;
},
bindActions: function () {
var self = this;
document.addEventListener('click', function (event) {
var tab = event.target.closest('[data-tab-target]');
if (tab && tab.closest('[data-themes-root]')) {
window.history.replaceState(null, '', '#' + tab.getAttribute('data-tab-target'));
}
var edit = event.target.closest('[data-theme-file-edit]');
if (edit) { self.openFile(edit.closest('[data-theme-path]')); return; }
var remove = event.target.closest('[data-theme-path-delete]');
if (remove) { self.deletePath(remove.closest('[data-theme-path]')); return; }
var kind = event.target.closest('[data-create-kind]');
if (kind) { self.configureCreateAsset(kind.getAttribute('data-create-kind')); return; }
var activate = event.target.closest('[data-theme-activate]');
if (activate) { self.activate(); return; }
if (event.target.closest('[data-theme-delete]')) { self.deleteTheme(); return; }
var revision = event.target.closest('[data-theme-revision-open]');
if (revision) { self.openRevision(revision.closest('[data-revision-id]')); return; }
var deleteRevision = event.target.closest('[data-theme-revision-delete]');
if (deleteRevision) { self.deleteRevision(deleteRevision.closest('[data-revision-id]')); return; }
if (event.target.closest('[data-theme-revisions-clear]')) { self.clearRevisions(); return; }
if (event.target.closest('[data-theme-revision-restore]')) { self.restoreRevision(); }
});
var upload = document.querySelector('[data-theme-upload]');
if (upload) {
upload.addEventListener('change', function () {
if (!upload.files.length) { return; }
var data = new FormData();
data.set('theme', self.theme());
data.set('directory', self.root.getAttribute('data-directory') || '');
Array.prototype.forEach.call(upload.files, function (file) { data.append('files[]', file); });
self.post(self.base() + '/themes/upload', data, { reload: true });
});
}
},
restoreTab: function () {
var name = window.location.hash.replace(/^#/, '');
if (!/^(files|connections|settings|revisions)$/.test(name)) { return; }
var tabs = this.root.querySelector('[data-tabs]');
if (tabs && Adminx.Tabs) { Adminx.Tabs.activate(tabs, name); }
},
bindForms: function () {
var self = this;
var fileForm = document.querySelector('[data-theme-file-form]');
if (fileForm) {
fileForm.addEventListener('submit', function (event) {
event.preventDefault();
if (Adminx.CodeEditor) { Adminx.CodeEditor.syncAll(fileForm); }
self.post(self.base() + '/themes/file', new FormData(fileForm)).then(function (data) {
if (data) { fileForm.dataset.clean = fileForm.elements.content.value; }
});
});
}
this.bindAjaxForm('[data-theme-create-asset]', function (form) {
return form.elements.kind.value === 'folder' ? '/themes/folders/create' : '/themes/files/create';
}, true);
this.bindAjaxForm('[data-theme-create-form]', function () { return '/themes/create'; });
this.bindAjaxForm('[data-theme-import-form]', function () { return '/themes/import'; });
var manifest = document.querySelector('[data-theme-manifest-form]');
if (manifest) {
manifest.addEventListener('submit', function (event) {
event.preventDefault();
self.indexManifest(manifest);
self.post(self.base() + '/themes/manifest', new FormData(manifest), { reload: true });
});
}
var settings = document.querySelector('[data-theme-settings-form]');
if (settings) {
settings.addEventListener('submit', function (event) {
event.preventDefault();
settings.querySelectorAll('[data-theme-order]').forEach(function (list) { self.syncThemeOrder(list); });
self.post(self.base() + '/themes/settings', new FormData(settings), { reload: true });
});
}
},
bindAjaxForm: function (selector, path, reload) {
var self = this;
var form = document.querySelector(selector);
if (!form) { return; }
form.addEventListener('submit', function (event) {
event.preventDefault();
self.post(self.base() + path(form), new FormData(form), { reload: !!reload });
});
},
configureCreateAsset: function (kind) {
var form = document.querySelector('[data-theme-create-asset]');
if (!form) { return; }
var folder = kind === 'folder';
form.reset();
form.elements.theme.value = this.theme();
form.elements.directory.value = this.root.getAttribute('data-directory') || '';
form.elements.kind.value = folder ? 'folder' : 'file';
document.querySelector('[data-asset-create-title]').textContent = folder ? 'Создать папку' : 'Создать файл';
document.querySelector('[data-asset-create-hint]').textContent = folder ? 'Имя без точки и служебных символов.' : 'Расширения: css, js, json, svg, xml, txt.';
form.elements.name.placeholder = folder ? 'components' : 'styles.css';
},
openFile: function (row) {
if (!row) { return; }
var self = this;
var url = this.base() + '/themes/file?theme=' + encodeURIComponent(this.theme()) + '&path=' + encodeURIComponent(row.dataset.themePath);
Adminx.Loader.show();
Adminx.Ajax.request(url).then(function (payload) {
Adminx.Loader.hide();
var response = payload.data || {};
if (!response.success) { Adminx.Toast.show(response.message || 'Файл не найден', 'error'); return; }
var item = response.data || {};
var form = document.querySelector('[data-theme-file-form]');
form.elements.path.value = item.path || '';
form.elements.content.value = item.content || '';
form.dataset.clean = item.content || '';
document.querySelector('[data-file-title]').textContent = item.name || 'Редактор файла';
document.querySelector('[data-file-meta]').textContent = (item.path || '') + ' · ' + (item.size_label || '') + ' · ' + (item.modified_label || '');
var textarea = form.elements.content;
textarea.setAttribute('data-mode', item.mode || 'text/plain');
if (textarea._adminxCodeMirror) {
textarea._adminxCodeMirror.setOption('mode', item.mode || 'text/plain');
textarea._adminxCodeMirror.setValue(item.content || '');
}
Adminx.Drawer.open('themeFileDrawer');
window.setTimeout(function () { if (Adminx.CodeEditor) { Adminx.CodeEditor.refreshAll(); } }, 80);
}).catch(function () { Adminx.Loader.hide(); Adminx.Toast.show('Сервер недоступен', 'error'); });
},
deletePath: function (row) {
if (!row) { return; }
var self = this;
Adminx.Confirm.open({
kind: 'error', title: row.dataset.themeType === 'directory' ? 'Удалить папку?' : 'Удалить файл?',
message: '«' + row.dataset.themeName + '» будет удалён из темы. Текстовая версия останется в ревизиях.',
confirmLabel: 'Удалить', confirmClass: 'btn-danger',
onConfirm: function () {
self.post(self.base() + '/themes/path/delete', self.formData({ theme: self.theme(), path: row.dataset.themePath }), { reload: true });
}
});
},
activate: function () {
var self = this;
Adminx.Confirm.open({
kind: 'warning', title: 'Активировать тему?',
message: 'Публичный сайт начнёт использовать тему «' + this.theme() + '». Подключения применятся только там, где установлены новые теги темы.',
confirmLabel: 'Активировать',
onConfirm: function () { self.post(self.base() + '/themes/activate', self.formData({ theme: self.theme() }), { reload: true }); }
});
},
deleteTheme: function () {
var self = this;
Adminx.Confirm.open({
kind: 'error', title: 'Удалить тему?',
message: 'Каталог «' + this.theme() + '», его ассеты и ревизии будут удалены без возможности восстановления.',
confirmLabel: 'Удалить тему', confirmClass: 'btn-danger',
onConfirm: function () { self.post(self.base() + '/themes/delete', self.formData({ theme: self.theme() })); }
});
},
bindManifest: function () {
var self = this;
document.addEventListener('click', function (event) {
var add = event.target.closest('[data-manifest-add]');
if (add) {
var kind = add.getAttribute('data-manifest-add');
var template = document.getElementById(kind === 'style' ? 'themeStyleRow' : 'themeScriptRow');
var list = document.querySelector('[data-manifest-list="' + kind + '"]');
if (template && list) { list.appendChild(template.content.cloneNode(true)); }
return;
}
var remove = event.target.closest('[data-manifest-remove]');
if (remove) { remove.closest('[data-manifest-row]').remove(); }
});
document.querySelectorAll('[data-manifest-row]').forEach(function (row) { self.makeDraggable(row); });
document.addEventListener('mouseover', function (event) {
var row = event.target.closest('[data-manifest-row]');
if (row && !row.hasAttribute('draggable')) { self.makeDraggable(row); }
});
},
bindSettings: function () {
var self = this;
var form = document.querySelector('[data-theme-settings-form]');
if (!form) { return; }
form.addEventListener('change', function (event) {
if (event.target.matches('input[name="presentation_mode"]')) {
form.querySelectorAll('.themes-mode-card').forEach(function (card) {
var radio = card.querySelector('input[name="presentation_mode"]');
card.classList.toggle('is-selected', !!(radio && radio.checked));
});
}
if (event.target.matches('[data-theme-order-visible]')) {
var row = event.target.closest('[data-theme-order-item]');
row.classList.toggle('is-disabled', !event.target.checked);
self.syncThemeOrder(row.parentNode);
}
});
form.addEventListener('click', function (event) {
var button = event.target.closest('[data-theme-order-move]');
if (!button || button.disabled) { return; }
var row = button.closest('[data-theme-order-item]');
if (button.getAttribute('data-theme-order-move') === 'up' && row.previousElementSibling) {
row.parentNode.insertBefore(row, row.previousElementSibling);
} else if (button.getAttribute('data-theme-order-move') === 'down' && row.nextElementSibling) {
row.parentNode.insertBefore(row.nextElementSibling, row);
}
self.syncThemeOrder(row.parentNode);
});
form.addEventListener('dragstart', function (event) {
var handle = event.target.closest('[data-theme-order-handle]');
var row = handle ? handle.closest('[data-theme-order-item]') : null;
if (!row || row.getAttribute('draggable') !== 'true') { event.preventDefault(); return; }
self.settingsDrag = row;
row.classList.add('is-dragging');
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', row.getAttribute('data-code') || '');
}
});
form.addEventListener('dragover', function (event) {
if (!self.settingsDrag) { return; }
var target = event.target.closest('[data-theme-order-item]');
var list = event.target.closest('[data-theme-order]');
if (!target || !list || target === self.settingsDrag || self.settingsDrag.parentNode !== list) { return; }
event.preventDefault();
var box = target.getBoundingClientRect();
list.insertBefore(self.settingsDrag, event.clientY < box.top + box.height / 2 ? target : target.nextSibling);
self.syncThemeOrder(list);
});
form.addEventListener('dragend', function () {
if (!self.settingsDrag) { return; }
var list = self.settingsDrag.parentNode;
self.settingsDrag.classList.remove('is-dragging');
self.settingsDrag = null;
self.syncThemeOrder(list);
});
},
syncThemeOrder: function (list) {
if (!list) { return; }
var input = list.parentNode.querySelector('[data-theme-order-value]');
if (!input) { return; }
input.value = JSON.stringify(Array.prototype.map.call(list.querySelectorAll('[data-theme-order-item]'), function (row) {
var toggle = row.querySelector('[data-theme-order-visible]');
return { code: row.getAttribute('data-code') || '', visible: !!(toggle && toggle.checked) };
}));
},
makeDraggable: function (row) {
var self = this;
row.setAttribute('draggable', 'true');
row.addEventListener('dragstart', function () { self.draggedRow = row; row.classList.add('is-dragging'); });
row.addEventListener('dragend', function () { row.classList.remove('is-dragging'); self.draggedRow = null; });
row.addEventListener('dragover', function (event) {
if (!self.draggedRow || self.draggedRow.parentNode !== row.parentNode || self.draggedRow === row) { return; }
event.preventDefault();
var box = row.getBoundingClientRect();
row.parentNode.insertBefore(self.draggedRow, event.clientY < box.top + box.height / 2 ? row : row.nextSibling);
});
},
indexManifest: function (form) {
form.querySelectorAll('[data-manifest-list]').forEach(function (list) {
var kind = list.getAttribute('data-manifest-list') === 'style' ? 'styles' : 'scripts';
list.querySelectorAll('[data-manifest-row]').forEach(function (row, index) {
row.querySelectorAll('[data-field]').forEach(function (field) {
field.name = kind + '[' + index + '][' + field.getAttribute('data-field') + ']';
});
});
});
},
openRevision: function (row) {
if (!row) { return; }
var self = this;
Adminx.Ajax.request(this.base() + '/themes/revisions/' + row.dataset.revisionId).then(function (payload) {
var response = payload.data || {};
if (!response.success) { Adminx.Toast.show(response.message || 'Ревизия не найдена', 'error'); return; }
var item = response.data || {};
self.currentRevisionId = item.id || 0;
document.querySelector('[data-revision-title]').textContent = item.path || 'Ревизия';
document.querySelector('[data-revision-meta]').textContent = (item.action_label || '') + ' · ' + (item.author_name || 'Система') + ' · ' + (item.created_label || '');
var textarea = document.querySelector('[data-revision-content]');
textarea.value = item.content || '';
if (textarea._adminxCodeMirror) { textarea._adminxCodeMirror.setValue(item.content || ''); }
Adminx.Drawer.open('themeRevisionDrawer');
window.setTimeout(function () { if (Adminx.CodeEditor) { Adminx.CodeEditor.refreshAll(); } }, 80);
});
},
restoreRevision: function () {
if (!this.currentRevisionId) { return; }
var self = this;
Adminx.Confirm.open({
kind: 'warning', title: 'Восстановить ревизию?', message: 'Текущее содержимое файла сначала будет сохранено как отдельная ревизия.',
confirmLabel: 'Восстановить',
onConfirm: function () { self.post(self.base() + '/themes/revisions/' + self.currentRevisionId + '/restore', new FormData(), { reload: true }); }
});
},
deleteRevision: function (row) {
if (!row) { return; }
var self = this;
Adminx.Confirm.open({ kind: 'error', title: 'Удалить ревизию?', message: 'Версию файла нельзя будет восстановить.', confirmLabel: 'Удалить', confirmClass: 'btn-danger', onConfirm: function () { self.post(self.base() + '/themes/revisions/' + row.dataset.revisionId + '/delete', new FormData(), { reload: true }); } });
},
clearRevisions: function () {
var self = this;
Adminx.Confirm.open({ kind: 'error', title: 'Удалить все ревизии темы?', message: 'История текстовых файлов будет очищена без возможности восстановления.', confirmLabel: 'Удалить все', confirmClass: 'btn-danger', onConfirm: function () { self.post(self.base() + '/themes/revisions/clear', self.formData({ theme: self.theme(), path: '' }), { reload: true }); } });
}
};
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { Adminx.Themes.init(); }); }
else { Adminx.Themes.init(); }
})(window, document);