You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
9.9 KiB
158 lines
9.9 KiB
{% load i18n %}
|
|
<div class="space-y-3 max-w-2xl" data-links-json data-field-name="{{ widget.field_name }}">
|
|
<div class="space-y-2" data-links-container>
|
|
{% for link in widget.links %}
|
|
<div class="flex items-center gap-2" data-link-row>
|
|
<div class="w-2/5">
|
|
<input type="text" value="{{ link.title }}" class="border border-base-200 bg-white font-medium min-w-20 placeholder-base-400 rounded shadow-sm text-font-default-light text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-base-900 dark:border-base-700 dark:text-font-default-dark dark:focus:border-primary-600 dark:focus:ring-primary-700 dark:focus:ring-opacity-50 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40 px-3 py-2 w-full max-w-2xl link-title" placeholder="{% trans 'Link Title (e.g. Source)' %}">
|
|
</div>
|
|
<div class="w-3/5">
|
|
<input type="url" value="{{ link.link }}" class="border border-base-200 bg-white font-medium min-w-20 placeholder-base-400 rounded shadow-sm text-font-default-light text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-base-900 dark:border-base-700 dark:text-font-default-dark dark:focus:border-primary-600 dark:focus:ring-primary-700 dark:focus:ring-opacity-50 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40 px-3 py-2 w-full max-w-2xl link-url" placeholder="{% trans 'URL (e.g. https://...)' %}">
|
|
</div>
|
|
<button type="button" class="remove-link-btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 p-2 text-sm transition-colors duration-150" title="Remove Link">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
{% empty %}
|
|
<div class="flex items-center gap-2" data-link-row>
|
|
<div class="w-2/5">
|
|
<input type="text" value="" class="border border-base-200 bg-white font-medium min-w-20 placeholder-base-400 rounded shadow-sm text-font-default-light text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-base-900 dark:border-base-700 dark:text-font-default-dark dark:focus:border-primary-600 dark:focus:ring-primary-700 dark:focus:ring-opacity-50 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40 px-3 py-2 w-full max-w-2xl link-title" placeholder="{% trans 'Link Title (e.g. Source)' %}">
|
|
</div>
|
|
<div class="w-3/5">
|
|
<input type="url" value="" class="border border-base-200 bg-white font-medium min-w-20 placeholder-base-400 rounded shadow-sm text-font-default-light text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-base-900 dark:border-base-700 dark:text-font-default-dark dark:focus:border-primary-600 dark:focus:ring-primary-700 dark:focus:ring-opacity-50 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40 px-3 py-2 w-full max-w-2xl link-url" placeholder="{% trans 'URL (e.g. https://...)' %}">
|
|
</div>
|
|
<button type="button" class="remove-link-btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 p-2 text-sm transition-colors duration-150" title="Remove Link">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<button type="button" class="add-link-btn mt-2 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary-50 dark:hover:bg-primary-950/30 rounded border border-primary-200 dark:border-primary-800 transition-colors duration-150" data-add-link>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
|
|
</svg>
|
|
{% trans "Add Link" %}
|
|
</button>
|
|
|
|
<input type="hidden" name="{{ widget.field_name }}" value='{{ widget.serialized|escapejs }}'>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
function init(root) {
|
|
if (root.getAttribute("data-initialized") === "true") return;
|
|
root.setAttribute("data-initialized", "true");
|
|
var fieldName = root.getAttribute("data-field-name");
|
|
if (!fieldName) return;
|
|
|
|
var container = root.querySelector("[data-links-container]");
|
|
var hidden = root.querySelector('input[type="hidden"][name="' + fieldName + '"]');
|
|
if (!container || !hidden) return;
|
|
|
|
function serialize() {
|
|
var result = [];
|
|
container.querySelectorAll('[data-link-row]').forEach(function (row) {
|
|
var titleInput = row.querySelector('.link-title');
|
|
var urlInput = row.querySelector('.link-url');
|
|
var titleVal = titleInput ? titleInput.value.trim() : '';
|
|
var urlVal = urlInput ? urlInput.value.trim() : '';
|
|
|
|
if (titleVal !== '' || urlVal !== '') {
|
|
result.push({ title: titleVal, link: urlVal });
|
|
}
|
|
});
|
|
try {
|
|
hidden.value = JSON.stringify(result);
|
|
} catch (e) {
|
|
console.error('JSON stringify error:', e);
|
|
}
|
|
}
|
|
|
|
function bindInputEvents(input) {
|
|
input.addEventListener('input', serialize);
|
|
}
|
|
|
|
container.querySelectorAll('input').forEach(bindInputEvents);
|
|
|
|
var addBtn = root.querySelector('[data-add-link]');
|
|
if (addBtn) {
|
|
addBtn.addEventListener('click', function () {
|
|
var row = document.createElement('div');
|
|
row.className = 'flex items-center gap-2 mt-2';
|
|
row.setAttribute('data-link-row', '');
|
|
|
|
var col1 = document.createElement('div');
|
|
col1.className = 'w-2/5';
|
|
var inputTitle = document.createElement('input');
|
|
inputTitle.type = 'text';
|
|
inputTitle.className = 'border border-base-200 bg-white font-medium min-w-20 placeholder-base-400 rounded shadow-sm text-font-default-light text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-base-900 dark:border-base-700 dark:text-font-default-dark dark:focus:border-primary-600 dark:focus:ring-primary-700 dark:focus:ring-opacity-50 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40 px-3 py-2 w-full max-w-2xl link-title';
|
|
inputTitle.placeholder = 'Link Title (e.g. Source)';
|
|
bindInputEvents(inputTitle);
|
|
col1.appendChild(inputTitle);
|
|
|
|
var col2 = document.createElement('div');
|
|
col2.className = 'w-3/5';
|
|
var inputUrl = document.createElement('input');
|
|
inputUrl.type = 'url';
|
|
inputUrl.className = 'border border-base-200 bg-white font-medium min-w-20 placeholder-base-400 rounded shadow-sm text-font-default-light text-sm focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none group-[.errors]:border-red-600 group-[.errors]:focus:ring-red-200 dark:bg-base-900 dark:border-base-700 dark:text-font-default-dark dark:focus:border-primary-600 dark:focus:ring-primary-700 dark:focus:ring-opacity-50 dark:group-[.errors]:border-red-500 dark:group-[.errors]:focus:ring-red-600/40 px-3 py-2 w-full max-w-2xl link-url';
|
|
inputUrl.placeholder = 'URL (e.g. https://...)';
|
|
bindInputEvents(inputUrl);
|
|
col2.appendChild(inputUrl);
|
|
|
|
var delBtn = document.createElement('button');
|
|
delBtn.type = 'button';
|
|
delBtn.className = 'remove-link-btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 p-2 text-sm transition-colors duration-150';
|
|
delBtn.title = 'Remove Link';
|
|
delBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" /></svg>';
|
|
|
|
delBtn.addEventListener('click', function () {
|
|
row.remove();
|
|
serialize();
|
|
});
|
|
|
|
row.appendChild(col1);
|
|
row.appendChild(col2);
|
|
row.appendChild(delBtn);
|
|
container.appendChild(row);
|
|
|
|
inputTitle.focus();
|
|
serialize();
|
|
});
|
|
}
|
|
|
|
container.querySelectorAll('.remove-link-btn').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var row = btn.closest('[data-link-row]');
|
|
if (row) {
|
|
row.remove();
|
|
serialize();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function initializeWidgets() {
|
|
document.querySelectorAll('[data-links-json]').forEach(init);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener("DOMContentLoaded", initializeWidgets);
|
|
} else {
|
|
initializeWidgets();
|
|
}
|
|
|
|
setTimeout(initializeWidgets, 100);
|
|
|
|
document.addEventListener("formset:added", function (event) {
|
|
var newFormset = event.detail.formsetRow;
|
|
if (newFormset) {
|
|
newFormset.querySelectorAll('[data-links-json]').forEach(init);
|
|
}
|
|
});
|
|
})();
|
|
</script>
|