169 lines
5.7 KiB
JavaScript
169 lines
5.7 KiB
JavaScript
(() => {
|
|
const root = document.getElementById('fx-rates-currencies');
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
const page = JSON.parse(root.dataset.page || '{}');
|
|
const currencies = Array.isArray(page.currencies) ? page.currencies : [];
|
|
const selected = new Set(
|
|
(Array.isArray(page.preferred_currencies) ? page.preferred_currencies : [])
|
|
.map((code) => String(code || '').trim().toUpperCase())
|
|
.filter(Boolean)
|
|
);
|
|
let displayBase = String(page.display_base_currency || '').trim().toUpperCase();
|
|
const savedDisplayBase = String(page.saved_display_base_currency || displayBase || '').trim().toUpperCase();
|
|
|
|
const nodes = {
|
|
tokenList: root.querySelector('[data-fx-token-list]'),
|
|
searchInput: root.querySelector('[data-fx-search-input]'),
|
|
suggestions: root.querySelector('[data-fx-suggestions]'),
|
|
displayBaseSelect: root.querySelector('[data-fx-display-base-select]'),
|
|
displayBaseHidden: root.querySelector('[data-fx-display-base-hidden]'),
|
|
hiddenPreferred: root.querySelector('[data-fx-hidden-preferred]'),
|
|
};
|
|
|
|
const escapeHtml = (value) => String(value || '')
|
|
.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('"', '"')
|
|
.replaceAll("'", ''');
|
|
|
|
const currencyByCode = new Map(
|
|
currencies.map((currency) => [String(currency.code || '').toUpperCase(), currency])
|
|
);
|
|
|
|
const sortedSelectedCodes = () => Array.from(selected).sort((left, right) => left.localeCompare(right));
|
|
|
|
const ensureDisplayBase = () => {
|
|
const available = sortedSelectedCodes();
|
|
if (available.length === 0) {
|
|
displayBase = '';
|
|
return;
|
|
}
|
|
if (!displayBase || !selected.has(displayBase)) {
|
|
displayBase = available[0];
|
|
}
|
|
};
|
|
|
|
const renderHiddenInputs = () => {
|
|
if (!nodes.hiddenPreferred) {
|
|
return;
|
|
}
|
|
nodes.hiddenPreferred.innerHTML = sortedSelectedCodes()
|
|
.map((code) => `<input type="hidden" name="preferred_currencies[]" value="${escapeHtml(code)}">`)
|
|
.join('');
|
|
if (nodes.displayBaseHidden) {
|
|
nodes.displayBaseHidden.value = displayBase;
|
|
}
|
|
};
|
|
|
|
const renderDisplayBase = () => {
|
|
if (!nodes.displayBaseSelect) {
|
|
return;
|
|
}
|
|
ensureDisplayBase();
|
|
const available = sortedSelectedCodes();
|
|
nodes.displayBaseSelect.innerHTML = available.length
|
|
? available.map((code) => `<option value="${escapeHtml(code)}" ${code === displayBase ? 'selected' : ''}>${escapeHtml(code)}</option>`).join('')
|
|
: '<option value="">Keine Waehrungen ausgewaehlt</option>';
|
|
nodes.displayBaseSelect.disabled = available.length === 0;
|
|
};
|
|
|
|
const removeCode = (code) => {
|
|
selected.delete(code);
|
|
renderAll();
|
|
};
|
|
|
|
const renderTokens = () => {
|
|
if (!nodes.tokenList) {
|
|
return;
|
|
}
|
|
const selectedCodes = sortedSelectedCodes();
|
|
if (selectedCodes.length === 0) {
|
|
nodes.tokenList.innerHTML = '<div class="fx-text">Noch keine bevorzugten Waehrungen ausgewaehlt.</div>';
|
|
return;
|
|
}
|
|
nodes.tokenList.innerHTML = selectedCodes.map((code) => {
|
|
const currency = currencyByCode.get(code) || { code, name: code };
|
|
return `
|
|
<button type="button" class="fx-token" data-remove-code="${escapeHtml(code)}" title="${escapeHtml(code)} entfernen">
|
|
<span>${escapeHtml(`${code} (${currency.name || code})`)}</span>
|
|
<span class="fx-token-close">x</span>
|
|
</button>
|
|
`;
|
|
}).join('');
|
|
|
|
nodes.tokenList.querySelectorAll('[data-remove-code]').forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
removeCode(String(button.getAttribute('data-remove-code') || '').toUpperCase());
|
|
});
|
|
});
|
|
};
|
|
|
|
const renderSuggestions = () => {
|
|
if (!nodes.suggestions || !nodes.searchInput) {
|
|
return;
|
|
}
|
|
const needle = String(nodes.searchInput.value || '').trim().toLowerCase();
|
|
if (!needle) {
|
|
nodes.suggestions.innerHTML = '';
|
|
return;
|
|
}
|
|
const matches = currencies
|
|
.filter((currency) => !selected.has(String(currency.code || '').toUpperCase()))
|
|
.filter((currency) => {
|
|
const code = String(currency.code || '').toLowerCase();
|
|
const name = String(currency.name || '').toLowerCase();
|
|
return code.includes(needle) || name.includes(needle);
|
|
})
|
|
.slice(0, 12);
|
|
|
|
nodes.suggestions.innerHTML = matches.map((currency) => `
|
|
<button type="button" class="fx-suggestion" data-add-code="${escapeHtml(String(currency.code || '').toUpperCase())}">
|
|
<strong>${escapeHtml(String(currency.code || '').toUpperCase())}</strong>
|
|
<span>${escapeHtml(String(currency.name || ''))}</span>
|
|
</button>
|
|
`).join('');
|
|
|
|
nodes.suggestions.querySelectorAll('[data-add-code]').forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
const code = String(button.getAttribute('data-add-code') || '').toUpperCase();
|
|
if (code) {
|
|
selected.add(code);
|
|
if (nodes.searchInput) {
|
|
nodes.searchInput.value = '';
|
|
}
|
|
renderAll();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const renderAll = () => {
|
|
ensureDisplayBase();
|
|
renderTokens();
|
|
renderDisplayBase();
|
|
renderHiddenInputs();
|
|
renderSuggestions();
|
|
};
|
|
|
|
nodes.searchInput?.addEventListener('input', renderSuggestions);
|
|
nodes.displayBaseSelect?.addEventListener('change', () => {
|
|
displayBase = String(nodes.displayBaseSelect?.value || '').trim().toUpperCase();
|
|
renderHiddenInputs();
|
|
const url = new URL(window.location.href);
|
|
if (displayBase) {
|
|
url.searchParams.set('base', displayBase);
|
|
} else if (savedDisplayBase) {
|
|
url.searchParams.set('base', savedDisplayBase);
|
|
} else {
|
|
url.searchParams.delete('base');
|
|
}
|
|
window.location.href = url.toString();
|
|
});
|
|
|
|
renderAll();
|
|
})();
|