Theseus: drag-to-reorder search engines (replaces up/down arrows)

Each engine row in Settings has a drag handle (⠿) and is draggable; dropping
onto another row reorders and persists via setEngineOrder, driving the
toolbar dropdown order. Visual drag/over states included.
This commit is contained in:
Local Dev 2026-07-31 21:53:01 +02:00
parent e284864d3c
commit 9c5c6c12c9

View file

@ -43,9 +43,11 @@
.eng .eic .ei{width:16px;height:16px;border-radius:3px} .eng .eic .es{font-size:14px} .eng .eic .ei{width:16px;height:16px;border-radius:3px} .eng .eic .es{font-size:14px}
.eng .enm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .eng .enm{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.eng .cx{cursor:pointer;color:var(--dim);border:none;background:transparent;font-size:13px} .eng .cx:hover{color:#f6768a} .eng .cx{cursor:pointer;color:var(--dim);border:none;background:transparent;font-size:13px} .eng .cx:hover{color:#f6768a}
.eng .mv{display:flex;flex-direction:column;flex:none;gap:1px} .eng{cursor:default}
.eng .mv button{border:none;background:transparent;color:var(--dim);cursor:pointer;font-size:8px;line-height:1;padding:1px 2px;border-radius:3px} .eng .grip{flex:none;color:var(--dim);cursor:grab;font-size:14px;line-height:1;padding:0 2px;user-select:none}
.eng .mv button:hover{background:var(--line);color:var(--ink)} .eng .grip:active{cursor:grabbing}
.eng.dragging{opacity:.45}
.eng.over{border-color:var(--acid);box-shadow:0 -2px 0 var(--acid) inset}
.sw.sm{width:38px;height:22px} .sw.sm .knob{width:15px;height:15px} .sw.sm input:checked + .track .knob{transform:translateX(16px)} .sw.sm{width:38px;height:22px} .sw.sm .knob{width:15px;height:15px} .sw.sm input:checked + .track .knob{transform:translateX(16px)}
.ceng .cn{font-weight:600} .ceng .cu{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1} .ceng .cn{font-weight:600} .ceng .cu{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
.ceng .cx{cursor:pointer;color:#8b98a9;border:none;background:transparent;font-size:13px} .ceng .cx:hover{color:#f6768a} .ceng .cx{cursor:pointer;color:#8b98a9;border:none;background:transparent;font-size:13px} .ceng .cx:hover{color:#f6768a}
@ -233,26 +235,33 @@
const enabled = d.engines.filter((e) => e.enabled); const enabled = d.engines.filter((e) => e.enabled);
sel.innerHTML = enabled.map((e) => `<option value="${e.id}">${(e.sym ? e.sym + " " : "")}${esc(e.name)}</option>`).join(""); sel.innerHTML = enabled.map((e) => `<option value="${e.id}">${(e.sym ? e.sym + " " : "")}${esc(e.name)}</option>`).join("");
sel.value = d.current; sel.value = d.current;
// unified checklist: drag handle / up-down to reorder; built-in get an // unified checklist: drag a row by its handle to reorder; built-in get an
// enable toggle, custom get a remove ✕. // enable toggle, custom get a remove ✕.
const list = document.getElementById("engineList"); const list = document.getElementById("engineList");
list.innerHTML = d.engines.map((e) => `<div class="eng" data-id="${e.id}">` + list.innerHTML = d.engines.map((e) => `<div class="eng" data-id="${e.id}" draggable="true">` +
`<span class="mv"><button class="up" title="Move up"></button><button class="dn" title="Move down"></button></span>` + `<span class="grip" title="Drag to reorder"></span>` +
`<span class="eic">${engIcon(e)}</span><span class="enm">${esc(e.name)}</span>` + `<span class="eic">${engIcon(e)}</span><span class="enm">${esc(e.name)}</span>` +
(e.builtin (e.builtin
? `<label class="sw sm"><input type="checkbox" data-id="${e.id}" ${e.enabled ? "checked" : ""}><span class="track"><span class="knob"></span></span></label>` ? `<label class="sw sm"><input type="checkbox" data-id="${e.id}" ${e.enabled ? "checked" : ""}><span class="track"><span class="knob"></span></span></label>`
: `<button class="cx" data-id="${e.id}" title="Remove"></button>`) + `</div>`).join(""); : `<button class="cx" data-id="${e.id}" title="Remove"></button>`) + `</div>`).join("");
list.querySelectorAll('input[type="checkbox"]').forEach((cb) => cb.onchange = () => C.setEngineEnabled(cb.dataset.id, cb.checked).then(renderEngines)); list.querySelectorAll('input[type="checkbox"]').forEach((cb) => cb.onchange = () => C.setEngineEnabled(cb.dataset.id, cb.checked).then(renderEngines));
list.querySelectorAll(".cx").forEach((b) => b.onclick = () => C.removeEngine(b.dataset.id).then(renderEngines)); list.querySelectorAll(".cx").forEach((b) => b.onclick = () => C.removeEngine(b.dataset.id).then(renderEngines));
const move = (id, dir) => { // drag-and-drop reorder
const ids = [...list.querySelectorAll(".eng")].map((el) => el.dataset.id); let dragId = null;
const i = ids.indexOf(id), j = dir === "up" ? i - 1 : i + 1; list.querySelectorAll(".eng").forEach((row) => {
if (j < 0 || j >= ids.length) return; row.addEventListener("dragstart", (e) => { dragId = row.dataset.id; e.dataTransfer.effectAllowed = "move"; row.classList.add("dragging"); });
[ids[i], ids[j]] = [ids[j], ids[i]]; row.addEventListener("dragend", () => { row.classList.remove("dragging"); list.querySelectorAll(".eng").forEach((r) => r.classList.remove("over")); });
C.setEngineOrder(ids).then(renderEngines); row.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; if (row.dataset.id !== dragId) row.classList.add("over"); });
}; row.addEventListener("dragleave", () => row.classList.remove("over"));
list.querySelectorAll(".up").forEach((b) => b.onclick = () => move(b.closest(".eng").dataset.id, "up")); row.addEventListener("drop", (e) => {
list.querySelectorAll(".dn").forEach((b) => b.onclick = () => move(b.closest(".eng").dataset.id, "down")); e.preventDefault(); row.classList.remove("over");
if (!dragId || dragId === row.dataset.id) return;
const ids = [...list.querySelectorAll(".eng")].map((el) => el.dataset.id);
const from = ids.indexOf(dragId), to = ids.indexOf(row.dataset.id);
ids.splice(from, 1); ids.splice(to, 0, dragId);
C.setEngineOrder(ids).then(renderEngines);
});
});
} }
sel.onchange = () => C.set("searchEngine", sel.value); sel.onchange = () => C.set("searchEngine", sel.value);
// appearance (theme) // appearance (theme)