feat: pipeline templates, analytics opt-out, 83 conversion presets, positioning + e2e modernization

Lands five integrated branches: pipeline templates (#355), analytics opt-out (#354), 83 conversion presets bringing the catalog to 240 tools (#356), self-hosted positioning (#353), and e2e modernization (#351).

Integration fixes: aligned stale web analytics tests with the opt-out/allow-list model, closed 3 CodeQL incomplete-sanitization alerts in the i18n generator, resolved settings/index/docs/format-matrix conflicts, and corrected tool counts to 240.
This commit is contained in:
SnapOtter
2026-06-28 18:57:53 +08:00
committed by GitHub
parent 88af8d46fb
commit 63a03d26f2
421 changed files with 17308 additions and 2540 deletions
+33 -36
View File
@@ -53,6 +53,7 @@ function renderIcon(iconName: string): string {
data-name={tool.name.toLowerCase()}
data-desc={tool.description.toLowerCase()}
data-mod={(mod?.label || "").toLowerCase()}
data-keywords={(tool.keywords ?? []).join(" ")}
hidden
>
<a
@@ -88,63 +89,59 @@ function renderIcon(iconName: string): string {
</div>
</div>
<script is:inline>
(function () {
var input = document.getElementById("hero-tool-search");
var box = document.getElementById("hero-search-results");
var empty = document.getElementById("hero-search-empty");
var items = Array.prototype.slice.call(
document.querySelectorAll("#hero-search-list .hero-result"),
);
var MAX = 7;
var firstHref = null;
<script>
import { matchTool } from "../lib/tool-search";
function close() {
const input = document.getElementById("hero-tool-search") as HTMLInputElement | null;
const box = document.getElementById("hero-search-results");
const empty = document.getElementById("hero-search-empty");
const items = Array.from(
document.querySelectorAll<HTMLLIElement>("#hero-search-list .hero-result"),
);
const MAX = 7;
let firstHref: string | null = null;
if (input && box && empty) {
const close = () => {
box.classList.add("hidden");
input.setAttribute("aria-expanded", "false");
}
};
function open() {
const open = () => {
box.classList.remove("hidden");
input.setAttribute("aria-expanded", "true");
}
};
function run() {
var q = input.value.toLowerCase().trim();
const run = () => {
const q = input.value.trim();
if (!q) {
close();
return;
}
var tokens = q.split(/\s+/);
var shown = 0;
let shown = 0;
firstHref = null;
items.forEach(function (li) {
var hay =
(li.getAttribute("data-name") || "") +
" " +
(li.getAttribute("data-desc") || "") +
" " +
(li.getAttribute("data-mod") || "");
var hit = tokens.every(function (t) {
return hay.indexOf(t) !== -1;
});
for (const li of items) {
const hay =
`${li.getAttribute("data-name") || ""} ${li.getAttribute("data-desc") || ""} ` +
`${li.getAttribute("data-mod") || ""} ${li.getAttribute("data-keywords") || ""}`;
const hit = matchTool(q, hay);
if (hit && shown < MAX) {
li.hidden = false;
shown++;
if (!firstHref) firstHref = li.querySelector("a").getAttribute("href");
if (!firstHref) firstHref = li.querySelector("a")?.getAttribute("href") ?? null;
} else {
li.hidden = true;
}
});
}
empty.classList.toggle("hidden", shown !== 0);
open();
}
};
input.addEventListener("input", run);
input.addEventListener("focus", function () {
input.addEventListener("focus", () => {
if (input.value.trim()) run();
});
input.addEventListener("keydown", function (e) {
input.addEventListener("keydown", (e) => {
if (e.key === "Enter" && firstHref) {
window.location.href = firstHref;
} else if (e.key === "Escape") {
@@ -152,8 +149,8 @@ function renderIcon(iconName: string): string {
input.blur();
}
});
document.addEventListener("click", function (e) {
if (!box.contains(e.target) && e.target !== input) close();
document.addEventListener("click", (e) => {
if (!box.contains(e.target as Node) && e.target !== input) close();
});
})();
}
</script>