1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
const tbody = document.getElementById('tbody');
const statusSpan = document.getElementById('status');
function newEl(tagname, content) {
const cell = document.createElement(tagname);
if (content instanceof Node)
cell.appendChild(content);
else
cell.textContent = content;
return cell;
}
const updateUrl = debounce(params => {
if (Object.keys(params).length == 0)
window.history.pushState({}, '', '/');
else
window.history.pushState({}, '', '?' + new URLSearchParams(params).toString());
}, 500);
function normalize(str) {
return str.toLowerCase().replaceAll('_', ' ')
}
function escapeForRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function display(proposals) {
const params = {};
if (nameInput.value) {
const regex = new RegExp('\\b' + escapeForRegex(normalize(nameInput.value)));
proposals = proposals.filter(p => regex.test(normalize(p.name || p.page_title)));
params.q = nameInput.value;
}
if (authorsInput.value) {
// The first letter of MediaWiki usernames is case-insensitive.
const firstChar = authorsInput.value.charAt(0);
const rest = authorsInput.value.slice(1);
const lowercase = firstChar.toLowerCase() + rest;
const uppercase = firstChar.toUpperCase() + rest;
proposals = proposals.filter(p => p.authors && (p.authors.includes(lowercase) || p.authors.includes(uppercase)));
params.author = authorsInput.value;
}
const nf = Intl.NumberFormat();
statusSpan.textContent = `Found ${nf.format(proposals.length)} proposals.`;
updateUrl(params);
tbody.innerHTML = '';
proposals.forEach(proposal => {
const row = document.createElement('tr');
const statusCell = newEl('td', proposal.status);
statusCell.className = 'status-' + proposal.status;
row.appendChild(statusCell);
let date = '???';
if (proposal.status == 'voting' || proposal.status == 'approved' || proposal.status == 'rejected') {
date = proposal.vote_start;
} else if (proposal.status == 'proposed') {
date = proposal.rfc_start;
} else {
date = proposal.draft_start;
}
row.appendChild(newEl('td', date));
const link = newEl('a', proposal.name || proposal.page_title);
link.href = 'https://wiki.openstreetmap.org/wiki/' + proposal.page_title.replaceAll(' ', '_');
const nameCell = newEl('td', link);
if (proposal.lang)
nameCell.appendChild(document.createTextNode(` (${proposal.lang})`));
row.appendChild(nameCell);
row.appendChild(newEl('td', proposal.authors));
tbody.appendChild(row);
});
}
const nameInput = document.getElementById('name');
const authorsInput = document.getElementById('authors');
function debounce(callback, wait) {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
(async function() {
const proposals = await (await fetch('proposals.json')).json();
const params = new URLSearchParams(location.search);
nameInput.value = params.get('q');
authorsInput.value = params.get('author');
display(proposals);
nameInput.addEventListener('input', debounce(e => {
display(proposals);
}, 100));
authorsInput.addEventListener('input', debounce(e => {
display(proposals);
}, 100));
})();
|