codetrail-cli 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codetrail/__init__.py +3 -0
- codetrail/__main__.py +5 -0
- codetrail/agents/__init__.py +4 -0
- codetrail/agents/convert.py +92 -0
- codetrail/agents/export.py +284 -0
- codetrail/agents/instructions.py +42 -0
- codetrail/agents/protocol.py +111 -0
- codetrail/agents/tools.py +185 -0
- codetrail/assistant/__init__.py +161 -0
- codetrail/assistant/claude_code.py +330 -0
- codetrail/assistant/codex.py +260 -0
- codetrail/assistant/environment.py +66 -0
- codetrail/assistant/estimate.py +225 -0
- codetrail/assistant/fake.py +74 -0
- codetrail/assistant/guard.py +80 -0
- codetrail/assistant/guard_hook.py +58 -0
- codetrail/assistant/local.py +245 -0
- codetrail/assistant/local_tools.py +177 -0
- codetrail/assistant/prompts.py +404 -0
- codetrail/assistant/routing.py +86 -0
- codetrail/assistant/runner.py +120 -0
- codetrail/assistant/status.py +186 -0
- codetrail/assistant/usage.py +93 -0
- codetrail/bridge.py +292 -0
- codetrail/cli.py +407 -0
- codetrail/config.py +485 -0
- codetrail/database/__init__.py +60 -0
- codetrail/database/migrations/0001_facts.sql +34 -0
- codetrail/database/migrations/0002_settings.sql +5 -0
- codetrail/database/migrations/0003_learning.sql +34 -0
- codetrail/database/migrations/0004_assistant_usage.sql +24 -0
- codetrail/database/migrations/0005_page_failures.sql +7 -0
- codetrail/database/migrations/0006_metric_values.sql +9 -0
- codetrail/errors.py +5 -0
- codetrail/extract/__init__.py +249 -0
- codetrail/extract/adr.py +98 -0
- codetrail/extract/github_actions.py +140 -0
- codetrail/extract/openapi.py +101 -0
- codetrail/extract/python.py +221 -0
- codetrail/extract/swift.py +204 -0
- codetrail/extract/terraform.py +166 -0
- codetrail/extract/typescript.py +464 -0
- codetrail/facts/__init__.py +112 -0
- codetrail/facts/store.py +209 -0
- codetrail/generate/__init__.py +1 -0
- codetrail/generate/failures.py +31 -0
- codetrail/generate/outline.py +159 -0
- codetrail/generate/revise.py +72 -0
- codetrail/generate/run.py +530 -0
- codetrail/generate/scope.py +135 -0
- codetrail/generate/validate.py +218 -0
- codetrail/guide/__init__.py +147 -0
- codetrail/learn/__init__.py +143 -0
- codetrail/learn/routes.py +139 -0
- codetrail/locales/ar/LC_MESSAGES/codetrail.mo +0 -0
- codetrail/locales/ar/LC_MESSAGES/codetrail.po +2236 -0
- codetrail/locales/codetrail.pot +1942 -0
- codetrail/lock.py +114 -0
- codetrail/metrics/__init__.py +6 -0
- codetrail/metrics/activity.py +96 -0
- codetrail/metrics/churn.py +156 -0
- codetrail/metrics/commits.py +45 -0
- codetrail/metrics/coverage.py +114 -0
- codetrail/metrics/decisions.py +77 -0
- codetrail/metrics/history.py +47 -0
- codetrail/metrics/inventory.py +63 -0
- codetrail/metrics/rationale.py +74 -0
- codetrail/metrics/releases.py +26 -0
- codetrail/metrics/report.py +111 -0
- codetrail/metrics/repository.py +128 -0
- codetrail/metrics/size.py +95 -0
- codetrail/py.typed +0 -0
- codetrail/remove.py +50 -0
- codetrail/repo/__init__.py +4 -0
- codetrail/repo/git.py +56 -0
- codetrail/repo/gitleaks.toml +6 -0
- codetrail/repo/history.py +262 -0
- codetrail/repo/mirror.py +137 -0
- codetrail/repo/refresh.py +41 -0
- codetrail/repo/rules.py +83 -0
- codetrail/repo/secrets.py +145 -0
- codetrail/repo/signal.py +65 -0
- codetrail/repo/source.py +166 -0
- codetrail/search.py +178 -0
- codetrail/server.py +45 -0
- codetrail/system.py +484 -0
- codetrail/update.py +276 -0
- codetrail/web/__init__.py +1 -0
- codetrail/web/app.py +897 -0
- codetrail/web/diagrams.py +414 -0
- codetrail/web/documentation.py +146 -0
- codetrail/web/i18n.py +44 -0
- codetrail/web/navigation.py +127 -0
- codetrail/web/render.py +126 -0
- codetrail/web/repository_view.py +230 -0
- codetrail/web/security.py +171 -0
- codetrail/web/static/brand/favicon.svg +1 -0
- codetrail/web/static/brand/mark.svg +1 -0
- codetrail/web/static/css/base.css +192 -0
- codetrail/web/static/css/components.css +2254 -0
- codetrail/web/static/css/content.css +310 -0
- codetrail/web/static/css/layout.css +410 -0
- codetrail/web/static/css/print.css +42 -0
- codetrail/web/static/css/tokens.css +228 -0
- codetrail/web/static/js/api.js +49 -0
- codetrail/web/static/js/ask.js +182 -0
- codetrail/web/static/js/d2-worker.js +34 -0
- codetrail/web/static/js/d2.js +28 -0
- codetrail/web/static/js/diagram.js +52 -0
- codetrail/web/static/js/documentation.js +24 -0
- codetrail/web/static/js/learning.js +47 -0
- codetrail/web/static/js/main.js +38 -0
- codetrail/web/static/js/outline.js +44 -0
- codetrail/web/static/js/palette.js +211 -0
- codetrail/web/static/js/repository.js +528 -0
- codetrail/web/static/js/resize.js +126 -0
- codetrail/web/static/js/shortcuts.js +87 -0
- codetrail/web/static/js/sidebar.js +194 -0
- codetrail/web/static/js/theme-init.js +27 -0
- codetrail/web/static/js/theme.js +42 -0
- codetrail/web/static/js/update.js +107 -0
- codetrail/web/static/vendor/d2/LICENSE +375 -0
- codetrail/web/static/vendor/d2/LICENSE-elk +263 -0
- codetrail/web/static/vendor/d2/LICENSE-wasm_exec +27 -0
- codetrail/web/static/vendor/d2/VERSION +12 -0
- codetrail/web/static/vendor/d2/d2.wasm +0 -0
- codetrail/web/static/vendor/d2/elk.js +105914 -0
- codetrail/web/static/vendor/d2/wasm_exec.js +656 -0
- codetrail/web/static/vendor/inter/InterVariable-Italic.woff2 +0 -0
- codetrail/web/static/vendor/inter/InterVariable.woff2 +0 -0
- codetrail/web/static/vendor/inter/LICENSE.txt +92 -0
- codetrail/web/static/vendor/inter/VERSION +6 -0
- codetrail/web/static/vendor/noto-sans-arabic/NotoSansArabic-wght.woff2 +0 -0
- codetrail/web/static/vendor/noto-sans-arabic/OFL.txt +93 -0
- codetrail/web/static/vendor/noto-sans-arabic/VERSION +12 -0
- codetrail/web/target_view.py +63 -0
- codetrail/web/templates/answers.html +25 -0
- codetrail/web/templates/area.html +46 -0
- codetrail/web/templates/base.html +37 -0
- codetrail/web/templates/decisions.html +30 -0
- codetrail/web/templates/diagram.html +10 -0
- codetrail/web/templates/digests.html +24 -0
- codetrail/web/templates/documentation.html +247 -0
- codetrail/web/templates/error.html +9 -0
- codetrail/web/templates/estimate.html +48 -0
- codetrail/web/templates/fact.html +29 -0
- codetrail/web/templates/home.html +196 -0
- codetrail/web/templates/layout/ask_panel.html +27 -0
- codetrail/web/templates/layout/header.html +32 -0
- codetrail/web/templates/layout/icons.html +27 -0
- codetrail/web/templates/layout/outline.html +4 -0
- codetrail/web/templates/layout/palette.html +27 -0
- codetrail/web/templates/layout/shortcuts.html +27 -0
- codetrail/web/templates/layout/sidebar.html +79 -0
- codetrail/web/templates/layout/update_dialog.html +18 -0
- codetrail/web/templates/macros.html +13 -0
- codetrail/web/templates/page.html +93 -0
- codetrail/web/templates/progress.html +46 -0
- codetrail/web/templates/repository.html +369 -0
- codetrail/web/templates/search.html +31 -0
- codetrail/web/templates/source.html +15 -0
- codetrail/web/templates/system.html +43 -0
- codetrail/web/templates/system_arrows.html +17 -0
- codetrail/web/templates/system_legend.html +9 -0
- codetrail/web/templates/update_log.html +30 -0
- codetrail/web/trend.py +22 -0
- codetrail_cli-1.4.1.dist-info/METADATA +372 -0
- codetrail_cli-1.4.1.dist-info/RECORD +171 -0
- codetrail_cli-1.4.1.dist-info/WHEEL +4 -0
- codetrail_cli-1.4.1.dist-info/entry_points.txt +3 -0
- codetrail_cli-1.4.1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Codetrail's page script. Each module wires one part of the page; the security policy allows no inline code,
|
|
2
|
+
// so this file and the theme's first-paint script are all the page runs; D2 runs in its own worker (d2-worker.js).
|
|
3
|
+
import { post } from "./api.js";
|
|
4
|
+
import { setUpAsk } from "./ask.js";
|
|
5
|
+
import { setUpDiagrams } from "./diagram.js";
|
|
6
|
+
import { setUpDocumentation } from "./documentation.js";
|
|
7
|
+
import { setUpLearning } from "./learning.js";
|
|
8
|
+
import { setUpOutline } from "./outline.js";
|
|
9
|
+
import { setUpPalette } from "./palette.js";
|
|
10
|
+
import { setUpRepository } from "./repository.js";
|
|
11
|
+
import { setUpResize } from "./resize.js";
|
|
12
|
+
import { setUpShortcuts } from "./shortcuts.js";
|
|
13
|
+
import { setUpSidebar } from "./sidebar.js";
|
|
14
|
+
import { setUpTheme } from "./theme.js";
|
|
15
|
+
import { setUpUpdate } from "./update.js";
|
|
16
|
+
|
|
17
|
+
function setUpLanguage() {
|
|
18
|
+
const form = document.querySelector("[data-language-form]");
|
|
19
|
+
form?.addEventListener("change", async () => {
|
|
20
|
+
const response = await post("/settings/language", { language: form.elements.language.value });
|
|
21
|
+
if (response.ok) window.location.reload();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setUpTheme();
|
|
26
|
+
setUpLanguage();
|
|
27
|
+
setUpLearning();
|
|
28
|
+
setUpAsk();
|
|
29
|
+
setUpPalette();
|
|
30
|
+
setUpUpdate();
|
|
31
|
+
setUpShortcuts();
|
|
32
|
+
setUpOutline();
|
|
33
|
+
setUpResize();
|
|
34
|
+
setUpDiagrams();
|
|
35
|
+
setUpRepository();
|
|
36
|
+
setUpDocumentation();
|
|
37
|
+
setUpSidebar();
|
|
38
|
+
document.documentElement.dataset.ready = "true"; // every handler is in place
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// "On this page": an outline of the reading column's headings that follows the reader (design section 16.1).
|
|
2
|
+
const outline = document.querySelector("[data-outline]");
|
|
3
|
+
|
|
4
|
+
function slug(text, used) {
|
|
5
|
+
const base = text.toLowerCase().normalize("NFKD").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "") || "section";
|
|
6
|
+
let candidate = base;
|
|
7
|
+
for (let number = 2; used.has(candidate) || document.getElementById(candidate); number += 1) {
|
|
8
|
+
candidate = `${base}-${number}`;
|
|
9
|
+
}
|
|
10
|
+
used.add(candidate);
|
|
11
|
+
return candidate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setUpOutline() {
|
|
15
|
+
if (!outline) return;
|
|
16
|
+
const headings = [...document.querySelectorAll("main .prose h2, main .prose h3, main .checks > h2")];
|
|
17
|
+
const list = outline.querySelector("ol");
|
|
18
|
+
if (headings.length < 2 || !list) return;
|
|
19
|
+
const used = new Set();
|
|
20
|
+
const links = new Map();
|
|
21
|
+
for (const heading of headings) {
|
|
22
|
+
if (!heading.id) heading.id = slug(heading.textContent, used);
|
|
23
|
+
const item = document.createElement("li");
|
|
24
|
+
item.className = heading.tagName === "H3" ? "depth-3" : "depth-2";
|
|
25
|
+
const link = document.createElement("a");
|
|
26
|
+
link.href = `#${heading.id}`;
|
|
27
|
+
link.textContent = heading.textContent;
|
|
28
|
+
item.append(link);
|
|
29
|
+
list.append(item);
|
|
30
|
+
links.set(heading, link);
|
|
31
|
+
}
|
|
32
|
+
outline.hidden = false;
|
|
33
|
+
const observer = new IntersectionObserver(
|
|
34
|
+
(entries) => {
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.isIntersecting) continue;
|
|
37
|
+
for (const link of links.values()) link.removeAttribute("aria-current");
|
|
38
|
+
links.get(entry.target)?.setAttribute("aria-current", "location");
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{ rootMargin: "-70px 0px -70% 0px" },
|
|
42
|
+
);
|
|
43
|
+
for (const heading of headings) observer.observe(heading);
|
|
44
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// The command palette (design section 16.3): search the guide and run a few actions, all from the keyboard.
|
|
2
|
+
// Results are drawn with textContent and <mark> elements only; nothing from the server is parsed as markup.
|
|
3
|
+
import { getJSON } from "./api.js";
|
|
4
|
+
import { openAsk } from "./ask.js";
|
|
5
|
+
import { cycleTheme } from "./theme.js";
|
|
6
|
+
import { startUpdate } from "./update.js";
|
|
7
|
+
|
|
8
|
+
const dialog = document.querySelector("[data-palette]");
|
|
9
|
+
const input = dialog?.querySelector("[data-palette-input]");
|
|
10
|
+
const list = dialog?.querySelector("[data-palette-list]");
|
|
11
|
+
const status = dialog?.querySelector("[data-palette-status]");
|
|
12
|
+
const GROUPS = [
|
|
13
|
+
["pages", ["area", "concept", "path", "digest"]],
|
|
14
|
+
["answers", ["answer"]],
|
|
15
|
+
["decisions", ["decision"]],
|
|
16
|
+
["facts", ["fact"]],
|
|
17
|
+
];
|
|
18
|
+
let options = [];
|
|
19
|
+
let active = -1;
|
|
20
|
+
let pending = null;
|
|
21
|
+
let timer = null;
|
|
22
|
+
|
|
23
|
+
function label(name, query = "") {
|
|
24
|
+
return (dialog.dataset[name] ?? "").replace("{query}", query);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Resolved the way the browser will follow it: a check on the text alone misses `/\host` and `/<tab>/host`,
|
|
28
|
+
// which browsers read as `//host`. A `blob:` URL takes the origin of the URL inside it, so the scheme is checked too.
|
|
29
|
+
function isLocalLink(link) {
|
|
30
|
+
if (typeof link !== "string") return false;
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(link, window.location.origin);
|
|
33
|
+
return url.origin === window.location.origin && (url.protocol === "http:" || url.protocol === "https:");
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function heading(text) {
|
|
40
|
+
const item = document.createElement("li");
|
|
41
|
+
item.className = "palette-group";
|
|
42
|
+
item.setAttribute("role", "presentation");
|
|
43
|
+
item.textContent = text;
|
|
44
|
+
list.append(item);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function option(title, detail, run, kind) {
|
|
48
|
+
const item = document.createElement("li");
|
|
49
|
+
item.className = "palette-option";
|
|
50
|
+
item.id = `palette-option-${options.length}`;
|
|
51
|
+
item.setAttribute("role", "option");
|
|
52
|
+
item.setAttribute("aria-selected", "false");
|
|
53
|
+
const line = document.createElement("span");
|
|
54
|
+
line.className = "title";
|
|
55
|
+
line.textContent = title;
|
|
56
|
+
if (kind) {
|
|
57
|
+
line.lang = "en"; // a result from the guide, which is English; its kind is in the reader's language
|
|
58
|
+
line.dir = "ltr";
|
|
59
|
+
const badge = document.createElement("span");
|
|
60
|
+
badge.className = "kind";
|
|
61
|
+
badge.lang = document.documentElement.lang;
|
|
62
|
+
badge.dir = document.documentElement.dir;
|
|
63
|
+
badge.textContent = label(`kind${kind[0].toUpperCase()}${kind.slice(1)}`) || kind;
|
|
64
|
+
line.append(" ", badge);
|
|
65
|
+
}
|
|
66
|
+
item.append(line);
|
|
67
|
+
if (detail) item.append(detail);
|
|
68
|
+
item.addEventListener("mousedown", (event) => event.preventDefault()); // keep focus in the input
|
|
69
|
+
item.addEventListener("click", () => activate(options.indexOf(entry)));
|
|
70
|
+
const entry = { item, run };
|
|
71
|
+
options.push(entry);
|
|
72
|
+
list.append(item);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function snippet(segments) {
|
|
76
|
+
if (!Array.isArray(segments) || !segments.length) return null;
|
|
77
|
+
const line = document.createElement("span");
|
|
78
|
+
line.className = "snippet";
|
|
79
|
+
line.lang = "en";
|
|
80
|
+
line.dir = "ltr";
|
|
81
|
+
for (const [text, matched] of segments) {
|
|
82
|
+
if (matched) {
|
|
83
|
+
const mark = document.createElement("mark");
|
|
84
|
+
mark.textContent = String(text);
|
|
85
|
+
line.append(mark);
|
|
86
|
+
} else {
|
|
87
|
+
line.append(String(text));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return line;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function select(index) {
|
|
94
|
+
if (!options.length) {
|
|
95
|
+
active = -1;
|
|
96
|
+
input.removeAttribute("aria-activedescendant");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
active = (index + options.length) % options.length;
|
|
100
|
+
options.forEach((entry, position) => entry.item.setAttribute("aria-selected", String(position === active)));
|
|
101
|
+
const current = options[active].item;
|
|
102
|
+
input.setAttribute("aria-activedescendant", current.id);
|
|
103
|
+
current.scrollIntoView({ block: "nearest" });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function activate(index) {
|
|
107
|
+
const entry = options[index];
|
|
108
|
+
if (!entry) return;
|
|
109
|
+
dialog.close();
|
|
110
|
+
entry.run();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function actions(query) {
|
|
114
|
+
heading(label("groupActions"));
|
|
115
|
+
if (query) {
|
|
116
|
+
option(label("actionAsk", query), null, () => openAsk(query));
|
|
117
|
+
option(label("actionAll", query), null, () => {
|
|
118
|
+
window.location.href = `/search?q=${encodeURIComponent(query)}`;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
option(label("actionUpdate"), null, () => startUpdate());
|
|
122
|
+
option(label("actionProgress"), null, () => {
|
|
123
|
+
window.location.href = "/progress";
|
|
124
|
+
});
|
|
125
|
+
option(label("actionTheme"), null, () => cycleTheme());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function draw(query, results, message) {
|
|
129
|
+
list.replaceChildren();
|
|
130
|
+
options = [];
|
|
131
|
+
for (const [group, kinds] of GROUPS) {
|
|
132
|
+
const found = results.filter((result) => kinds.includes(result.kind) && isLocalLink(result.link));
|
|
133
|
+
if (!found.length) continue;
|
|
134
|
+
heading(label(`group${group[0].toUpperCase()}${group.slice(1)}`));
|
|
135
|
+
for (const result of found) {
|
|
136
|
+
option(String(result.title), snippet(result.snippet), () => {
|
|
137
|
+
window.location.href = result.link;
|
|
138
|
+
}, result.kind);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
actions(query);
|
|
142
|
+
status.textContent = message;
|
|
143
|
+
input.setAttribute("aria-expanded", "true");
|
|
144
|
+
select(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function search() {
|
|
148
|
+
const query = input.value.trim();
|
|
149
|
+
pending?.abort();
|
|
150
|
+
if (!query) {
|
|
151
|
+
draw("", [], "");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
pending = new AbortController();
|
|
155
|
+
status.textContent = label("labelSearching");
|
|
156
|
+
try {
|
|
157
|
+
const { results, available } = await getJSON(`/search/results?q=${encodeURIComponent(query)}`, {
|
|
158
|
+
signal: pending.signal,
|
|
159
|
+
});
|
|
160
|
+
let message = "";
|
|
161
|
+
if (!available) message = label("labelUnavailable");
|
|
162
|
+
else if (!results.length) message = label("labelEmpty", query);
|
|
163
|
+
draw(query, results, message);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error.name !== "AbortError") draw(query, [], label("labelUnavailable"));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function openPalette(initial = "") {
|
|
170
|
+
if (!dialog || dialog.open) return;
|
|
171
|
+
input.value = initial;
|
|
172
|
+
dialog.showModal();
|
|
173
|
+
input.focus();
|
|
174
|
+
search();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function setUpPalette() {
|
|
178
|
+
if (!dialog) return;
|
|
179
|
+
input.addEventListener("input", () => {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
timer = setTimeout(search, 120);
|
|
182
|
+
});
|
|
183
|
+
input.addEventListener("keydown", (event) => {
|
|
184
|
+
if (event.key === "ArrowDown") {
|
|
185
|
+
event.preventDefault();
|
|
186
|
+
select(active + 1);
|
|
187
|
+
} else if (event.key === "ArrowUp") {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
select(active - 1);
|
|
190
|
+
} else if (event.key === "Enter" && !event.isComposing) {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
activate(active);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
dialog.addEventListener("close", () => {
|
|
196
|
+
pending?.abort();
|
|
197
|
+
input.setAttribute("aria-expanded", "false");
|
|
198
|
+
});
|
|
199
|
+
dialog.addEventListener("click", (event) => {
|
|
200
|
+
if (event.target === dialog) dialog.close(); // a click on the backdrop
|
|
201
|
+
});
|
|
202
|
+
const form = document.querySelector("[data-search-form]");
|
|
203
|
+
form?.addEventListener("submit", (event) => {
|
|
204
|
+
event.preventDefault();
|
|
205
|
+
openPalette(form.elements.q.value);
|
|
206
|
+
});
|
|
207
|
+
form?.elements.q.addEventListener("focus", (event) => {
|
|
208
|
+
event.target.blur();
|
|
209
|
+
openPalette(event.target.value);
|
|
210
|
+
});
|
|
211
|
+
}
|