v0.11.0 #50
@@ -275,7 +275,13 @@ async function _fetchTypeahead(input, fieldKind) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Args go in data-* attributes (not inline onclick) to avoid the quote
|
||||
// collision when location names contain characters JSON.stringify quotes
|
||||
// (e.g. anything with spaces/punctuation — basically every real name).
|
||||
// _esc() escapes for HTML attribute context (entities for <>&"), then
|
||||
// the browser decodes them when reading the dataset value.
|
||||
dropdown.innerHTML = items.map((it, idx) => {
|
||||
const cid = _esc(input.dataset.clusterId);
|
||||
if (it.kind === 'match') {
|
||||
const m = it.payload;
|
||||
const scoreBadge = m.score >= 0.99
|
||||
@@ -291,16 +297,24 @@ async function _fetchTypeahead(input, fieldKind) {
|
||||
}
|
||||
const metaLine = meta.length ? `<div class="text-xs text-gray-500 dark:text-gray-400">${meta.join(' · ')}</div>` : '';
|
||||
return `<button type="button"
|
||||
data-cid="${cid}"
|
||||
data-field-kind="${fieldKind}"
|
||||
data-entity-id="${_esc(m.id)}"
|
||||
data-entity-name="${_esc(m.name)}"
|
||||
onmousedown="event.preventDefault()"
|
||||
onclick="onTypeaheadPick(event, '${_esc(input.dataset.clusterId)}', '${fieldKind}', '${_esc(m.id)}', ${JSON.stringify(m.name)})"
|
||||
onclick="_typeaheadPickFromButton(this)"
|
||||
class="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-slate-700 border-b border-gray-100 dark:border-gray-700 last:border-b-0">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white">${_esc(m.name)}${scoreBadge}</div>
|
||||
${metaLine}
|
||||
</button>`;
|
||||
}
|
||||
return `<button type="button"
|
||||
data-cid="${cid}"
|
||||
data-field-kind="${fieldKind}"
|
||||
data-entity-id=""
|
||||
data-entity-name="${_esc(it.name)}"
|
||||
onmousedown="event.preventDefault()"
|
||||
onclick="onTypeaheadPick(event, '${_esc(input.dataset.clusterId)}', '${fieldKind}', '', ${JSON.stringify(it.name)})"
|
||||
onclick="_typeaheadPickFromButton(this)"
|
||||
class="w-full text-left px-3 py-2 hover:bg-orange-50 dark:hover:bg-orange-900/20 border-t border-gray-200 dark:border-gray-700 text-seismo-orange font-medium text-sm">
|
||||
+ ${_esc(it.label)}
|
||||
</button>`;
|
||||
@@ -308,6 +322,19 @@ async function _fetchTypeahead(input, fieldKind) {
|
||||
dropdown.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Trampoline — reads the click target's data-* attributes and forwards
|
||||
// to onTypeaheadPick. Keeps the inline onclick attribute free of any
|
||||
// string interpolation that could collide with HTML quoting.
|
||||
function _typeaheadPickFromButton(btn) {
|
||||
onTypeaheadPick(
|
||||
null,
|
||||
btn.dataset.cid,
|
||||
btn.dataset.fieldKind,
|
||||
btn.dataset.entityId || '',
|
||||
btn.dataset.entityName || ''
|
||||
);
|
||||
}
|
||||
|
||||
function onTypeaheadPick(event, clusterId, fieldKind, entityId, name) {
|
||||
// entityId is empty string for "create new", or a UUID for matched existing.
|
||||
const inputs = document.querySelectorAll(`input[data-cluster-id="${clusterId}"]`);
|
||||
|
||||
Reference in New Issue
Block a user