snapshot of c000b74a5b09c433f96a69fa102c5c8f6583dff0 Annotations about the code that implements koment.

internal/ui/assets/koment.js

1 (function () {
2 "use strict";
3
4 var switcher = document.querySelector("[data-switcher]");
5 if (switcher) {
6 switcher.addEventListener("change", function () {
7 var destination = sameOriginDestination(switcher.value);
8 if (destination) {
9 window.location.assign(destination);
10 }
11 });
12 }
13
14 setupSearch();
15 alignNotes();
16
17 function sameOriginDestination(value) {
18 if (typeof value !== "string" || value.length === 0) {
19 return null;
20 }
21 var target;
22 try {
23 target = new URL(value, document.baseURI);
24 } catch (error) {
25 return null;
26 }
27 if (target.origin !== window.location.origin) {
28 return null;
29 }
30 if (target.protocol !== "http:" && target.protocol !== "https:") {
31 return null;
32 }
33 return target.href;
34 }
35
36 function setupSearch() {
37 var dialog = document.querySelector("[data-search-dialog]");
38 var trigger = document.querySelector("[data-search-open]");
39 var close = document.querySelector("[data-search-close]");
40 var input = document.querySelector("[data-search-input]");
41 var results = document.querySelector("[data-search-results]");
42 var tree = document.querySelector("[data-tree]");
43 if (!dialog || !trigger || !close || !input || !results || !tree) return;
44
45 var files = Array.prototype.slice.call(tree.querySelectorAll(".file"));
46 var shortcut = document.querySelector("[data-search-shortcut]");
47 var platform = navigator.userAgentData && navigator.userAgentData.platform || navigator.platform || navigator.userAgent;
48 var apple = /Mac|iPhone|iPad|iPod/i.test(platform);
49 var selected = 0;
50 var rendered = [];
51 var returnFocus = null;
52
53 shortcut.textContent = apple ? "⌘K" : "Ctrl K";
54 trigger.hidden = false;
55
56 function editing(target) {
57 if (!target) return false;
58 var name = target.tagName;
59 return target.isContentEditable || name === "INPUT" || name === "TEXTAREA" || name === "SELECT";
60 }
61
62 function matches() {
63 var needle = input.value.trim().toLowerCase();
64 var found = files.filter(function (file) {
65 return !needle || file.dataset.search.indexOf(needle) !== -1;
66 }).slice(0, 50);
67
68 results.replaceChildren();
69 rendered = found.map(function (file, index) {
70 var result = document.createElement("a");
71 var dot = document.createElement("span");
72 var path = document.createElement("span");
73 var meta = document.createElement("span");
74
75 result.className = "search-result";
76 result.href = file.href;
77 result.setAttribute("role", "option");
78 dot.className = "dot " + file.dataset.status;
79 path.className = "search-result-path";
80 path.textContent = file.dataset.path;
81 meta.className = "search-result-meta";
82 meta.textContent = file.dataset.count + (file.dataset.count === "1" ? " annotation" : " annotations");
83 result.append(dot, path, meta);
84 results.appendChild(result);
85 if (index === selected) result.classList.add("selected");
86 result.addEventListener("mousemove", function () { select(index); });
87 return result;
88 });
89
90 if (!rendered.length) {
91 var empty = document.createElement("p");
92 empty.className = "search-empty";
93 empty.textContent = "No matching annotations";
94 results.appendChild(empty);
95 }
96 selected = Math.min(selected, Math.max(rendered.length - 1, 0));
97 select(selected);
98 }
99
100 function select(index) {
101 if (!rendered.length) return;
102 selected = (index + rendered.length) % rendered.length;
103 rendered.forEach(function (result, candidate) {
104 var current = candidate === selected;
105 result.classList.toggle("selected", current);
106 result.setAttribute("aria-selected", current ? "true" : "false");
107 });
108 rendered[selected].scrollIntoView({ block: "nearest" });
109 }
110
111 function openSearch() {
112 if (dialog.open) return;
113 returnFocus = document.activeElement;
114 selected = 0;
115 input.value = "";
116 matches();
117 if (typeof dialog.showModal === "function") dialog.showModal();
118 else dialog.setAttribute("open", "");
119 input.focus();
120 }
121
122 function closeSearch() {
123 if (!dialog.open) return;
124 if (typeof dialog.close === "function") dialog.close();
125 else dialog.removeAttribute("open");
126 if (returnFocus && typeof returnFocus.focus === "function") returnFocus.focus();
127 }
128
129 trigger.addEventListener("click", openSearch);
130 close.addEventListener("click", closeSearch);
131 input.addEventListener("input", function () { selected = 0; matches(); });
132 input.addEventListener("keydown", function (event) {
133 if (event.key === "ArrowDown" || event.key === "ArrowUp") {
134 event.preventDefault();
135 select(selected + (event.key === "ArrowDown" ? 1 : -1));
136 }
137 if (event.key === "Enter" && rendered[selected]) {
138 event.preventDefault();
139 rendered[selected].click();
140 }
141 });
142 dialog.addEventListener("click", function (event) {
143 if (event.target === dialog) closeSearch();
144 });
145 dialog.addEventListener("close", function () {
146 if (returnFocus && typeof returnFocus.focus === "function") returnFocus.focus();
147 });
148 document.addEventListener("keydown", function (event) {
149 var modifier = apple ? event.metaKey : event.ctrlKey;
150 if ((modifier && event.key.toLowerCase() === "k") || (event.key === "/" && !editing(event.target))) {
151 event.preventDefault();
152 openSearch();
153 } else if (event.key === "Escape" && dialog.open) {
154 event.preventDefault();
155 closeSearch();
156 }
157 });
158
159 matches();
160 }
161
162 function alignNotes() {
163 var reading = document.querySelector("[data-reading]");
164 var gloss = document.querySelector("[data-gloss]");
165 if (!reading || !gloss) return;
166
167 var notes = Array.prototype.slice.call(gloss.querySelectorAll(".note[data-for]"));
168 if (!notes.length) return;
169
170 var code = reading.querySelector(".code");
171 var narrow = window.matchMedia("(max-width: 900px)");
172 var scheduled = null;
173 var interleaved = false;
174 var noteGap = 12;
175
176 function interleave() {
177 if (interleaved || !code) return;
178 var last = {};
179 notes.forEach(function (note) {
180 var row = document.getElementById("L" + note.dataset.for);
181 if (!row || row.parentNode !== code) return;
182 var behind = last[note.dataset.for] || row;
183 code.insertBefore(note, behind.nextSibling);
184 last[note.dataset.for] = note;
185 });
186 gloss.classList.add("interleaved");
187 interleaved = true;
188 }
189
190 function column() {
191 if (!interleaved) return;
192 notes.forEach(function (note) { gloss.appendChild(note); });
193 gloss.classList.remove("interleaved");
194 interleaved = false;
195 }
196
197 function place() {
198 scheduled = null;
199
200 if (narrow.matches) {
201 notes.forEach(function (note) { note.style.transform = ""; });
202 gloss.classList.remove("aligned");
203 gloss.style.height = "";
204 interleave();
205 return;
206 }
207
208 column();
209 gloss.classList.add("aligned");
210 gloss.style.height = "";
211 notes.forEach(function (note) {
212 note.style.transform = "";
213 });
214
215 var top = gloss.getBoundingClientRect().top;
216 var floor = 0;
217
218 notes.forEach(function (note) {
219 var row = document.getElementById("L" + note.dataset.for);
220 if (!row) return;
221
222 var wanted = row.getBoundingClientRect().top - top;
223 var resting = note.offsetTop;
224 var placed = Math.max(wanted, floor);
225
226 note.style.transform = "translateY(" + (placed - resting) + "px)";
227 floor = placed + note.offsetHeight + noteGap;
228 });
229
230 gloss.style.height = floor + "px";
231 }
232
233 function schedule() {
234 if (scheduled) return;
235 scheduled = window.requestAnimationFrame(place);
236 }
237
238 place();
239 window.addEventListener("resize", schedule);
240 reading.addEventListener("toggle", schedule, true);
241 if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);
242
243 notes.forEach(function (note) {
244 var row = document.getElementById("L" + note.dataset.for);
245 if (!row) return;
246 note.addEventListener("mouseenter", function () { row.classList.add("lit"); });
247 note.addEventListener("mouseleave", function () { row.classList.remove("lit"); });
248 row.addEventListener("mouseenter", function () { note.classList.add("lit"); });
249 row.addEventListener("mouseleave", function () { note.classList.remove("lit"); });
250 });
251 }
252 })();

Find an annotation

Search file paths, rationale, kinds, and authors.

moveEnter openEsc close