Jump to…
snowfeat: given a new redesign and emulated terminal homepagerxkspqmsoknz1mo
1// The ⌘K palette. Enhancement only.
2//
3// Without this script the masthead control is a plain link to /search and the
4// overlay stays `hidden`, so nothing here is load-bearing — it is a faster
5// route to a page that is reachable anyway. That is also why the ⌘K keycap
6// starts hidden in the markup: advertising a shortcut that does not work is
7// worse than not advertising it.
8//
9// Results come from htmx hitting /search?fragment=1. This file never renders a
10// result, so there is no second copy of the visibility rule to get wrong.
11(function () {
12 var backdrop = document.querySelector("[data-palette]");
13 var input = document.querySelector("[data-palette-input]");
14 if (!backdrop || !input) return;
15
16 var opener = document.querySelector("[data-palette-open]");
17 var kbd = document.querySelector("[data-palette-kbd]");
18 var lastFocus = null;
19
20 // Mac gets ⌘, everything else Ctrl. Checking the platform is unreliable in
21 // general but perfectly adequate for choosing which glyph to draw.
22 var isMac = /Mac|iPhone|iPad/.test(navigator.platform || "");
23 if (kbd) {
24 kbd.textContent = isMac ? "⌘K" : "Ctrl K";
25 kbd.hidden = false;
26 }
27
28 function open() {
29 if (!backdrop.hidden) return;
30 lastFocus = document.activeElement;
31 backdrop.hidden = false;
32 input.value = "";
33 input.focus();
34 }
35
36 function close() {
37 if (backdrop.hidden) return;
38 backdrop.hidden = true;
39 if (lastFocus && lastFocus.focus) lastFocus.focus();
40 }
41
42 if (opener) {
43 opener.addEventListener("click", function (e) {
44 e.preventDefault();
45 open();
46 });
47 }
48
49 document.addEventListener("keydown", function (e) {
50 var accel = isMac ? e.metaKey : e.ctrlKey;
51 if (accel && (e.key === "k" || e.key === "K")) {
52 e.preventDefault();
53 backdrop.hidden ? open() : close();
54 return;
55 }
56 if (e.key === "Escape" && !backdrop.hidden) {
57 e.preventDefault();
58 close();
59 }
60 });
61
62 // Clicking the backdrop closes; clicking the panel inside it must not.
63 backdrop.addEventListener("click", function (e) {
64 if (e.target === backdrop) close();
65 });
66
67 // Arrow keys walk the results. The input keeps focus throughout so typing
68 // never has to be resumed — only Enter leaves, by following the active row.
69 function items() {
70 return Array.prototype.slice.call(
71 backdrop.querySelectorAll(".palette-item")
72 );
73 }
74
75 function activeIndex(list) {
76 for (var i = 0; i < list.length; i++) {
77 if (list[i].classList.contains("is-active")) return i;
78 }
79 return -1;
80 }
81
82 function activate(list, i) {
83 list.forEach(function (el) {
84 el.classList.remove("is-active");
85 });
86 if (list[i]) {
87 list[i].classList.add("is-active");
88 list[i].scrollIntoView({ block: "nearest" });
89 }
90 }
91
92 input.addEventListener("keydown", function (e) {
93 var list = items();
94 if (!list.length) return;
95 var i = activeIndex(list);
96
97 if (e.key === "ArrowDown") {
98 e.preventDefault();
99 activate(list, i + 1 >= list.length ? 0 : i + 1);
100 } else if (e.key === "ArrowUp") {
101 e.preventDefault();
102 activate(list, i <= 0 ? list.length - 1 : i - 1);
103 } else if (e.key === "Enter") {
104 // With nothing selected, fall through to the form's own behaviour:
105 // a full search for whatever was typed.
106 if (i < 0) return;
107 e.preventDefault();
108 list[i].click();
109 }
110 });
111
112 // Fresh results, fresh selection: the first row is pre-selected so Enter
113 // does the obvious thing immediately after typing.
114 document.body.addEventListener("htmx:afterSwap", function (e) {
115 if (e.target && e.target.id === "palette-results") {
116 var list = items();
117 if (list.length) activate(list, 0);
118 }
119 });
120
121 backdrop.addEventListener("click", function (e) {
122 var cmd = e.target.closest && e.target.closest("[data-palette-theme]");
123 if (cmd && window.dogfoodToggleTheme) {
124 e.preventDefault();
125 window.dogfoodToggleTheme();
126 close();
127 }
128 });
129})();

129 lines · JavaScript