8e6b6d0cdd
Build / Build & Release draft (push) Failing after 0s
PHP tests / PHP Syntax check 5.6 => 8.1 (push) Failing after 33s
PHP tests / PHPStan (1.7.1.2) (push) Has been cancelled
PHP tests / PHPStan (1.7.2.5) (push) Has been cancelled
PHP tests / PHPStan (1.7.3.4) (push) Has been cancelled
PHP tests / PHPStan (1.7.4.4) (push) Has been cancelled
PHP tests / PHPStan (1.7.5.1) (push) Has been cancelled
PHP tests / PHPStan (1.7.6) (push) Has been cancelled
PHP tests / PHPStan (1.7.7) (push) Has been cancelled
PHP tests / PHPStan (1.7.8) (push) Has been cancelled
PHP tests / PHPStan (latest) (push) Has been cancelled
PHP tests / PHP-CS-Fixer (push) Has been cancelled
427 lines
16 KiB
JavaScript
427 lines
16 KiB
JavaScript
(function () {
|
||
function boot() {
|
||
var root = document.getElementById('adv-megamenu-admin');
|
||
if (!root) {
|
||
return;
|
||
}
|
||
|
||
var treeContainer = root.querySelector('.js-tree');
|
||
var treeInput = document.getElementById('advmegamenu_tree_json');
|
||
var ajaxUrl = root.getAttribute('data-ajax-url');
|
||
var token = root.getAttribute('data-token');
|
||
var languages = JSON.parse((root.querySelector('.js-adv-languages-data') || {}).textContent || '[]');
|
||
var tree = JSON.parse((root.querySelector('.js-adv-tree-data') || {}).textContent || '[]');
|
||
var tempId = Date.now();
|
||
|
||
function nextId() {
|
||
tempId += 1;
|
||
return tempId;
|
||
}
|
||
|
||
function createEmptyNode() {
|
||
var lang = {};
|
||
languages.forEach(function (language) {
|
||
lang[language.id_lang] = {
|
||
title: '',
|
||
description: '',
|
||
custom_link: ''
|
||
};
|
||
});
|
||
|
||
return {
|
||
id: nextId(),
|
||
active: true,
|
||
type: 'category',
|
||
entity_id: 0,
|
||
icon_path: '',
|
||
icon_url: '',
|
||
new_window: false,
|
||
lang: lang,
|
||
layouts: [],
|
||
children: []
|
||
};
|
||
}
|
||
|
||
function createEmptyLayout() {
|
||
return {
|
||
id: nextId(),
|
||
column_width: 4,
|
||
show_title: true,
|
||
custom_image: '',
|
||
custom_image_url: '',
|
||
background_color: '',
|
||
block_type: 'promo',
|
||
products: []
|
||
};
|
||
}
|
||
|
||
function serializeTree() {
|
||
treeInput.value = JSON.stringify(tree);
|
||
}
|
||
|
||
function render() {
|
||
serializeTree();
|
||
treeContainer.innerHTML = '';
|
||
tree.forEach(function (node, index) {
|
||
treeContainer.appendChild(renderNode(node, tree, index, 0));
|
||
});
|
||
}
|
||
|
||
function move(collection, index, direction) {
|
||
var target = direction === 'up' ? index - 1 : index + 1;
|
||
if (target < 0 || target >= collection.length) {
|
||
return;
|
||
}
|
||
|
||
var current = collection[index];
|
||
collection[index] = collection[target];
|
||
collection[target] = current;
|
||
render();
|
||
}
|
||
|
||
function remove(collection, index) {
|
||
collection.splice(index, 1);
|
||
render();
|
||
}
|
||
|
||
function renderNode(node, collection, index, depth) {
|
||
var wrapper = document.createElement('section');
|
||
wrapper.className = 'adv-menu-node';
|
||
|
||
var title = getNodeTitle(node);
|
||
wrapper.innerHTML = '' +
|
||
'<div class="adv-menu-node__header">' +
|
||
' <strong>' + escapeHtml(title || 'Menu item') + '</strong>' +
|
||
' <div class="adv-menu-node__actions">' +
|
||
' <button type="button" class="btn btn-default btn-xs js-move-up">↑</button>' +
|
||
' <button type="button" class="btn btn-default btn-xs js-move-down">↓</button>' +
|
||
' <button type="button" class="btn btn-default btn-xs js-add-child">+ child</button>' +
|
||
' <button type="button" class="btn btn-default btn-xs js-add-layout">+ block</button>' +
|
||
' <button type="button" class="btn btn-danger btn-xs js-remove-node">×</button>' +
|
||
' </div>' +
|
||
'</div>' +
|
||
'<div class="adv-menu-node__body">' +
|
||
' <div class="adv-menu-node__grid">' +
|
||
' <label><span>Type</span><select class="form-control js-node-type">' +
|
||
' <option value="category">category</option>' +
|
||
' <option value="cms">cms</option>' +
|
||
' <option value="link">link</option>' +
|
||
' <option value="rich_content">rich_content</option>' +
|
||
' </select></label>' +
|
||
' <label><span>Entity ID</span><input type="number" class="form-control js-entity-id" min="0"></label>' +
|
||
' <label><span>Active</span><input type="checkbox" class="js-active"></label>' +
|
||
' <label><span>New window</span><input type="checkbox" class="js-new-window"></label>' +
|
||
' <label><span>Icon upload</span><input type="file" class="js-icon-upload" accept="image/*"><input type="hidden" class="js-icon-path"></label>' +
|
||
' <div class="js-icon-preview-wrap"></div>' +
|
||
' </div>' +
|
||
' <div class="adv-menu-node__languages"></div>' +
|
||
' <div class="adv-menu-node__layouts"></div>' +
|
||
' <div class="adv-menu-node__children"></div>' +
|
||
'</div>';
|
||
|
||
wrapper.querySelector('.js-node-type').value = node.type || 'category';
|
||
wrapper.querySelector('.js-entity-id').value = node.entity_id || 0;
|
||
wrapper.querySelector('.js-active').checked = !!node.active;
|
||
wrapper.querySelector('.js-new-window').checked = !!node.new_window;
|
||
wrapper.querySelector('.js-icon-path').value = node.icon_path || '';
|
||
|
||
var iconPreviewWrap = wrapper.querySelector('.js-icon-preview-wrap');
|
||
renderImagePreview(iconPreviewWrap, node.icon_url);
|
||
|
||
wrapper.querySelector('.js-node-type').addEventListener('change', function (event) {
|
||
node.type = event.target.value;
|
||
render();
|
||
});
|
||
wrapper.querySelector('.js-entity-id').addEventListener('input', function (event) {
|
||
node.entity_id = parseInt(event.target.value || '0', 10);
|
||
serializeTree();
|
||
});
|
||
wrapper.querySelector('.js-active').addEventListener('change', function (event) {
|
||
node.active = event.target.checked;
|
||
serializeTree();
|
||
});
|
||
wrapper.querySelector('.js-new-window').addEventListener('change', function (event) {
|
||
node.new_window = event.target.checked;
|
||
serializeTree();
|
||
});
|
||
|
||
wrapper.querySelector('.js-icon-upload').addEventListener('change', function (event) {
|
||
uploadImage(event.target.files[0], 'menu_icon', function (response) {
|
||
node.icon_path = response.path;
|
||
node.icon_url = response.url;
|
||
serializeTree();
|
||
renderImagePreview(iconPreviewWrap, response.url);
|
||
});
|
||
});
|
||
|
||
wrapper.querySelector('.js-move-up').addEventListener('click', function () {
|
||
move(collection, index, 'up');
|
||
});
|
||
wrapper.querySelector('.js-move-down').addEventListener('click', function () {
|
||
move(collection, index, 'down');
|
||
});
|
||
wrapper.querySelector('.js-remove-node').addEventListener('click', function () {
|
||
remove(collection, index);
|
||
});
|
||
wrapper.querySelector('.js-add-child').addEventListener('click', function () {
|
||
node.children = node.children || [];
|
||
node.children.push(createEmptyNode());
|
||
render();
|
||
});
|
||
wrapper.querySelector('.js-add-layout').addEventListener('click', function () {
|
||
node.layouts = node.layouts || [];
|
||
node.layouts.push(createEmptyLayout());
|
||
render();
|
||
});
|
||
|
||
var langWrap = wrapper.querySelector('.adv-menu-node__languages');
|
||
languages.forEach(function (language) {
|
||
var langId = String(language.id_lang);
|
||
if (!node.lang[langId]) {
|
||
node.lang[langId] = { title: '', description: '', custom_link: '' };
|
||
}
|
||
|
||
var box = document.createElement('div');
|
||
box.className = 'adv-menu-language';
|
||
box.innerHTML = '' +
|
||
'<h4>' + escapeHtml(language.iso_code + ' / ' + language.name) + '</h4>' +
|
||
'<label><span>Title</span><input type="text" class="form-control js-lang-title"></label>' +
|
||
'<label><span>Description</span><textarea class="form-control js-lang-description" rows="3"></textarea></label>' +
|
||
'<label><span>Custom link</span><input type="text" class="form-control js-lang-link"></label>';
|
||
|
||
box.querySelector('.js-lang-title').value = node.lang[langId].title || '';
|
||
box.querySelector('.js-lang-description').value = node.lang[langId].description || '';
|
||
box.querySelector('.js-lang-link').value = node.lang[langId].custom_link || '';
|
||
box.querySelector('.js-lang-title').addEventListener('input', function (event) {
|
||
node.lang[langId].title = event.target.value;
|
||
serializeTree();
|
||
});
|
||
box.querySelector('.js-lang-description').addEventListener('input', function (event) {
|
||
node.lang[langId].description = event.target.value;
|
||
serializeTree();
|
||
});
|
||
box.querySelector('.js-lang-link').addEventListener('input', function (event) {
|
||
node.lang[langId].custom_link = event.target.value;
|
||
serializeTree();
|
||
});
|
||
|
||
langWrap.appendChild(box);
|
||
});
|
||
|
||
var layoutsWrap = wrapper.querySelector('.adv-menu-node__layouts');
|
||
(node.layouts || []).forEach(function (layout, layoutIndex) {
|
||
layoutsWrap.appendChild(renderLayout(node.layouts, layout, layoutIndex));
|
||
});
|
||
|
||
var childrenWrap = wrapper.querySelector('.adv-menu-node__children');
|
||
(node.children || []).forEach(function (child, childIndex) {
|
||
childrenWrap.appendChild(renderNode(child, node.children, childIndex, depth + 1));
|
||
});
|
||
|
||
return wrapper;
|
||
}
|
||
|
||
function renderLayout(layouts, layout, index) {
|
||
var wrapper = document.createElement('section');
|
||
wrapper.className = 'adv-menu-layout';
|
||
wrapper.innerHTML = '' +
|
||
'<div class="adv-menu-layout__header">' +
|
||
' <strong>Rich block</strong>' +
|
||
' <div class="adv-menu-layout__actions">' +
|
||
' <button type="button" class="btn btn-default btn-xs js-layout-up">↑</button>' +
|
||
' <button type="button" class="btn btn-default btn-xs js-layout-down">↓</button>' +
|
||
' <button type="button" class="btn btn-danger btn-xs js-remove-layout">×</button>' +
|
||
' </div>' +
|
||
'</div>' +
|
||
'<div class="adv-menu-layout__body">' +
|
||
' <div class="adv-menu-layout__grid">' +
|
||
' <label><span>Width</span><input type="number" class="form-control js-layout-width" min="1" max="12"></label>' +
|
||
' <label><span>Block type</span><select class="form-control js-layout-type"><option value="promo">promo</option><option value="products">products</option></select></label>' +
|
||
' <label><span>Show title</span><input type="checkbox" class="js-layout-title"></label>' +
|
||
' <label><span>Background color</span><input type="text" class="form-control js-layout-bg" placeholder="#f3efe8"></label>' +
|
||
' <label><span>Image upload</span><input type="file" class="js-layout-upload" accept="image/*"></label>' +
|
||
' <div class="js-layout-preview-wrap"></div>' +
|
||
' </div>' +
|
||
' <label><span>Product search</span><input type="text" class="form-control js-product-search" placeholder="Search product by name, reference or ID"></label>' +
|
||
' <div class="adv-menu-product-results js-product-results"></div>' +
|
||
' <div class="adv-menu-products js-products"></div>' +
|
||
'</div>';
|
||
|
||
wrapper.querySelector('.js-layout-width').value = layout.column_width || 4;
|
||
wrapper.querySelector('.js-layout-type').value = layout.block_type || 'promo';
|
||
wrapper.querySelector('.js-layout-title').checked = !!layout.show_title;
|
||
wrapper.querySelector('.js-layout-bg').value = layout.background_color || '';
|
||
|
||
var previewWrap = wrapper.querySelector('.js-layout-preview-wrap');
|
||
renderImagePreview(previewWrap, layout.custom_image_url);
|
||
|
||
wrapper.querySelector('.js-layout-width').addEventListener('input', function (event) {
|
||
layout.column_width = parseInt(event.target.value || '4', 10);
|
||
serializeTree();
|
||
});
|
||
wrapper.querySelector('.js-layout-type').addEventListener('change', function (event) {
|
||
layout.block_type = event.target.value;
|
||
serializeTree();
|
||
});
|
||
wrapper.querySelector('.js-layout-title').addEventListener('change', function (event) {
|
||
layout.show_title = event.target.checked;
|
||
serializeTree();
|
||
});
|
||
wrapper.querySelector('.js-layout-bg').addEventListener('input', function (event) {
|
||
layout.background_color = event.target.value;
|
||
serializeTree();
|
||
});
|
||
wrapper.querySelector('.js-layout-upload').addEventListener('change', function (event) {
|
||
uploadImage(event.target.files[0], 'layout_image', function (response) {
|
||
layout.custom_image = response.path;
|
||
layout.custom_image_url = response.url;
|
||
serializeTree();
|
||
renderImagePreview(previewWrap, response.url);
|
||
});
|
||
});
|
||
|
||
wrapper.querySelector('.js-layout-up').addEventListener('click', function () {
|
||
move(layouts, index, 'up');
|
||
});
|
||
wrapper.querySelector('.js-layout-down').addEventListener('click', function () {
|
||
move(layouts, index, 'down');
|
||
});
|
||
wrapper.querySelector('.js-remove-layout').addEventListener('click', function () {
|
||
remove(layouts, index);
|
||
});
|
||
|
||
var productsWrap = wrapper.querySelector('.js-products');
|
||
renderProducts(layout.products || [], productsWrap, function (nextProducts) {
|
||
layout.products = nextProducts;
|
||
serializeTree();
|
||
});
|
||
|
||
var resultsWrap = wrapper.querySelector('.js-product-results');
|
||
var searchInput = wrapper.querySelector('.js-product-search');
|
||
var searchTimer = null;
|
||
searchInput.addEventListener('input', function () {
|
||
clearTimeout(searchTimer);
|
||
var value = searchInput.value.trim();
|
||
if (value.length < 2) {
|
||
resultsWrap.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
searchTimer = window.setTimeout(function () {
|
||
fetch(ajaxUrl + '&ajax=1&action=searchProducts&q=' + encodeURIComponent(value))
|
||
.then(function (response) { return response.json(); })
|
||
.then(function (payload) {
|
||
resultsWrap.innerHTML = '';
|
||
(payload.results || []).forEach(function (result) {
|
||
var button = document.createElement('button');
|
||
button.type = 'button';
|
||
button.textContent = result.label;
|
||
button.addEventListener('click', function () {
|
||
layout.products = layout.products || [];
|
||
if (layout.products.indexOf(result.id) === -1) {
|
||
layout.products.push(result.id);
|
||
}
|
||
render();
|
||
});
|
||
resultsWrap.appendChild(button);
|
||
});
|
||
});
|
||
}, 200);
|
||
});
|
||
|
||
return wrapper;
|
||
}
|
||
|
||
function renderProducts(products, wrap, onChange) {
|
||
wrap.innerHTML = '';
|
||
products.forEach(function (productId, index) {
|
||
var chip = document.createElement('span');
|
||
chip.className = 'adv-menu-product-chip';
|
||
chip.innerHTML = '<span>#' + escapeHtml(String(productId)) + '</span><button type="button">×</button>';
|
||
chip.querySelector('button').addEventListener('click', function () {
|
||
var nextProducts = products.slice();
|
||
nextProducts.splice(index, 1);
|
||
onChange(nextProducts);
|
||
render();
|
||
});
|
||
wrap.appendChild(chip);
|
||
});
|
||
}
|
||
|
||
function renderImagePreview(wrap, url) {
|
||
wrap.innerHTML = '';
|
||
if (!url) {
|
||
return;
|
||
}
|
||
|
||
var image = document.createElement('img');
|
||
image.src = url;
|
||
image.className = 'adv-menu-image-preview';
|
||
wrap.appendChild(image);
|
||
}
|
||
|
||
function getNodeTitle(node) {
|
||
var firstLang = languages[0] ? String(languages[0].id_lang) : null;
|
||
if (firstLang && node.lang[firstLang] && node.lang[firstLang].title) {
|
||
return node.lang[firstLang].title;
|
||
}
|
||
|
||
var langIds = Object.keys(node.lang || {});
|
||
if (langIds.length && node.lang[langIds[0]]) {
|
||
return node.lang[langIds[0]].title || '';
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
function uploadImage(file, preset, callback) {
|
||
if (!file) {
|
||
return;
|
||
}
|
||
|
||
var formData = new FormData();
|
||
formData.append('ajax', '1');
|
||
formData.append('action', 'uploadImage');
|
||
formData.append('token', token);
|
||
formData.append('preset', preset || 'default');
|
||
formData.append('image', file);
|
||
|
||
fetch(ajaxUrl, {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(function (response) { return response.json(); })
|
||
.then(function (payload) {
|
||
if (payload && !payload.error) {
|
||
callback(payload);
|
||
}
|
||
});
|
||
}
|
||
|
||
function escapeHtml(value) {
|
||
return value
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
var addRootButton = root.querySelector('.js-add-root-node');
|
||
if (addRootButton) {
|
||
addRootButton.addEventListener('click', function () {
|
||
tree.push(createEmptyNode());
|
||
render();
|
||
});
|
||
}
|
||
|
||
render();
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', boot);
|
||
} else {
|
||
boot();
|
||
}
|
||
})();
|