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

Find an annotation

Search file paths, rationale, kinds, and authors.

moveEnter openEsc close