internal/ui/assets/koment.js
1
(function () {
2
"use strict";
4
var switcher = document.querySelector("[data-switcher]");
5
if (switcher) {
6
switcher.addEventListener("change", function () {
7
window.location.href = switcher.value;
8
});
9
}
11
setupSearch();
12
alignNotes();
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;
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;
31
shortcut.textContent = apple ? "⌘K" : "Ctrl K";
32
trigger.hidden = false;
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
}
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);
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");
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
});
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
}
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
}
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
}
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
}
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
});
137
matches();
138
}
140
function alignNotes() {
141
var reading = document.querySelector("[data-reading]");
142
var gloss = document.querySelector("[data-gloss]");
143
if (!reading || !gloss) return;
145
var notes = Array.prototype.slice.call(gloss.querySelectorAll(".note[data-for]"));
146
if (!notes.length) return;
148
var narrow = window.matchMedia("(max-width: 900px)");
149
var scheduled = null;
151
function place() {
152
scheduled = null;
154
if (narrow.matches) {
155
notes.forEach(function (note) { note.style.transform = ""; });
156
gloss.classList.remove("aligned");
157
return;
158
}
160
var top = gloss.getBoundingClientRect().top;
161
var floor = 0;
163
notes.forEach(function (note) {
164
note.style.transform = "";
165
});
167
notes.forEach(function (note) {
168
var row = document.getElementById("L" + note.dataset.for);
169
if (!row) return;
171
var wanted = row.getBoundingClientRect().top - top;
172
var resting = note.offsetTop;
173
var placed = Math.max(wanted, floor);
175
note.style.transform = "translateY(" + (placed - resting) + "px)";
176
floor = placed + note.offsetHeight + 12;
177
});
179
gloss.classList.add("aligned");
180
}
182
function schedule() {
183
if (scheduled) return;
184
scheduled = window.requestAnimationFrame(place);
185
}
187
place();
188
window.addEventListener("resize", schedule);
189
if (document.fonts && document.fonts.ready) document.fonts.ready.then(schedule);
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
})();