Browse Source
multilanguage widget appearance updated
multilanguage widget appearance updated
swithc from buttons to dropdown for language selection a dedicated widget template added for address and links for hadith appmaster
3 changed files with 456 additions and 179 deletions
-
158templates/utils/widgets/links_json_widget.html
-
240templates/utils/widgets/multilang_address_widget.html
-
237templates/utils/widgets/multilang_json_widget.html
@ -0,0 +1,158 @@ |
|||||
|
{% 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> |
||||
@ -0,0 +1,240 @@ |
|||||
|
{% load i18n %} |
||||
|
<div class="space-y-3 max-w-2xl" data-multilang-address data-field-name="{{ widget.field_name }}"> |
||||
|
<div> |
||||
|
<div class="w-full max-w-2xl pr-2"> |
||||
|
<div class="flex items-center gap-2 py-1.5"> |
||||
|
<span class="text-xs font-semibold text-font-subtle-light dark:text-font-subtle-dark uppercase tracking-wider"> |
||||
|
{% trans "Select Language" %}: |
||||
|
</span> |
||||
|
<select class="lang-select border border-base-200 bg-white font-semibold min-w-[120px] rounded shadow-sm text-font-default-light text-xs focus:ring focus:ring-primary-300 focus:border-primary-600 focus:outline-none 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 px-3 py-1.5" data-lang-select> |
||||
|
{% for code in widget.languages %} |
||||
|
<option value="{{ code }}"> |
||||
|
{{ code|upper }}{% if widget.has_value_codes and code in widget.has_value_codes %} ✓{% endif %} |
||||
|
</option> |
||||
|
{% endfor %} |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="space-y-3" data-inputs> |
||||
|
{% for item in widget.rendered_languages %} |
||||
|
<div class="hidden" data-input-wrapper data-lang-code="{{ item.code }}"> |
||||
|
<div class="flex items-center gap-2 mb-1"> |
||||
|
<span class="text-xs font-medium text-font-subtle-light dark:text-font-subtle-dark"> |
||||
|
{{ item.code|upper }} |
||||
|
</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="space-y-2" data-parts-container> |
||||
|
{% for part in item.parts %} |
||||
|
<div class="flex items-center gap-2" data-part-row> |
||||
|
<input type="text" value="{{ part }}" 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" placeholder="Address part (e.g. Book 1, Chapter 9)"> |
||||
|
<button type="button" class="remove-part-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 part"> |
||||
|
<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-part-row> |
||||
|
<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" placeholder="Address part (e.g. Book 1, Chapter 9)"> |
||||
|
<button type="button" class="remove-part-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 part"> |
||||
|
<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-part-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-part> |
||||
|
<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 Part" %} |
||||
|
</button> |
||||
|
</div> |
||||
|
{% endfor %} |
||||
|
<input type="hidden" name="{{ widget.field_name }}" value='{{ widget.serialized|escapejs }}'> |
||||
|
</div> |
||||
|
|
||||
|
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark"> |
||||
|
{% trans "Click a language code to manage its address parts." %} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
/* Reuse styling rules from main widget to maintain consistency */ |
||||
|
[data-multilang-address] [data-input-wrapper].hidden { |
||||
|
display: none !important; |
||||
|
} |
||||
|
|
||||
|
[data-multilang-address] [data-input-wrapper]:not(.hidden) { |
||||
|
display: block !important; |
||||
|
} |
||||
|
|
||||
|
.lang-select { |
||||
|
cursor: pointer; |
||||
|
appearance: none !important; |
||||
|
-webkit-appearance: none !important; |
||||
|
-moz-appearance: none !important; |
||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E") !important; |
||||
|
background-repeat: no-repeat !important; |
||||
|
background-position: right 0.5rem center !important; |
||||
|
background-size: 1.25rem !important; |
||||
|
padding-right: 1.75rem !important; |
||||
|
} |
||||
|
|
||||
|
[dir="rtl"] .lang-select { |
||||
|
background-position: left 0.5rem center !important; |
||||
|
padding-right: 0.75rem !important; |
||||
|
padding-left: 1.75rem !important; |
||||
|
} |
||||
|
</style> |
||||
|
|
||||
|
<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 select = root.querySelector("[data-lang-select]"); |
||||
|
var inputsRoot = root.querySelector("[data-inputs]"); |
||||
|
if (!inputsRoot || !select) return; |
||||
|
|
||||
|
// First, hide all wrappers |
||||
|
inputsRoot.querySelectorAll('[data-input-wrapper]').forEach(function (w) { |
||||
|
w.classList.add("hidden"); |
||||
|
}); |
||||
|
|
||||
|
var selectedCode = select.value; |
||||
|
var wrapper = inputsRoot.querySelector('[data-input-wrapper][data-lang-code="' + selectedCode + '"]'); |
||||
|
if (wrapper) { |
||||
|
wrapper.classList.remove("hidden"); |
||||
|
} |
||||
|
|
||||
|
select.addEventListener("change", function () { |
||||
|
var code = select.value; |
||||
|
var wrapper = inputsRoot.querySelector('[data-input-wrapper][data-lang-code="' + code + '"]'); |
||||
|
if (!wrapper) return; |
||||
|
|
||||
|
inputsRoot.querySelectorAll('[data-input-wrapper]').forEach(function (w) { |
||||
|
w.classList.add("hidden"); |
||||
|
}); |
||||
|
|
||||
|
wrapper.classList.remove("hidden"); |
||||
|
|
||||
|
var input = wrapper.querySelector('input[type="text"]'); |
||||
|
if (input) { |
||||
|
setTimeout(function(){ input.focus(); }, 50); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
var hidden = root.querySelector('input[type="hidden"][name="' + fieldName + '"]'); |
||||
|
|
||||
|
function serialize() { |
||||
|
if (!hidden) return; |
||||
|
var result = []; |
||||
|
inputsRoot.querySelectorAll('[data-input-wrapper]').forEach(function (w) { |
||||
|
var code = w.getAttribute('data-lang-code'); |
||||
|
var parts = []; |
||||
|
w.querySelectorAll('input[type="text"]').forEach(function (inp) { |
||||
|
if (inp.value && inp.value.trim() !== '') { |
||||
|
parts.push(inp.value.trim()); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
var option = select.querySelector('option[value="' + code + '"]'); |
||||
|
var baseText = code.toUpperCase(); |
||||
|
|
||||
|
if (parts.length > 0) { |
||||
|
result.push({ language_code: code, title: parts }); |
||||
|
if (option) option.textContent = baseText + ' ✓'; |
||||
|
} else { |
||||
|
if (option) option.textContent = baseText; |
||||
|
} |
||||
|
}); |
||||
|
try { |
||||
|
hidden.value = JSON.stringify(result); |
||||
|
} catch (e) { |
||||
|
console.error('JSON stringify error:', e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function bindInputEvents(input) { |
||||
|
input.addEventListener('input', serialize); |
||||
|
} |
||||
|
|
||||
|
inputsRoot.querySelectorAll('input[type="text"]').forEach(bindInputEvents); |
||||
|
|
||||
|
inputsRoot.querySelectorAll('[data-add-part]').forEach(function (addBtn) { |
||||
|
addBtn.addEventListener('click', function () { |
||||
|
var wrapper = addBtn.closest('[data-input-wrapper]'); |
||||
|
var container = wrapper.querySelector('[data-parts-container]'); |
||||
|
if (!container) return; |
||||
|
|
||||
|
var row = document.createElement('div'); |
||||
|
row.className = 'flex items-center gap-2 mt-2'; |
||||
|
row.setAttribute('data-part-row', ''); |
||||
|
|
||||
|
var input = document.createElement('input'); |
||||
|
input.type = 'text'; |
||||
|
input.placeholder = 'Address part (e.g. Book 1, Chapter 9)'; |
||||
|
input.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'; |
||||
|
|
||||
|
bindInputEvents(input); |
||||
|
|
||||
|
var delBtn = document.createElement('button'); |
||||
|
delBtn.type = 'button'; |
||||
|
delBtn.className = 'remove-part-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 part'; |
||||
|
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(input); |
||||
|
row.appendChild(delBtn); |
||||
|
container.appendChild(row); |
||||
|
|
||||
|
input.focus(); |
||||
|
serialize(); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
inputsRoot.querySelectorAll('.remove-part-btn').forEach(function (btn) { |
||||
|
btn.addEventListener('click', function () { |
||||
|
var row = btn.closest('[data-part-row]'); |
||||
|
if (row) { |
||||
|
row.remove(); |
||||
|
serialize(); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function initializeWidgets() { |
||||
|
document.querySelectorAll('[data-multilang-address]').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-multilang-address]').forEach(init); |
||||
|
} |
||||
|
}); |
||||
|
})(); |
||||
|
</script> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue