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
|
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
.pragma library
// Pure helpers for the user page, tested by test_user.js under node.
// `userctl get` output into an object.
function parse(text) {
const out = {};
for (const line of text.split("\n")) {
const i = line.indexOf("=");
if (i > 0) out[line.slice(0, i)] = line.slice(i + 1);
}
return out;
}
// `userctl keys` output into [{fpr, uid}].
function keys(text) {
return text.split("\n").filter(l => l.includes("\t"))
.map(l => ({ fpr: l.slice(0, l.indexOf("\t")), uid: l.slice(l.indexOf("\t") + 1) }));
}
// `locale -a` prints it_IT.utf8; AccountsService stores it_IT.UTF-8.
function locales(text) {
return text.split("\n")
.filter(l => /\.utf-?8$/i.test(l) && !/^C\./.test(l))
.map(l => l.replace(/\.utf-?8$/i, ".UTF-8"))
.sort();
}
// The name git gets, for the page's preview. ponytail: duplicates userctl's
// gitname; both tests pin the same cases, keep them in step.
function gitName(name) {
const w = name.trim().split(/\s+/).filter(s => s !== "");
if (w.length < 2) return w.join(" ");
return w.slice(0, -1).join(" ") + " " + Array.from(w[w.length - 1])[0] + ".";
}
// Nominatim jsonv2 search results as location labels, "place, region,
// country", deduplicated. Anything that is not a JSON array (an error page,
// a rate-limit notice) is no suggestions rather than an error.
function places(text) {
let rows;
try { rows = JSON.parse(text); } catch (e) { return []; }
if (!Array.isArray(rows)) return [];
const out = [];
for (const r of rows) {
const a = r.address || {};
const parts = [a.city || a.town || a.village || a.hamlet || a.municipality || r.name,
a.state, a.country].filter(p => p);
const label = parts.filter((p, i) => parts.indexOf(p) === i).join(", ");
if (label && !out.includes(label)) out.push(label);
}
return out;
}
// The drawer's greeting, from ~/.config/keystone/greetings: one per line,
// {user} for the login name, blank lines and # comments skipped. A missing or
// empty file is the built-in set. The row is rebuilt on every drawer open, so
// the last pick lives here, in the shared library, to never show the same one
// twice running.
const defaultGreetings = ["Hello {user}", "Hi {user}", "Welcome back, {user}",
"Hey there, {user}", "Good to see you, {user}"];
let lastGreeting = -1;
function greeting(text, user, random) {
const lines = text.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("#"));
const all = lines.length ? lines : defaultGreetings;
const skip = all.length > 1 && lastGreeting >= 0 && lastGreeting < all.length;
let i = Math.floor((random ?? Math.random()) * (all.length - skip));
if (skip && i >= lastGreeting) i++;
lastGreeting = i;
return all[i].replace(/\{user\}/g, user);
}
|