feat(structure): admin screen with validated save and term sync
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
162
viator-collector/assets/js/vc-structure-admin.js
Normal file
162
viator-collector/assets/js/vc-structure-admin.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Settings → Viator Structure editor. Regions and cities are plain DOM
|
||||
* blocks; cities can only be added from the Viator destination search, and
|
||||
* indexed field names are assigned on submit.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var cfg = window.vcStructure || {};
|
||||
var i18n = cfg.i18n || {};
|
||||
var form = document.getElementById('vc-structure-form');
|
||||
var list = document.getElementById('vc-regions');
|
||||
if (!form || !list) {
|
||||
return;
|
||||
}
|
||||
|
||||
function slugify(s) {
|
||||
return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function fromTemplate(id) {
|
||||
var tpl = document.getElementById(id);
|
||||
return tpl.content.firstElementChild.cloneNode(true);
|
||||
}
|
||||
|
||||
function usedIds() {
|
||||
var ids = {};
|
||||
list.querySelectorAll('.vc-city [data-f="id"]').forEach(function (input) {
|
||||
ids[input.value] = true;
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function move(el, dir) {
|
||||
if (dir < 0 && el.previousElementSibling) {
|
||||
el.parentNode.insertBefore(el, el.previousElementSibling);
|
||||
} else if (dir > 0 && el.nextElementSibling) {
|
||||
el.parentNode.insertBefore(el.nextElementSibling, el);
|
||||
}
|
||||
}
|
||||
|
||||
function addCity(region, item) {
|
||||
var row = fromTemplate('vc-tpl-city');
|
||||
row.querySelector('[data-f="id"]').value = item.id;
|
||||
row.querySelector('[data-f="name"]').value = item.name;
|
||||
row.querySelector('[data-f="slug"]').value = slugify(item.name);
|
||||
row.querySelector('.vc-city__name').textContent = item.name;
|
||||
row.querySelector('.vc-city__id').textContent = item.id;
|
||||
region.querySelector('.vc-cities tbody').appendChild(row);
|
||||
}
|
||||
|
||||
function search(region) {
|
||||
var q = region.querySelector('.vc-city-search input[type="search"]');
|
||||
var out = region.querySelector('.vc-results');
|
||||
out.textContent = i18n.searching || '…';
|
||||
|
||||
var data = new FormData();
|
||||
data.append('action', 'vc_search_destinations');
|
||||
data.append('_ajax_nonce', cfg.nonce);
|
||||
data.append('q', q.value);
|
||||
|
||||
fetch(cfg.ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (!res || !res.success) {
|
||||
out.textContent = (res && res.data && res.data.message) || i18n.failed;
|
||||
return;
|
||||
}
|
||||
if (!res.data.results.length) {
|
||||
out.textContent = i18n.noMatches;
|
||||
return;
|
||||
}
|
||||
var used = usedIds();
|
||||
out.innerHTML = '';
|
||||
res.data.results.forEach(function (item) {
|
||||
var row = document.createElement('div');
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'button button-small';
|
||||
if (used[item.id]) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = i18n.used;
|
||||
} else {
|
||||
btn.textContent = i18n.add;
|
||||
btn.addEventListener('click', function () {
|
||||
addCity(region, item);
|
||||
out.innerHTML = '';
|
||||
q.value = '';
|
||||
});
|
||||
}
|
||||
var label = document.createElement('span');
|
||||
label.textContent = item.name + ' (ID ' + item.id + ')';
|
||||
row.appendChild(btn);
|
||||
row.appendChild(label);
|
||||
out.appendChild(row);
|
||||
});
|
||||
})
|
||||
.catch(function () { out.textContent = i18n.failed; });
|
||||
}
|
||||
|
||||
list.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-act]');
|
||||
if (!btn) {
|
||||
return;
|
||||
}
|
||||
var region = btn.closest('.vc-region');
|
||||
var city = btn.closest('.vc-city');
|
||||
var target = city || region;
|
||||
switch (btn.getAttribute('data-act')) {
|
||||
case 'up': move(target, -1); break;
|
||||
case 'down': move(target, 1); break;
|
||||
case 'remove-city': city.remove(); break;
|
||||
case 'remove-region':
|
||||
if (window.confirm(i18n.confirm)) {
|
||||
region.remove();
|
||||
}
|
||||
break;
|
||||
case 'search': search(region); break;
|
||||
}
|
||||
});
|
||||
|
||||
list.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' && e.target.matches('.vc-city-search input[type="search"]')) {
|
||||
e.preventDefault();
|
||||
search(e.target.closest('.vc-region'));
|
||||
}
|
||||
});
|
||||
|
||||
// Suggest a region slug from its name until the slug is edited by hand.
|
||||
list.addEventListener('input', function (e) {
|
||||
var input = e.target;
|
||||
if (input.matches('.vc-region__head [data-f="slug"]')) {
|
||||
input.dataset.touched = '1';
|
||||
}
|
||||
if (input.matches('.vc-region__head [data-f="name"]')) {
|
||||
var slug = input.closest('.vc-region__head').querySelector('[data-f="slug"]');
|
||||
if (!slug.dataset.touched && (slug.value === '' || slug.dataset.auto === '1')) {
|
||||
slug.value = slugify(input.value);
|
||||
slug.dataset.auto = '1';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('vc-add-region').addEventListener('click', function () {
|
||||
var region = fromTemplate('vc-tpl-region');
|
||||
list.appendChild(region);
|
||||
region.querySelector('[data-f="name"]').focus();
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function () {
|
||||
list.querySelectorAll('.vc-region').forEach(function (region, i) {
|
||||
region.querySelectorAll('.vc-region__head [data-f]').forEach(function (input) {
|
||||
input.name = 'vc_structure[' + i + '][' + input.dataset.f + ']';
|
||||
});
|
||||
region.querySelectorAll('.vc-city').forEach(function (city, j) {
|
||||
city.querySelectorAll('[data-f]').forEach(function (input) {
|
||||
input.name = 'vc_structure[' + i + '][cities][' + j + '][' + input.dataset.f + ']';
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user