hmgr 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/checkout-pfad.js +5 -20
- package/dist/adapter/claude-code.js +21 -17
- package/dist/adapter/dateisystem.js +105 -5
- package/dist/adapter/git.js +124 -11
- package/dist/adapter/hermes.js +163 -10
- package/dist/adapter/quelle-text.js +1 -3
- package/dist/adapter/skill-md.js +94 -18
- package/dist/adapter/terminal.js +24 -1
- package/dist/adapter/zustand-json.js +4 -0
- package/dist/cli/hilfe.js +144 -27
- package/dist/cli/main.js +374 -28
- package/dist/gui/icons.js +114 -0
- package/dist/gui/main.js +481 -0
- package/dist/gui/oeffnen.js +33 -0
- package/dist/gui/preload.cjs +55 -0
- package/dist/gui/renderer/fonts/Inter-LICENSE.txt +92 -0
- package/dist/gui/renderer/fonts/InterVariable.woff2 +0 -0
- package/dist/gui/renderer/fonts/JetBrainsMono-Medium.woff2 +0 -0
- package/dist/gui/renderer/fonts/JetBrainsMono-OFL.txt +93 -0
- package/dist/gui/renderer/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/dist/gui/renderer/index.html +491 -0
- package/dist/gui/renderer/renderer.js +2611 -0
- package/dist/gui/renderer/styles.css +638 -0
- package/dist/kern/anwendungsfaelle.js +798 -68
- package/dist/kern/modell.js +175 -1
- package/dist/kern/quelle-ort.js +42 -0
- package/dist/ports/harness.js +13 -0
- package/dist/ports/zustand.js +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,2611 @@
|
|
|
1
|
+
// hmgr-GUI, Renderer. Kein Bundler, kein Framework: DOM direkt gebaut, Daten über window.hmgr (IPC).
|
|
2
|
+
(function () {
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/* ---------------- Kleinkram ---------------- */
|
|
6
|
+
|
|
7
|
+
function $(id) { return document.getElementById(id); }
|
|
8
|
+
function e(tag, klasse, text) {
|
|
9
|
+
var el = document.createElement(tag);
|
|
10
|
+
if (klasse) el.className = klasse;
|
|
11
|
+
if (text != null) el.textContent = text;
|
|
12
|
+
return el;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Home-Verzeichnis, einmal beim Laden über hmgr:home geholt (siehe ladeHome unten). Nur für die
|
|
16
|
+
// Anzeige: fließt nie in einen IPC-Aufruf oder einen zielKey zurück.
|
|
17
|
+
var HOME = '';
|
|
18
|
+
/** Kürzt einen absoluten Pfad auf ~, wenn er mit dem Home-Verzeichnis beginnt (Grenze am „/"). */
|
|
19
|
+
function pfadKurz(p) {
|
|
20
|
+
if (!HOME || typeof p !== 'string') return p;
|
|
21
|
+
if (p === HOME) return '~';
|
|
22
|
+
if (p.indexOf(HOME + '/') === 0) return '~' + p.slice(HOME.length);
|
|
23
|
+
return p;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function melde(text) {
|
|
27
|
+
$('meldungtext').textContent = text;
|
|
28
|
+
$('meldung').hidden = false;
|
|
29
|
+
}
|
|
30
|
+
$('meldungzu').addEventListener('click', function () { $('meldung').hidden = true; });
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Hinweiszeile im Erfolgsdialog (E26, F32). Jeder Aufruf, der d-erfolg füllt, ruft das auf, auch
|
|
34
|
+
* mit leerer Liste: sonst stünde der Hinweis des vorigen Laufs noch da. Doppelte Hinweise stehen
|
|
35
|
+
* einmal, ein Lauf über mehrere Skills sagt dieselbe Sache sonst N-mal.
|
|
36
|
+
*/
|
|
37
|
+
function setzeErfolgHinweise(hinweise) {
|
|
38
|
+
var einmalig = [];
|
|
39
|
+
(hinweise || []).forEach(function (h) {
|
|
40
|
+
if (h && einmalig.indexOf(h) < 0) einmalig.push(h);
|
|
41
|
+
});
|
|
42
|
+
$('de-hinweis').textContent = einmalig.join('\n');
|
|
43
|
+
$('de-hinweis').hidden = einmalig.length === 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Ruft einen window.hmgr-Kanal auf, meldet einen Fehler in der Leiste statt in der Konsole. */
|
|
47
|
+
async function rufe(fn) {
|
|
48
|
+
var ergebnis = await fn();
|
|
49
|
+
if (!ergebnis.ok) {
|
|
50
|
+
melde(ergebnis.fehler);
|
|
51
|
+
throw new Error(ergebnis.fehler);
|
|
52
|
+
}
|
|
53
|
+
return ergebnis.daten;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ---------------- Dialoge ---------------- */
|
|
57
|
+
|
|
58
|
+
var fokusStapel = [];
|
|
59
|
+
function zeige(dlg) {
|
|
60
|
+
fokusStapel.push(document.activeElement);
|
|
61
|
+
dlg.returnValue = '';
|
|
62
|
+
dlg.showModal();
|
|
63
|
+
}
|
|
64
|
+
document.querySelectorAll('dialog').forEach(function (dlg) {
|
|
65
|
+
dlg.addEventListener('close', function () {
|
|
66
|
+
var ziel = fokusStapel.pop();
|
|
67
|
+
if (ziel && ziel.focus && ziel.isConnected) ziel.focus();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
function nachDialog(dlg, fn) {
|
|
71
|
+
dlg.addEventListener('close', function () { fn(dlg.returnValue); });
|
|
72
|
+
}
|
|
73
|
+
// Enter im einzigen Textfeld meint Bestätigen. Ohne das drückt die implizite Formular-Absendung
|
|
74
|
+
// den ersten Submit-Knopf, und der ist in den Dialogen „Abbrechen".
|
|
75
|
+
function enterBestaetigt(feld, knopf) {
|
|
76
|
+
feld.addEventListener('keydown', function (ev) {
|
|
77
|
+
if (ev.key !== 'Enter') return;
|
|
78
|
+
ev.preventDefault();
|
|
79
|
+
knopf.click();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ---------------- Harness-Zeichen ---------------- */
|
|
84
|
+
|
|
85
|
+
// Originalzeichen der Harnesses: Claude-Stern (Anthropics Claude-Symbol, Pfad aus simple-icons,
|
|
86
|
+
// identisch mit dem offiziellen Logo-SVG; Füllpfad, kein Strich), Agent-Skills-Hexagon (Favicon
|
|
87
|
+
// von agentskills.io, dem Standard hinter ~/.agents). Beide einfarbig über currentColor.
|
|
88
|
+
var HARNESS_ZEICHEN = {
|
|
89
|
+
'claude-code': { viewBox: '0 0 24 24', inhalt: '<path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/>' },
|
|
90
|
+
agents: { viewBox: '0 0 32 32', inhalt: '<path d="M16 0.5L29.4234 8.25V23.75L16 31.5L2.57661 23.75V8.25L16 0.5Z"/>' },
|
|
91
|
+
};
|
|
92
|
+
// Hermes hat kein reduzierbares Vektorzeichen (das Original ist eine Manga-Illustration); hier
|
|
93
|
+
// das 32px-Favicon aus Tims lokaler Hermes-Installation, weißer Grund inklusive.
|
|
94
|
+
var HARNESS_BILD = {
|
|
95
|
+
hermes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAKSUlEQVR4AYxXCXBNWRr+zn0v+yL2RpOtBT2tRULb1yiji+5B2UONaqbH0pIY+9aoooZuhLKvZeyjGU0mtoSyTQvVmCSSEEIwQSSRhOxvm+8/74momqmZW/e8e+655/zr9y/PmDdvXsrp06drzp8/b/l/xrlz5yyJiYmW48ePWw4dPmw5cuSvlhMnTljOnDnzwfmk/0FPeM6ePTvFaNeunREVFWXq37+/+T+PKHO/fv3MvXv3Nkd0jDCHtW5tdnP3MKenp5t379pl/uGH1eZNmzaZz5w5a66srDJHRnYy9+3bz9wvqj/pRXHI0zX0mmvev79JeBu8wEvJ0zAZMAznUErB4XCgsLAAlBYLFy7EjBkzsGbtWuT96xlGjRyFpUuXIjT0E9y8eRNbtmzGuLFjMHLkCCQm/h0V5RUgCRc9EzRt5aRtMpxPpRQMvLsUJw4O3sLYYrHg7NmzGDt2LCZMmIB18fE4fOQwNm/ejGnTpmHiNxNRWVmJrVu3YPr06RDByysqcPHiRUyeNEkL9+bNG1KTm4R5y0xGnSkMpRQlVQ5VZ7WwsBCrV63SjK9cuQJNiNaQw0opVFdXI/WfqYiJicGtX29h7ty5iI6Ohslk4haFUjI+ePAgDhw4AFGEi//1rrWA8Hc47MjPz8eiRYuwfsMGVFBDPz8/mM1mraFQcXd3R1hYGLp164Zly5bj0uVLdFMhZs6cSXeEwtfXB16eXggPD4e3tzeIFdhsNjn6wVCut1oB5N1qtSEpKQm5jx/B398fvXr2QOPGjdG+fXt06tQJISEhaNWqFWJiYzUeQkKCkZmZiYyMDP1t/Pjx+Pp3X6Nrt66YMmUKPv30N9i4cSMFLBLydQbx5XqrFcBBE+fl5SEiIgKxcTMxbNhwfPvtH7Fy5UrExcVh4aKFWLFiBbZv347hw4ejrOwtfj9xIq5fv04cbEV5eRmGDhuG4tfFMNFia9aswfr18UhJScGdO7ch9IF3eou9nfNaAcrKyvDzzychPi8tLaXfS7Ft2zY94uPjERsTi/nz54N5A9OnTsO+v+xDzsOH1K4Q165doyApIBcUMGrupqcjjePO7dsoKS5GQkICampqwA2olQEiBFArwMuXL/HTT0exevVqzJr1Jw2gywSg+Fj8KFq9ePECt27dwqmEU7hKplarFXa7Q4Nyx44dGgdWixXFZBpQrx6ec38No+nixQvIzc2Fvpx89VTx1xDTcKgnT54g614WXr16RSC+0hI77DbYbXYOG3x9fFCPRO12u0a2hRrxHEk4YONaamqqxoKgXvZIWMq8uroKeXkvdK7g5g9ukaXWAiUlJaiqrEJVVZXeZBgmNGveXEeAg6FXWlKKDh066GhQSkExmUApvVdML6FbVFSEBw8eaIuUlZfD08OTilhQTZqSrMRicsB1CvKszQMSbm5ubqRFufjFMBQ+YZaLZnx3ZgQ0aNgQffr0Ra9evdCjRw8MGjQI7T/7DHJGiFYzN4jGYhmxgDD19fOFjW6y2qw6WsrKymWry/saEai1QFjrMAQGBcLH2wdmkxuGDR2q88G6detwYP9+fPXVEJw9ewbBwcFYy3S8d+9eHD16FIGBQVBKacs0aNAAzVu00FYT0Pn6+oIftVLPnz9HSUmxnqPOZWhxqPHHLT/GgvkLEREZgTZtwrRfBXBeXt4Ibd0aAwcOJODsKCktQcr1FPgxT7QgM09PD01OLBEZGYnu3bujfkB9iLkFG4Id2SDAlDCXOchPP/lj6BdaXQiMGjUSe3bv1rH/9NkzyBBDGcoAKxekIE2dMhXjosfBne4SLaUekA5q6ILk5CQcO3YMhUWFXHKgiCldXEcD4e3bt7h79662QN20b3Bn7W1iLg8JDUWTJk3gR/N99910eHiIhg7Ur18fQ4YMwYABA9CQeJBDAjrRTKKBOpA4xWVC0+9ceMN84uHpyRphpoA1uHHjBuzyXQ67xgcCyJpSCoLo4OAQ1PPzZ2RUcvmdzRQUraG4IkxycnK0ZnzVt9QMGUrJDmhmlayQbmY32FhnfmUOKWcG1ZtdPy4BnAdcayhgLnBzd9NVTRkG7MwHTppUi6CRX0H6XdYA+QbXcVmTwiPCGTzn7ubOFF0Ow2RwiwOPHz1CVtZ9WkooOLkZzsf7BVpIZ7BMEr969Sp27dqlS68AyrnX+Sshl6F9qiAY8fLygp1aCnNyg9nNDV7eXpDwFNe6u3tAhLty5bJ+Qi4FuARQ8qqHaCRofcg8L8Ds0qULmjRtwm/v9/CFmlUg9/FjTh346KNmYMvGufMWkJkMg7436azpYLr28fGFhLAoJUlK76TeWgCr1YKCggJtmuqaakjMCg6SLyRr9DdnRjScPtDn5KeiohwFRLlo3LdvH3RkFVUOp5CkC8GCRIlobXPY+G5Cj549Ibgpfv0aYiWllFhAITU1TTcOdC/K3pZpASSOE04lsD9I1ulZTCmdkQzxdQXBVc50K4zc2aSkpFyHYTgFMAyDcwOyR/ZKgVJKIZypXCqthHcxy7aDDLUFbt++hXv37mkfPmP8S9UDLyEwZ85sxMbGIi4uDuMnjMekSd/ofC8CWmg5YWYyTBjO/sGbBYvHtOklP4j2YqGaGgskHL2ZZdk5o5DWTkqmYqw9rIZS3ay67q9bF48lS5Zo/0rbpZTCY/p5N5PTzp07kZiQCOkZpGXLz39JKyp4Ms4FJ5IPRCgRQJ7CVObO4YA/W7v09DRioka7+v79+7ps0wIKAfUDIMln1ao/I+NuBiawtRLUN2rUSJtStBBTislEK+mWRdhQJi1x2Zy5c3QfIVpTKs0ANK+TOaCUgoDw1MmTungppZB97z4rpRUG5/RNOCSvT2SLFRMbg8ysDLxicxrKaiiMUefyZLiFtWmD6HHRWL58OSI7RRIjlaiorAA5QS6lFKfvh6ylpaUilz2HFKzs7GydY/z9fUELAG3btsFU9vr5ZLphwwakpaXrlGtnAnLIaddQSiGQTensWbOQkZnBjFkEqYq9e/eB1P4WzZuhaZOmOm0HBQXp/iGE1dNkMun/EKJM23btcJKWGDJkMAICApwCCJC6fPEF9uzZg8WLF2vQNWXs5zzKQQuGoFJKiyCuyM3NxZOnT1kx29CE1fqPSCT7heXLlqEP+wUBbHLyBSQnJesGdhaF/S0rqaeXJ8SldrboXdnSiwUNQzkFAC+lFCScBg8ezI5XQvEFFsxfoLXhZ8ApA0Fk1RXzNhvOLEaOAHDsmDHIZic0YsQIHS2ff94eQcFBupWXTjkiMpLWCEdgYCBGjx6Npd9/Dy8fb4BEGQUOiGnejWbNmtG3y7Bv337dA2Y/yIZcIpyEWVhYa4SHh/M/4Ej8YfJknVYPHTqkmX056EuthOwXa8kQcEezq4roGI54RpnMpbckUjVfIysrC5cuX8blOuPa1X/gxIm/YRX/nlmqLToSOnfqjEhmOx9KHhUVxVAtx48/rsGFCxfYxOajVcuW+OX6L046l97TE7qSW7p27YZidkRXWV9kTYaA8d8AAAD//0ETEQ4AAAAGSURBVAMAkxZvMbKHjbQAAAAASUVORK5CYII=',
|
|
96
|
+
};
|
|
97
|
+
/** Anzeigename je Harness-Slug (Slugs bleiben in Daten, Schlüsseln und IPC). */
|
|
98
|
+
var HARNESS_NAME = { 'claude-code': 'Claude Code', hermes: 'Hermes', agents: 'Agents' };
|
|
99
|
+
function harnessName(h) { return HARNESS_NAME[h] || h; }
|
|
100
|
+
function harnessZeichen(h) {
|
|
101
|
+
if (HARNESS_BILD[h]) {
|
|
102
|
+
var img = document.createElement('img');
|
|
103
|
+
img.src = HARNESS_BILD[h];
|
|
104
|
+
img.className = 'hsymbol';
|
|
105
|
+
img.alt = '';
|
|
106
|
+
img.setAttribute('aria-hidden', 'true');
|
|
107
|
+
return img;
|
|
108
|
+
}
|
|
109
|
+
var z = HARNESS_ZEICHEN[h];
|
|
110
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
111
|
+
svg.setAttribute('viewBox', z ? z.viewBox : '0 0 16 16');
|
|
112
|
+
svg.setAttribute('fill', 'currentColor');
|
|
113
|
+
svg.setAttribute('class', 'hsymbol');
|
|
114
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
115
|
+
svg.innerHTML = z ? z.inhalt : '';
|
|
116
|
+
return svg;
|
|
117
|
+
}
|
|
118
|
+
function beschriftet(el, h) {
|
|
119
|
+
el.insertBefore(harnessZeichen(h), el.firstChild);
|
|
120
|
+
return el;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Kleine Harness-Zeichen einer Zeile (E53), ein Zeichen je genannten Harness, mit
|
|
124
|
+
Sammel-aria-label statt einzelner Beschriftung je Icon. */
|
|
125
|
+
function harnesseZeichenSammel(harnesse) {
|
|
126
|
+
var wrap = e('span', 'zeilenzeichen');
|
|
127
|
+
wrap.setAttribute('role', 'img');
|
|
128
|
+
wrap.setAttribute('aria-label', harnesse.map(harnessName).join(', '));
|
|
129
|
+
harnesse.forEach(function (h) { wrap.appendChild(harnessZeichen(h)); });
|
|
130
|
+
return wrap;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Ordner-Symbol vom „Verzeichnis hinzufügen"-Knopf, hier für den karten-weiten
|
|
134
|
+
„Skills hinzufügen"-Knopf im Chip „Alle" (E53 Pkt. 4). */
|
|
135
|
+
function ordnerZeichen() {
|
|
136
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
137
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
138
|
+
svg.setAttribute('fill', 'none');
|
|
139
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
140
|
+
svg.setAttribute('stroke-width', '1.5');
|
|
141
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
142
|
+
svg.innerHTML = '<path d="M1.2 13.2V4.1h3.9l1.3 1.8h5.2v7.3z"/><path d="M13.2 1.9v4.6M10.9 4.2h4.6"/>';
|
|
143
|
+
return svg;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ---------------- Ort- und Grund-Texte ---------------- */
|
|
147
|
+
|
|
148
|
+
function ortName(ort) {
|
|
149
|
+
if (ort.art === 'home') return 'Home';
|
|
150
|
+
if (ort.art === 'profil') return 'Profil „' + ort.name + '“';
|
|
151
|
+
return 'Arbeitsverzeichnis ' + pfadKurz(ort.pfad);
|
|
152
|
+
}
|
|
153
|
+
function zielTitel(z) { return harnessName(z.ziel.harness) + ' · ' + ortName(z.ziel.ort); }
|
|
154
|
+
function zielKey(ziel) {
|
|
155
|
+
var o = ziel.ort;
|
|
156
|
+
var ort = o.art === 'home' ? 'home' : o.art === 'profil' ? 'profil:' + o.name : 'dir:' + o.pfad;
|
|
157
|
+
return ziel.harness + '/' + ort;
|
|
158
|
+
}
|
|
159
|
+
function quelleKurz(quelle) { return quelle.art === 'git' ? quelle.url : quelle.pfad; }
|
|
160
|
+
/**
|
|
161
|
+
* Adresse einer Quelle als Text, egal woher sie kommt: aus dem Ist-Zustand trägt sie `url` bzw.
|
|
162
|
+
* `pfad`, aus `hmgr:quellen` (Einstellungen) nur das schon fertige Feld `text`. Beides ist
|
|
163
|
+
* dasselbe (`quelleAlsText`), aber nur so findet auch die Einstellungen-Liste ihr Icon.
|
|
164
|
+
*/
|
|
165
|
+
function quelleAdresse(quelle) { return quelle.text != null ? quelle.text : quelleKurz(quelle); }
|
|
166
|
+
/** Letztes nicht leeres Segment eines Pfads oder einer URL, ohne abschließende Schrägstriche. */
|
|
167
|
+
function letztesSegment(text) {
|
|
168
|
+
var bereinigt = text.replace(/\/+$/, '');
|
|
169
|
+
var segmente = bereinigt.split('/').filter(function (s) { return s.length > 0; });
|
|
170
|
+
return segmente.length > 0 ? segmente[segmente.length - 1] : bereinigt;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Anzeigename einer Quelle (E59): der gespeicherte Name (Feld `quelle.name`, kommt über IPC mit,
|
|
174
|
+
* wo der Main-Prozess ihn nicht schon direkt liefert), sonst abgeleitet wie im Kern
|
|
175
|
+
* (`quelleName`/`quelleNameAbleiten` in modell.ts, dort führend): letztes Pfadsegment der URL
|
|
176
|
+
* ohne `.git`-Suffix, bei einer lokalen Quelle der Ordnername. Kleine Kopie, weil der
|
|
177
|
+
* Renderer-Prozess den Kern nicht importieren kann (kein Node-Zugriff, contextIsolation).
|
|
178
|
+
*/
|
|
179
|
+
function quelleAnzeigename(quelle) {
|
|
180
|
+
if (quelle.name) return quelle.name;
|
|
181
|
+
if (quelle.art === 'lokal') return letztesSegment(quelle.pfad);
|
|
182
|
+
var ohneSchraeg = quelle.url.replace(/\/+$/, '');
|
|
183
|
+
var ohneGit = ohneSchraeg.endsWith('.git') ? ohneSchraeg.slice(0, -4) : ohneSchraeg;
|
|
184
|
+
return letztesSegment(ohneGit);
|
|
185
|
+
}
|
|
186
|
+
/** Ein `<span>` mit dem Anzeigenamen der Quelle, die volle Adresse als title-Tooltip (E59). */
|
|
187
|
+
function quelleSpan(klasse, quelle, rest, praefix) {
|
|
188
|
+
var el = e('span', klasse, (praefix || '') + quelleAnzeigename(quelle) + (rest || ''));
|
|
189
|
+
el.title = quelleKurz(quelle);
|
|
190
|
+
return el;
|
|
191
|
+
}
|
|
192
|
+
/* ---------------- Quellen: Icon und Sprung nach draussen (E60) ---------------- */
|
|
193
|
+
|
|
194
|
+
/** Das kleine Pfeilsymbol hinter einem Quellennamen: „das geht nach draussen". */
|
|
195
|
+
function externSvg() {
|
|
196
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
197
|
+
svg.setAttribute('viewBox', '0 0 12 12');
|
|
198
|
+
svg.setAttribute('class', 'extern');
|
|
199
|
+
svg.setAttribute('fill', 'none');
|
|
200
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
201
|
+
svg.setAttribute('stroke-width', '1.6');
|
|
202
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
203
|
+
svg.innerHTML = '<path d="M3.6 8.4 8.4 3.6"/><path d="M4.9 3.6h3.5v3.5"/>';
|
|
204
|
+
return svg;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Generisches Ordner-/Repo-Symbol (E60 F35): lokale Quellen und jeder Icon-Fehlschlag. */
|
|
208
|
+
function repoSvgGenerisch() {
|
|
209
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
210
|
+
svg.setAttribute('viewBox', '0 0 20 20');
|
|
211
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
212
|
+
svg.innerHTML = '<rect width="20" height="20" rx="5" fill="#FFFFFF"/>'
|
|
213
|
+
+ '<path d="M4.2 14.3V5.4h4.1l1.3 1.8h6.2v7.1z" fill="none" stroke="#4E5B66" stroke-width="1.5" stroke-linejoin="round"/>';
|
|
214
|
+
return svg;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Icon-Cache im Renderer, Schlüssel wie quelleKurz: dataURL nach Erfolg, null nach Fehlschlag
|
|
218
|
+
oder bei einer lokalen Quelle, sonst ein noch laufendes Promise. Verhindert doppelte
|
|
219
|
+
IPC-Aufrufe für dieselbe Quelle, wenn sie in mehreren Zeilen auftaucht. */
|
|
220
|
+
var ICON_CACHE = {};
|
|
221
|
+
function ladeIcon(quelleText) {
|
|
222
|
+
var vorhanden = ICON_CACHE[quelleText];
|
|
223
|
+
if (vorhanden !== undefined) return vorhanden instanceof Promise ? vorhanden : Promise.resolve(vorhanden);
|
|
224
|
+
var p = window.hmgr.icon(quelleText).then(function (ergebnis) {
|
|
225
|
+
var url = ergebnis && ergebnis.ok ? ergebnis.daten : null;
|
|
226
|
+
ICON_CACHE[quelleText] = url;
|
|
227
|
+
return url;
|
|
228
|
+
}).catch(function () {
|
|
229
|
+
ICON_CACHE[quelleText] = null;
|
|
230
|
+
return null;
|
|
231
|
+
});
|
|
232
|
+
ICON_CACHE[quelleText] = p;
|
|
233
|
+
return p;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Das 20px-Icon einer Quelle (E60): erst generisch, tauscht asynchron, sobald geladen. Blockiert
|
|
237
|
+
nie den Aufbau der Liste. Lokale Quellen fragen gar nicht erst ab. */
|
|
238
|
+
function repoIcon(quelle) {
|
|
239
|
+
var span = e('span', 'repo');
|
|
240
|
+
span.appendChild(repoSvgGenerisch());
|
|
241
|
+
if (quelle.art === 'lokal') return span;
|
|
242
|
+
ladeIcon(quelleAdresse(quelle)).then(function (url) {
|
|
243
|
+
if (!url || !span.isConnected) return;
|
|
244
|
+
span.textContent = '';
|
|
245
|
+
var img = document.createElement('img');
|
|
246
|
+
img.src = url;
|
|
247
|
+
img.alt = '';
|
|
248
|
+
img.width = 20;
|
|
249
|
+
img.height = 20;
|
|
250
|
+
img.setAttribute('aria-hidden', 'true');
|
|
251
|
+
span.appendChild(img);
|
|
252
|
+
});
|
|
253
|
+
return span;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Eine Quelle, angeschrieben mit ihrem Namen und anklickbar (E60): keine Unterstreichung, das
|
|
258
|
+
* kleine Pfeilsymbol dahinter trägt das Signalorange. Klick öffnet über den Main-Prozess die
|
|
259
|
+
* Repo-Seite im Browser oder den Ordner im Finder (`window.hmgr.quelleOeffnen`), still im
|
|
260
|
+
* Fehlerfall statt in der Meldungsleiste, ein Öffnen-Fehlschlag soll die Ansicht nicht stören.
|
|
261
|
+
*/
|
|
262
|
+
function quelleLinkElement(quelle) {
|
|
263
|
+
var lokal = quelle.art === 'lokal';
|
|
264
|
+
var el = document.createElement(lokal ? 'button' : 'a');
|
|
265
|
+
if (lokal) el.type = 'button'; else el.href = '#';
|
|
266
|
+
el.className = 'qlink';
|
|
267
|
+
el.title = quelleAdresse(quelle);
|
|
268
|
+
el.setAttribute('aria-label', quelleAnzeigename(quelle) + ' ' + (lokal ? 'im Finder öffnen' : 'im Browser öffnen'));
|
|
269
|
+
el.appendChild(document.createTextNode(quelleAnzeigename(quelle)));
|
|
270
|
+
el.appendChild(externSvg());
|
|
271
|
+
el.addEventListener('click', function (ev) {
|
|
272
|
+
ev.preventDefault();
|
|
273
|
+
window.hmgr.quelleOeffnen(quelleAdresse(quelle)).catch(function () {});
|
|
274
|
+
});
|
|
275
|
+
return el;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* ---------------- Zustand (im Renderer gehalten) ---------------- */
|
|
279
|
+
|
|
280
|
+
var ZIELE = [];
|
|
281
|
+
var SKILLS = [];
|
|
282
|
+
var MARKETPLACES = [];
|
|
283
|
+
var QUELLEN = [];
|
|
284
|
+
var HARNESSE_ALLE = [];
|
|
285
|
+
var AKTIVE_HARNESSE = [];
|
|
286
|
+
var HARNESSE_NICHT_INSTALLIERT = [];
|
|
287
|
+
var quellenFilter = 'alle';
|
|
288
|
+
var zielFilter = 'alle';
|
|
289
|
+
// Standard-Ref je Quelle (E60), aus dem Katalog abgeleitet (SKILLS trägt ihn schon je Skill): ein
|
|
290
|
+
// Ref an einem Link erscheint nur, wenn er davon abweicht (`@v2.1`). Schlüssel wie quelleKurz.
|
|
291
|
+
var STANDARD_REF = {};
|
|
292
|
+
|
|
293
|
+
function harnessAktiv(h) { return AKTIVE_HARNESSE.indexOf(h) > -1; }
|
|
294
|
+
|
|
295
|
+
// Ignorierte fremde Einträge zählen hier nicht mit (E43): Ignorieren blendet die Warnung aus,
|
|
296
|
+
// sonst bliebe das Ziel im Filter „braucht Aufmerksamkeit" stehen.
|
|
297
|
+
function zielBraucht(z) {
|
|
298
|
+
var ignoriert = z.ignoriert || [];
|
|
299
|
+
return z.eintraege.some(function (ei) {
|
|
300
|
+
if (ei.art === 'fremd') return ignoriert.indexOf(ei.name) === -1;
|
|
301
|
+
return ei.art === 'link-ohne-quelle';
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// E45: ein Ziel eines inaktiven Harness verschwindet, sobald dort nichts liegt, weder eigene noch
|
|
306
|
+
// fremde Einträge noch eine harness-eigene Registrierung. Ein aktiver Harness zeigt sein Ziel immer.
|
|
307
|
+
function zielSichtbar(z) {
|
|
308
|
+
if (harnessAktiv(z.ziel.harness)) return true;
|
|
309
|
+
return z.eintraege.length > 0 || z.istRegistriert;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Von Hand aufgeklappte Ziele bleiben über Neuaufbauten der Liste offen (E45), Schlüssel wie
|
|
313
|
+
// zielKey. Verzeichnis-Karten (E49) nutzen stattdessen verzKey (reiner Pfad, kollidiert nicht mit
|
|
314
|
+
// zielKey, der immer mit "harness/" beginnt). Nicht persistiert, nur für die Dauer der Sitzung.
|
|
315
|
+
var ZIEL_OFFEN = {};
|
|
316
|
+
function verzKey(pfad) { return 'dir:' + pfad; }
|
|
317
|
+
|
|
318
|
+
// Aktiver Harness-Chip je Verzeichnis-Karte (E53), gleicher Schlüssel wie ZIEL_OFFEN
|
|
319
|
+
// (verzKey(pfad)); fehlt der Eintrag oder zeigt er auf einen nicht mehr beteiligten Harness,
|
|
320
|
+
// gilt „alle". Nicht persistiert, nur für die Dauer der Sitzung.
|
|
321
|
+
var VERZ_CHIP = {};
|
|
322
|
+
|
|
323
|
+
// Aufgeklappte Skill-Details im Katalog (F10), Schlüssel Quelle + Pfad: der Index in SKILLS
|
|
324
|
+
// verschiebt sich, wenn ladeAlles Quellen oder Skills anders sortiert oder eine wegfällt.
|
|
325
|
+
// Nicht persistiert, nur für die Dauer der Sitzung.
|
|
326
|
+
var SKILL_OFFEN = {};
|
|
327
|
+
function skillKey(skill) { return skill.quelleSchluessel + '|' + skill.pfad; }
|
|
328
|
+
// Ein Chip-Klick baut die ganze Liste neu und zerstört dabei den geklickten Knopf. Damit die
|
|
329
|
+
// Tastatur-Bedienung nicht am Seitenanfang landet, merkt sich der Klick, welcher Chip nach dem
|
|
330
|
+
// Neuaufbau den Fokus bekommt.
|
|
331
|
+
var CHIP_FOKUS = null;
|
|
332
|
+
/** Schlüssel der Karte, in der ein Ziel steckt: Arbeitsverzeichnisse sind gebündelt (E49). */
|
|
333
|
+
function kartenKey(ziel) {
|
|
334
|
+
return ziel.ort.art === 'arbeitsverzeichnis' ? verzKey(ziel.ort.pfad) : zielKey(ziel);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Laufende Nummer für die ids der Körper: aus dem Schlüssel abgeleitete ids kollidieren, sobald
|
|
338
|
+
// sich zwei Verzeichnisse nur in Zeichen unterscheiden, die eine id nicht tragen kann.
|
|
339
|
+
var zielKoerperNr = 0;
|
|
340
|
+
|
|
341
|
+
/* ---------------- Laden ---------------- */
|
|
342
|
+
|
|
343
|
+
/** Home-Verzeichnis einmal beim Start holen (nur für pfadKurz, siehe oben). */
|
|
344
|
+
async function ladeHome() {
|
|
345
|
+
HOME = await rufe(function () { return window.hmgr.home(); });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function ladeAlles() {
|
|
349
|
+
var version = await rufe(function () { return window.hmgr.version(); });
|
|
350
|
+
$('version').textContent = version;
|
|
351
|
+
|
|
352
|
+
var ergebnisse = await Promise.all([
|
|
353
|
+
rufe(function () { return window.hmgr.istZustand(); }),
|
|
354
|
+
rufe(function () { return window.hmgr.skills(); }),
|
|
355
|
+
rufe(function () { return window.hmgr.marketplaces(); }),
|
|
356
|
+
rufe(function () { return window.hmgr.quellen(); }),
|
|
357
|
+
rufe(function () { return window.hmgr.harnessTypen(); }),
|
|
358
|
+
rufe(function () { return window.hmgr.aktiveHarnesses(); }),
|
|
359
|
+
rufe(function () { return window.hmgr.nichtVorhandeneHarnesses(); }),
|
|
360
|
+
]);
|
|
361
|
+
ZIELE = ergebnisse[0];
|
|
362
|
+
SKILLS = ergebnisse[1];
|
|
363
|
+
// Eine alphabetische Liste über alle Quellen; wer nach Quelle sucht, hat die Filter-Chips.
|
|
364
|
+
SKILLS.sort(function (a, b) { return a.name.localeCompare(b.name, 'de') || a.quelleText.localeCompare(b.quelleText, 'de'); });
|
|
365
|
+
MARKETPLACES = ergebnisse[2];
|
|
366
|
+
QUELLEN = ergebnisse[3];
|
|
367
|
+
HARNESSE_ALLE = ergebnisse[4];
|
|
368
|
+
AKTIVE_HARNESSE = ergebnisse[5];
|
|
369
|
+
HARNESSE_NICHT_INSTALLIERT = ergebnisse[6];
|
|
370
|
+
STANDARD_REF = {};
|
|
371
|
+
SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
|
|
372
|
+
|
|
373
|
+
baueZiele();
|
|
374
|
+
baueSkills();
|
|
375
|
+
baueQuellenfilter();
|
|
376
|
+
baueMarketplaces();
|
|
377
|
+
baueEinstellungen();
|
|
378
|
+
aktualisiereKopf();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Karten, wie die Zielliste sie zeigt: je Agent-Ziel eine, je Arbeitsverzeichnis eine gebündelte
|
|
382
|
+
(E49). Grundlage für "N von M Karten gezeigt" und den Zähler am Achtung-Chip. */
|
|
383
|
+
function zielKarten() {
|
|
384
|
+
var sichtbar = ZIELE.filter(zielSichtbar);
|
|
385
|
+
var agents = sichtbar.filter(function (z) { return z.ziel.ort.art !== 'arbeitsverzeichnis'; });
|
|
386
|
+
var gruppen = {};
|
|
387
|
+
sichtbar.forEach(function (z) {
|
|
388
|
+
if (z.ziel.ort.art !== 'arbeitsverzeichnis') return;
|
|
389
|
+
var pfad = z.ziel.ort.pfad;
|
|
390
|
+
(gruppen[pfad] = gruppen[pfad] || []).push(z);
|
|
391
|
+
});
|
|
392
|
+
var verzeichnisse = Object.keys(gruppen).map(function (pfad) { return { pfad: pfad, teile: gruppen[pfad] }; });
|
|
393
|
+
return { agents: agents, verzeichnisse: verzeichnisse };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Kopfzahl und Nav-Zahl tragen beide das Wort "Ziele" und stehen gleichzeitig im Bild, also zählen
|
|
397
|
+
// beide im Kern-Sinn: jedes Harness+Ort-Paar einzeln, unabhängig von der Bündelung. Karten zählt
|
|
398
|
+
// nur, was sie auch so nennt: "N von M Karten gezeigt" und der Achtung-Chip (E49).
|
|
399
|
+
function aktualisiereKopf() {
|
|
400
|
+
var sichtbar = ZIELE.filter(zielSichtbar);
|
|
401
|
+
var linkZahl = 0, fremdZahl = 0;
|
|
402
|
+
sichtbar.forEach(function (z) {
|
|
403
|
+
var ignoriert = z.ignoriert || [];
|
|
404
|
+
z.eintraege.forEach(function (ei) {
|
|
405
|
+
if (ei.art === 'fremd') { if (ignoriert.indexOf(ei.name) === -1) fremdZahl++; }
|
|
406
|
+
else linkZahl++;
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
$('kopfzahlen').textContent = zahlwort(sichtbar.length, 'Ziel', 'Ziele') + ' · ' +
|
|
410
|
+
zahlwort(linkZahl, 'Link', 'Links') + ' · ' +
|
|
411
|
+
zahlwort(fremdZahl, 'nicht von hmgr verwalteter Eintrag', 'nicht von hmgr verwaltete Einträge');
|
|
412
|
+
$('navzahlziele').textContent = sichtbar.length;
|
|
413
|
+
$('navzahlskills').textContent = SKILLS.length;
|
|
414
|
+
var pluginZahl = MARKETPLACES.reduce(function (n, m) { return n + m.plugins.length; }, 0);
|
|
415
|
+
$('navzahlplugins').textContent = pluginZahl;
|
|
416
|
+
|
|
417
|
+
var karten = zielKarten();
|
|
418
|
+
// Betroffene Karten für den Chip-Zähler: eine Verzeichnis-Karte zählt, wenn mindestens ein
|
|
419
|
+
// Abschnitt (Harness) darin zielBraucht ist.
|
|
420
|
+
var betroffen = karten.agents.filter(zielBraucht).length
|
|
421
|
+
+ karten.verzeichnisse.filter(function (g) { return g.teile.some(zielBraucht); }).length;
|
|
422
|
+
var achtungChip = document.querySelector('#ansicht-ziele .chip[data-zielfilter="achtung"]');
|
|
423
|
+
if (achtungChip) achtungChip.textContent = 'Benötigt Aufmerksamkeit (' + betroffen + ')';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* ---------------- Navigation ---------------- */
|
|
427
|
+
|
|
428
|
+
var ANSICHTEN = ['ziele', 'katalog', 'update', 'plugins'];
|
|
429
|
+
function zeigeAnsicht(name) {
|
|
430
|
+
document.querySelectorAll('.plakette').forEach(function (a) {
|
|
431
|
+
if (a.dataset.ansicht === name) a.setAttribute('aria-current', 'page');
|
|
432
|
+
else a.removeAttribute('aria-current');
|
|
433
|
+
});
|
|
434
|
+
ANSICHTEN.forEach(function (x) { $('ansicht-' + x).hidden = x !== name; });
|
|
435
|
+
// Der Bericht ist jetzt sichtbar, die Ankündigung „mit Neuem" hat ihren Dienst getan.
|
|
436
|
+
if (name === 'update' && standZeit !== null) standRuhig();
|
|
437
|
+
window.scrollTo(0, 0);
|
|
438
|
+
}
|
|
439
|
+
document.querySelectorAll('[data-ansicht]').forEach(function (k) {
|
|
440
|
+
k.addEventListener('click', function () { zeigeAnsicht(k.dataset.ansicht); });
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
/* ================= ZIELE ================= */
|
|
444
|
+
|
|
445
|
+
/** Hinweiszeile, wenn ein inaktiver Harness noch Links oder eine eigene Registrierung an diesem
|
|
446
|
+
Ziel hat (E38 R7): Säubern-Weg direkt in der Karte. Von linkTeil und alleTeil geteilt. */
|
|
447
|
+
function ruheZeile(z, hatEigene) {
|
|
448
|
+
var rz = e('div', 'ruhezeile');
|
|
449
|
+
var rsvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
450
|
+
rsvg.setAttribute('viewBox', '0 0 16 16');
|
|
451
|
+
rsvg.setAttribute('stroke-width', '1.6');
|
|
452
|
+
rsvg.setAttribute('aria-hidden', 'true');
|
|
453
|
+
rsvg.innerHTML = '<circle cx="8" cy="8" r="6.5"/><path d="M8 4.5v4M8 10.5v.5"/>';
|
|
454
|
+
rz.appendChild(rsvg);
|
|
455
|
+
rz.appendChild(e('p', null, harnessName(z.ziel.harness) + ' ist inaktiv. '
|
|
456
|
+
+ (hatEigene ? 'hmgr pflegt diese Links nicht mehr.' : 'Ein harness-eigener Eintrag verweist noch auf dieses Ziel.')));
|
|
457
|
+
var rk = e('button', 'knopf leise klein', 'Säubern');
|
|
458
|
+
rk.type = 'button';
|
|
459
|
+
rk.setAttribute('aria-label', harnessName(z.ziel.harness) + ' säubern');
|
|
460
|
+
rk.addEventListener('click', function () { frageSaeubern(z.ziel.harness); });
|
|
461
|
+
rz.appendChild(rk);
|
|
462
|
+
return rz;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Eine Zeile im Fremdkasten für einen fremden Eintrag x an einem Ziel z. Mit mitZeichen=true
|
|
466
|
+
steht davor das kleine Harness-Zeichen seines Fundorts (E53 Pkt. 5, „Alle"-Ansicht). Gibt
|
|
467
|
+
neben dem li auch zurück, ob eine Quelle erkannt wurde (für den Erklärtext). */
|
|
468
|
+
function fremdZeile(x, z, mitZeichen) {
|
|
469
|
+
var prognosen = z.uebernahme || {};
|
|
470
|
+
var prognose = prognosen[x.name];
|
|
471
|
+
var erkannt = prognose && prognose.art === 'erkannt';
|
|
472
|
+
var li = e('li');
|
|
473
|
+
var kopf = e('span', 'fremdkopf');
|
|
474
|
+
// Nur die „Alle"-Ansicht braucht die Zeile als Flex-Kasten neben dem Harness-Zeichen. Unter
|
|
475
|
+
// einem Harness-Chip bleibt es der schlichte Span von vorher: ein Flex-Kasten bricht lange
|
|
476
|
+
// Namen anders um als der Fließtext.
|
|
477
|
+
var namezeile = e('span', mitZeichen ? 'fremdname' : null);
|
|
478
|
+
if (mitZeichen) namezeile.appendChild(harnesseZeichenSammel([z.ziel.harness]));
|
|
479
|
+
namezeile.appendChild(document.createTextNode(x.name));
|
|
480
|
+
kopf.appendChild(namezeile);
|
|
481
|
+
if (erkannt) kopf.appendChild(quelleSpan('mono', prognose.quelle, ' @ ' + prognose.ref));
|
|
482
|
+
li.appendChild(kopf);
|
|
483
|
+
var knoepfe = e('span', 'fremdknoepfe');
|
|
484
|
+
if (erkannt) {
|
|
485
|
+
var ue = e('button', 'knopf leise klein', 'Übernehmen');
|
|
486
|
+
ue.type = 'button';
|
|
487
|
+
ue.setAttribute('aria-label', x.name + ' übernehmen');
|
|
488
|
+
ue.addEventListener('click', function () { oeffneUebernahme(z.ziel, x.name); });
|
|
489
|
+
knoepfe.appendChild(ue);
|
|
490
|
+
}
|
|
491
|
+
var ig = e('button', 'knopf leise klein', 'Ignorieren');
|
|
492
|
+
ig.type = 'button';
|
|
493
|
+
ig.setAttribute('aria-label', x.name + ' ignorieren');
|
|
494
|
+
ig.addEventListener('click', async function () {
|
|
495
|
+
await rufe(function () {
|
|
496
|
+
return window.hmgr.eintragIgnorieren({ harness: z.ziel.harness, ort: z.ziel.ort, name: x.name });
|
|
497
|
+
});
|
|
498
|
+
await ladeAlles();
|
|
499
|
+
});
|
|
500
|
+
knoepfe.appendChild(ig);
|
|
501
|
+
var lo = e('button', 'knopf leise klein', 'Löschen');
|
|
502
|
+
lo.type = 'button';
|
|
503
|
+
lo.setAttribute('aria-label', x.name + ' löschen');
|
|
504
|
+
lo.addEventListener('click', function () { frageLoeschen(x.name, z.ziel, z.verzeichnis); });
|
|
505
|
+
knoepfe.appendChild(lo);
|
|
506
|
+
li.appendChild(knoepfe);
|
|
507
|
+
// Ohne erkannte Quelle: Ursache und Handreichungen direkt im Kasten statt eines Vorschau-Dialogs (E43, E48).
|
|
508
|
+
if (!erkannt) {
|
|
509
|
+
var grundInfo = UEBERNAHME_GRUENDE[prognose ? prognose.grund : 'kein-link'];
|
|
510
|
+
if (grundInfo) {
|
|
511
|
+
var ursache = e('div', 'fremdursache');
|
|
512
|
+
ursache.appendChild(e('p', 'mono', grundInfo.ursache));
|
|
513
|
+
if (grundInfo.tipps && grundInfo.tipps.length > 0) {
|
|
514
|
+
var tippbox = e('div', 'tipps');
|
|
515
|
+
tippbox.appendChild(e('p', 'tippkopf', 'Außerhalb von hmgr:'));
|
|
516
|
+
var tul = e('ul', 'tippliste');
|
|
517
|
+
grundInfo.tipps.forEach(function (t) { tul.appendChild(e('li', null, '→ ' + t)); });
|
|
518
|
+
tippbox.appendChild(tul);
|
|
519
|
+
ursache.appendChild(tippbox);
|
|
520
|
+
}
|
|
521
|
+
li.appendChild(ursache);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return { li: li, erkannt: erkannt };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Fremdkasten über eine Liste von {x, z}-Paaren (ein Ziel bei linkTeil, mehrere bei alleTeil). */
|
|
528
|
+
function fremdKasten(paare, mitZeichen) {
|
|
529
|
+
var kasten = e('div', 'fremdkasten');
|
|
530
|
+
var text = e('div', 'fremdtext');
|
|
531
|
+
var p = e('p');
|
|
532
|
+
p.appendChild(e('strong', null, paare.length + (paare.length === 1
|
|
533
|
+
? ' nicht von hmgr verwalteter Eintrag'
|
|
534
|
+
: ' nicht von hmgr verwaltete Einträge')));
|
|
535
|
+
text.appendChild(p);
|
|
536
|
+
var namen = e('ul', 'fremdnamen');
|
|
537
|
+
var einErkannt = false;
|
|
538
|
+
paare.forEach(function (paar) {
|
|
539
|
+
var r = fremdZeile(paar.x, paar.z, mitZeichen);
|
|
540
|
+
if (r.erkannt) einErkannt = true;
|
|
541
|
+
namen.appendChild(r.li);
|
|
542
|
+
});
|
|
543
|
+
text.appendChild(namen);
|
|
544
|
+
// Der Erklärtext nennt Übernehmen nur, wo der Knopf auch steht (E48).
|
|
545
|
+
text.appendChild(e('p', 'fremderklaerung',
|
|
546
|
+
(einErkannt ? 'Übernehmen zeigt zuerst eine Vorschau und macht daraus einen Link auf die Quelle. ' : '')
|
|
547
|
+
+ (paare.length === 1
|
|
548
|
+
? 'Ignorieren blendet ihn aus, er bleibt liegen. Löschen entfernt ihn endgültig von der Platte.'
|
|
549
|
+
: 'Ignorieren blendet einen Eintrag aus, er bleibt liegen. Löschen entfernt ihn endgültig von der Platte.')));
|
|
550
|
+
kasten.appendChild(text);
|
|
551
|
+
return kasten;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Zeile ignorierter fremder Einträge über eine Liste von {x, z}-Paaren, analog fremdKasten. */
|
|
555
|
+
function ignoriertZeile(paare, mitZeichen) {
|
|
556
|
+
var izeile = e('div', 'ignoriertzeile');
|
|
557
|
+
izeile.appendChild(e('span', 'ignoriertzahl', paare.length + ' ignoriert'));
|
|
558
|
+
paare.forEach(function (paar) {
|
|
559
|
+
var x = paar.x, z = paar.z;
|
|
560
|
+
var spanne = e('span', 'ignoriertname');
|
|
561
|
+
if (mitZeichen) spanne.appendChild(harnesseZeichenSammel([z.ziel.harness]));
|
|
562
|
+
spanne.appendChild(e('span', null, x.name));
|
|
563
|
+
var wb = e('button', 'knopf leise klein', 'Wieder beachten');
|
|
564
|
+
wb.type = 'button';
|
|
565
|
+
wb.setAttribute('aria-label', x.name + ' wieder beachten');
|
|
566
|
+
wb.addEventListener('click', async function () {
|
|
567
|
+
await rufe(function () {
|
|
568
|
+
return window.hmgr.eintragBeachten({ harness: z.ziel.harness, ort: z.ziel.ort, name: x.name });
|
|
569
|
+
});
|
|
570
|
+
await ladeAlles();
|
|
571
|
+
});
|
|
572
|
+
spanne.appendChild(wb);
|
|
573
|
+
izeile.appendChild(spanne);
|
|
574
|
+
});
|
|
575
|
+
return izeile;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** Sichtbare Konfiguration eines Ziels mit der Fähigkeit „verwalteter Skill-Ordner": bei Hermes
|
|
579
|
+
der external_dirs-Ordner, zum Festlegen oder Entfernen (E56). Ob ein Ziel das kann, sagt der
|
|
580
|
+
Kern mit kannSkillOrdner; der Harness-Name ist hier kein Kriterium (E16). */
|
|
581
|
+
function konfigZeile(z) {
|
|
582
|
+
if (!z.kannSkillOrdner) return null;
|
|
583
|
+
var kz = e('div', 'konfigzeile');
|
|
584
|
+
if (z.verwalteterSkillOrdner) {
|
|
585
|
+
kz.appendChild(e('b', null, 'Liest Skills aus'));
|
|
586
|
+
kz.appendChild(e('span', 'mono', pfadKurz(z.verwalteterSkillOrdner)));
|
|
587
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen …');
|
|
588
|
+
weg.type = 'button';
|
|
589
|
+
weg.setAttribute('aria-label', 'Skill-Ordner für ' + zielTitel(z) + ' entfernen');
|
|
590
|
+
weg.addEventListener('click', function () { frageSkillOrdnerEntfernen(z); });
|
|
591
|
+
kz.appendChild(weg);
|
|
592
|
+
} else {
|
|
593
|
+
kz.appendChild(e('span', 'konfigquelle', 'Kein Skill-Ordner verwaltet.'));
|
|
594
|
+
var fest = e('button', 'knopf leise klein', 'Skill-Ordner festlegen …');
|
|
595
|
+
fest.type = 'button';
|
|
596
|
+
fest.setAttribute('aria-label', 'Skill-Ordner für ' + zielTitel(z) + ' festlegen');
|
|
597
|
+
fest.addEventListener('click', function () { oeffneSkillOrdnerFestlegen(z); });
|
|
598
|
+
kz.appendChild(fest);
|
|
599
|
+
}
|
|
600
|
+
return kz;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function linkTeil(z) {
|
|
604
|
+
var teil = document.createDocumentFragment();
|
|
605
|
+
var eigene = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; });
|
|
606
|
+
var fremde = z.eintraege.filter(function (ei) { return ei.art === 'fremd'; });
|
|
607
|
+
|
|
608
|
+
// Harness abgeschaltet, Konfiguration liegt noch (E38, GRILL.md Entwurfskritik 4): der Weg,
|
|
609
|
+
// sie loszuwerden, steht hier oben in der Karte. Zählt auch ohne Links, wenn die harness-eigene
|
|
610
|
+
// Registrierung noch da ist (E38 R7), etwa Hermes' external_dirs-Eintrag ohne Links darunter.
|
|
611
|
+
if (!harnessAktiv(z.ziel.harness) && (eigene.length > 0 || z.istRegistriert)) {
|
|
612
|
+
teil.appendChild(ruheZeile(z, eigene.length > 0));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
var kz = konfigZeile(z);
|
|
616
|
+
if (kz) teil.appendChild(kz);
|
|
617
|
+
|
|
618
|
+
if (fremde.length > 0) {
|
|
619
|
+
var ignoriertNamen = z.ignoriert || [];
|
|
620
|
+
var beachtete = fremde.filter(function (x) { return ignoriertNamen.indexOf(x.name) === -1; });
|
|
621
|
+
var ignorierte = fremde.filter(function (x) { return ignoriertNamen.indexOf(x.name) !== -1; });
|
|
622
|
+
|
|
623
|
+
if (beachtete.length > 0) {
|
|
624
|
+
teil.appendChild(fremdKasten(beachtete.map(function (x) { return { x: x, z: z }; }), false));
|
|
625
|
+
}
|
|
626
|
+
if (ignorierte.length > 0) {
|
|
627
|
+
teil.appendChild(ignoriertZeile(ignorierte.map(function (x) { return { x: x, z: z }; }), false));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (eigene.length === 0) {
|
|
632
|
+
var leer = e('div', 'leerzeile');
|
|
633
|
+
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
634
|
+
leer.appendChild(skillsKnopf(z));
|
|
635
|
+
teil.appendChild(leer);
|
|
636
|
+
} else {
|
|
637
|
+
// Fünf Spalten (E60): Icon, Skill, Beschreibung (die breiteste, sie beantwortet die Frage,
|
|
638
|
+
// wegen der die Tabelle gelesen wird), Quelle, Aktion.
|
|
639
|
+
var kopf = e('div', 'linkkopf');
|
|
640
|
+
kopf.appendChild(e('span'));
|
|
641
|
+
kopf.appendChild(e('span', 'etikett', 'Skill'));
|
|
642
|
+
kopf.appendChild(e('span', 'etikett', 'Beschreibung'));
|
|
643
|
+
kopf.appendChild(e('span', 'etikett', 'Quelle'));
|
|
644
|
+
kopf.appendChild(e('span'));
|
|
645
|
+
teil.appendChild(kopf);
|
|
646
|
+
|
|
647
|
+
var ul = e('ul', 'linkliste');
|
|
648
|
+
// Defekte Links (ohne Quelle) zuerst, sie brauchen Aufmerksamkeit (E45), sonst alphabetisch.
|
|
649
|
+
eigene
|
|
650
|
+
.slice()
|
|
651
|
+
.sort(function (a, b) {
|
|
652
|
+
var da = a.art === 'link-ohne-quelle', db = b.art === 'link-ohne-quelle';
|
|
653
|
+
if (da !== db) return da ? -1 : 1;
|
|
654
|
+
var na = a.art === 'link' ? a.link.name : a.name;
|
|
655
|
+
var nb = b.art === 'link' ? b.link.name : b.name;
|
|
656
|
+
return na.localeCompare(nb, 'de');
|
|
657
|
+
})
|
|
658
|
+
.forEach(function (ei) {
|
|
659
|
+
var name = ei.art === 'link' ? ei.link.name : ei.name;
|
|
660
|
+
var li = e('li');
|
|
661
|
+
|
|
662
|
+
if (ei.art === 'link') li.appendChild(repoIcon(ei.link.checkout.quelle));
|
|
663
|
+
else li.appendChild(repoIcon({ art: 'lokal', pfad: ei.checkoutPfad }));
|
|
664
|
+
|
|
665
|
+
li.appendChild(e('span', 'name', name));
|
|
666
|
+
|
|
667
|
+
// Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
|
|
668
|
+
// deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
|
|
669
|
+
var beschreibung = ei.art === 'link' ? ei.link.skill.beschreibung : undefined;
|
|
670
|
+
if (beschreibung) {
|
|
671
|
+
var wasEl = e('span', 'was', beschreibung);
|
|
672
|
+
if (beschreibung.length > 88) wasEl.title = beschreibung;
|
|
673
|
+
li.appendChild(wasEl);
|
|
674
|
+
} else {
|
|
675
|
+
li.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Quelle: der Name, anklickbar, dahinter nur, was vom Normalfall abweicht — ein Ref, das
|
|
679
|
+
// vom Standard-Ref der Quelle abweicht, und der Grund „über Zeiger" (E57). „Manuell" (der
|
|
680
|
+
// Normalfall) entfällt ersatzlos, ein Plugin-Grund steht stattdessen in der Aktionsspalte.
|
|
681
|
+
var qz = e('span', 'quelle');
|
|
682
|
+
if (ei.art === 'link') {
|
|
683
|
+
var einschraenkung = z.einschraenkungen[name];
|
|
684
|
+
if (einschraenkung) qz.appendChild(document.createTextNode(einschraenkung + ' → '));
|
|
685
|
+
qz.appendChild(quelleLinkElement(ei.link.checkout.quelle));
|
|
686
|
+
var standardRef = STANDARD_REF[quelleKurz(ei.link.checkout.quelle)];
|
|
687
|
+
if (ei.link.checkout.ref && standardRef && ei.link.checkout.ref !== standardRef) {
|
|
688
|
+
qz.appendChild(e('span', 'ref', '@' + ei.link.checkout.ref));
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
qz.appendChild(e('span', 'mono', pfadKurz(ei.checkoutPfad)));
|
|
692
|
+
}
|
|
693
|
+
var grund = z.gruende[name];
|
|
694
|
+
if (grund && grund.art === 'zeiger') qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
695
|
+
li.appendChild(qz);
|
|
696
|
+
|
|
697
|
+
// Aus einem Plugin verlinkt: kein Entfernen-Knopf, das geht nur über das Plugin — der
|
|
698
|
+
// Grund erklärt hier zugleich, warum der Knopf fehlt.
|
|
699
|
+
if (grund && grund.art === 'plugin') {
|
|
700
|
+
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + grund.plugin.split('@')[0] + '“'));
|
|
701
|
+
} else {
|
|
702
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
703
|
+
weg.type = 'button';
|
|
704
|
+
weg.setAttribute('aria-label', 'Link auf ' + name + ' entfernen');
|
|
705
|
+
weg.addEventListener('click', function () { frageEntfernen(name, z.ziel); });
|
|
706
|
+
li.appendChild(weg);
|
|
707
|
+
}
|
|
708
|
+
ul.appendChild(li);
|
|
709
|
+
});
|
|
710
|
+
teil.appendChild(ul);
|
|
711
|
+
|
|
712
|
+
var fuss = e('div', 'zielfuss');
|
|
713
|
+
fuss.appendChild(skillsKnopf(z));
|
|
714
|
+
teil.appendChild(fuss);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return teil;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** Knopf „Skills hinzufügen" im Körper einer Ziel-Karte (E47), Gegenrichtung zu starteVerlinken.
|
|
721
|
+
Reichweite ist immer dieser eine Harness, das Zeichen steht davor (E53 Pkt. 4, 7, 8). */
|
|
722
|
+
function skillsKnopf(z) {
|
|
723
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'Skills hinzufügen');
|
|
724
|
+
knopf.type = 'button';
|
|
725
|
+
knopf.insertBefore(harnessZeichen(z.ziel.harness), knopf.firstChild);
|
|
726
|
+
knopf.setAttribute('aria-label', 'Skills zu ' + zielTitel(z) + ' hinzufügen (' + harnessName(z.ziel.harness) + ')');
|
|
727
|
+
knopf.addEventListener('click', function () { oeffneSkillsHinzufuegen(z); });
|
|
728
|
+
return knopf;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/** Knopf „Skills hinzufügen" für eine Verzeichnis-Karte im Chip „Alle" (E53 Pkt. 4): Reichweite
|
|
732
|
+
sind alle beteiligten Harnesses, das Ordner-Symbol steht davor. */
|
|
733
|
+
function alleSkillsKnopf(pfad, teile) {
|
|
734
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'Skills hinzufügen');
|
|
735
|
+
knopf.type = 'button';
|
|
736
|
+
knopf.insertBefore(ordnerZeichen(), knopf.firstChild);
|
|
737
|
+
knopf.setAttribute('aria-label', 'Skills zu ' + pfadKurz(pfad) + ' hinzufügen (alle Harnesses)');
|
|
738
|
+
knopf.addEventListener('click', function () { oeffneSkillsHinzufuegenAlle(pfad, teile); });
|
|
739
|
+
return knopf;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function zielKarte(z) {
|
|
743
|
+
var karte = e('div', 'ziel');
|
|
744
|
+
var kopf = e('div', 'zielkopf');
|
|
745
|
+
kopf.setAttribute('role', 'button');
|
|
746
|
+
kopf.setAttribute('tabindex', '0');
|
|
747
|
+
var schluessel = zielKey(z.ziel);
|
|
748
|
+
var bodyId = 'zielkoerper-' + zielKoerperNr++;
|
|
749
|
+
// Der Filter „Benötigt Aufmerksamkeit" ist eine Absichtserklärung (E45): er zeigt nur betroffene
|
|
750
|
+
// Ziele und klappt sie auf, unabhängig vom gemerkten Zustand, der ihm nicht gehört.
|
|
751
|
+
var offen = zielFilter === 'achtung' || !!ZIEL_OFFEN[schluessel];
|
|
752
|
+
kopf.setAttribute('aria-controls', bodyId);
|
|
753
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
754
|
+
kopf.appendChild(beschriftet(e('h3', null, zielTitel(z)), z.ziel.harness));
|
|
755
|
+
kopf.appendChild(e('span', 'mono', pfadKurz(z.verzeichnis)));
|
|
756
|
+
if (!harnessAktiv(z.ziel.harness)) kopf.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
757
|
+
if (zielBraucht(z)) {
|
|
758
|
+
// Zeichen statt Text-Badge: trägt die Farbe selbst (Emoji-Darstellung), aria-label hält den
|
|
759
|
+
// Akkordeon-Knopf-Namen brauchbar (role="img", die Bedeutung steht nicht im Glyph selbst).
|
|
760
|
+
var warnspan = e('span', 'warnung', '⚠️');
|
|
761
|
+
warnspan.setAttribute('role', 'img');
|
|
762
|
+
warnspan.setAttribute('aria-label', 'braucht Aufmerksamkeit');
|
|
763
|
+
kopf.appendChild(warnspan);
|
|
764
|
+
}
|
|
765
|
+
var zahl = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
766
|
+
kopf.appendChild(e('span', 'zahl', zahl + (zahl === 1 ? ' Link' : ' Links')));
|
|
767
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
768
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
769
|
+
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
770
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
771
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
772
|
+
kopf.appendChild(pfeil);
|
|
773
|
+
karte.appendChild(kopf);
|
|
774
|
+
var koerper = e('div', 'zielkoerper');
|
|
775
|
+
koerper.id = bodyId;
|
|
776
|
+
koerper.hidden = !offen;
|
|
777
|
+
koerper.appendChild(linkTeil(z));
|
|
778
|
+
karte.appendChild(koerper);
|
|
779
|
+
|
|
780
|
+
function schalte() {
|
|
781
|
+
offen = !offen;
|
|
782
|
+
ZIEL_OFFEN[schluessel] = offen;
|
|
783
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
784
|
+
koerper.hidden = !offen;
|
|
785
|
+
}
|
|
786
|
+
// Ein Bedienelement in der Kopfzeile (E47 „Skills hinzufügen") schaltet nicht zusätzlich um.
|
|
787
|
+
function ausBedienelement(ev) {
|
|
788
|
+
return ev.target.closest('button, a, input, select, textarea') !== null;
|
|
789
|
+
}
|
|
790
|
+
kopf.addEventListener('click', function (ev) {
|
|
791
|
+
if (!ausBedienelement(ev)) schalte();
|
|
792
|
+
});
|
|
793
|
+
kopf.addEventListener('keydown', function (ev) {
|
|
794
|
+
if (ausBedienelement(ev)) return;
|
|
795
|
+
if (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar') {
|
|
796
|
+
ev.preventDefault();
|
|
797
|
+
schalte();
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
return karte;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/** Körper des Chips „Alle" einer Verzeichnis-Karte (E53 Pkt. 2, 4, 5): eine gemeinsame Liste
|
|
805
|
+
statt gestapelter Harness-Abschnitte. Eigene Links gebündelt (Schlüssel Name+Quelle), fremde
|
|
806
|
+
Einträge und die Inaktiv-Zeile je Harness mit dessen Zeichen am Fundort. */
|
|
807
|
+
function alleTeil(pfad, teile) {
|
|
808
|
+
var teil = document.createDocumentFragment();
|
|
809
|
+
|
|
810
|
+
teile.forEach(function (z) {
|
|
811
|
+
var eigeneZ = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; });
|
|
812
|
+
if (!harnessAktiv(z.ziel.harness) && (eigeneZ.length > 0 || z.istRegistriert)) {
|
|
813
|
+
teil.appendChild(ruheZeile(z, eigeneZ.length > 0));
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
var beachteteAlle = [], ignorierteAlle = [];
|
|
818
|
+
teile.forEach(function (z) {
|
|
819
|
+
var ignoriertNamen = z.ignoriert || [];
|
|
820
|
+
z.eintraege.filter(function (ei) { return ei.art === 'fremd'; }).forEach(function (x) {
|
|
821
|
+
var paar = { x: x, z: z };
|
|
822
|
+
if (ignoriertNamen.indexOf(x.name) === -1) beachteteAlle.push(paar);
|
|
823
|
+
else ignorierteAlle.push(paar);
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
if (beachteteAlle.length > 0) teil.appendChild(fremdKasten(beachteteAlle, true));
|
|
827
|
+
if (ignorierteAlle.length > 0) teil.appendChild(ignoriertZeile(ignorierteAlle, true));
|
|
828
|
+
|
|
829
|
+
// Eigene Links über alle Harnesses gebündelt: Schlüssel Name+Quelle, hängt ein Name je Harness
|
|
830
|
+
// aus verschiedener Quelle, gibt es je Quelle eine eigene Zeile (E53 Pkt. 2).
|
|
831
|
+
var zeilen = {};
|
|
832
|
+
var reihenfolge = [];
|
|
833
|
+
teile.forEach(function (z) {
|
|
834
|
+
z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).forEach(function (ei) {
|
|
835
|
+
var defekt = ei.art === 'link-ohne-quelle';
|
|
836
|
+
var name = defekt ? ei.name : ei.link.name;
|
|
837
|
+
var quelleSchluessel = defekt ? ('defekt\u0000' + ei.checkoutPfad)
|
|
838
|
+
: (quelleKurz(ei.link.checkout.quelle) + '@' + ei.link.checkout.ref);
|
|
839
|
+
// NUL als Trenner, kein Leerzeichen: Skill-Namen dürfen Leerzeichen enthalten, Quellen-Namen
|
|
840
|
+
// seit E59 auch, ein lokaler Pfad ohnehin — „a b“ + „x“ und „a“ + „b x“ ergäben sonst
|
|
841
|
+
// denselben Schlüssel. Immer als Escape geschrieben, nie als rohes Byte im Quelltext.
|
|
842
|
+
var key = name + '\u0000' + quelleSchluessel;
|
|
843
|
+
if (!zeilen[key]) {
|
|
844
|
+
zeilen[key] = {
|
|
845
|
+
name: name,
|
|
846
|
+
defekt: defekt,
|
|
847
|
+
icon: defekt ? { art: 'lokal', pfad: ei.checkoutPfad } : ei.link.checkout.quelle,
|
|
848
|
+
checkoutPfad: defekt ? ei.checkoutPfad : null,
|
|
849
|
+
quelle: defekt ? null : ei.link.checkout.quelle,
|
|
850
|
+
ref: defekt ? null : ei.link.checkout.ref,
|
|
851
|
+
einschraenkung: defekt ? null : z.einschraenkungen[name],
|
|
852
|
+
beschreibung: defekt ? undefined : ei.link.skill.beschreibung,
|
|
853
|
+
teile: [],
|
|
854
|
+
};
|
|
855
|
+
reihenfolge.push(key);
|
|
856
|
+
}
|
|
857
|
+
zeilen[key].teile.push(z);
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
if (reihenfolge.length === 0) {
|
|
862
|
+
var leer = e('div', 'leerzeile');
|
|
863
|
+
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
864
|
+
leer.appendChild(alleSkillsKnopf(pfad, teile));
|
|
865
|
+
teil.appendChild(leer);
|
|
866
|
+
return teil;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
var kopf = e('div', 'linkkopf');
|
|
870
|
+
kopf.appendChild(e('span'));
|
|
871
|
+
kopf.appendChild(e('span', 'etikett', 'Skill'));
|
|
872
|
+
kopf.appendChild(e('span', 'etikett', 'Beschreibung'));
|
|
873
|
+
kopf.appendChild(e('span', 'etikett', 'Quelle'));
|
|
874
|
+
kopf.appendChild(e('span'));
|
|
875
|
+
teil.appendChild(kopf);
|
|
876
|
+
|
|
877
|
+
var ul = e('ul', 'linkliste');
|
|
878
|
+
reihenfolge
|
|
879
|
+
.slice()
|
|
880
|
+
.sort(function (ka, kb) {
|
|
881
|
+
var a = zeilen[ka], b = zeilen[kb];
|
|
882
|
+
if (a.defekt !== b.defekt) return a.defekt ? -1 : 1;
|
|
883
|
+
return a.name.localeCompare(b.name, 'de');
|
|
884
|
+
})
|
|
885
|
+
.forEach(function (key) {
|
|
886
|
+
var row = zeilen[key];
|
|
887
|
+
var harnesse = [];
|
|
888
|
+
row.teile.forEach(function (z) { if (harnesse.indexOf(z.ziel.harness) === -1) harnesse.push(z.ziel.harness); });
|
|
889
|
+
harnesse.sort(function (a, b) { return harnessName(a).localeCompare(harnessName(b), 'de'); });
|
|
890
|
+
|
|
891
|
+
var li = e('li');
|
|
892
|
+
li.appendChild(repoIcon(row.icon));
|
|
893
|
+
|
|
894
|
+
var nameSpan = e('span', 'name');
|
|
895
|
+
nameSpan.appendChild(harnesseZeichenSammel(harnesse));
|
|
896
|
+
nameSpan.appendChild(document.createTextNode(row.name));
|
|
897
|
+
li.appendChild(nameSpan);
|
|
898
|
+
|
|
899
|
+
if (row.beschreibung) {
|
|
900
|
+
var wasEl = e('span', 'was', row.beschreibung);
|
|
901
|
+
if (row.beschreibung.length > 88) wasEl.title = row.beschreibung;
|
|
902
|
+
li.appendChild(wasEl);
|
|
903
|
+
} else {
|
|
904
|
+
li.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
var qz = e('span', 'quelle');
|
|
908
|
+
if (row.defekt) {
|
|
909
|
+
qz.appendChild(e('span', 'mono', pfadKurz(row.checkoutPfad)));
|
|
910
|
+
} else {
|
|
911
|
+
if (row.einschraenkung) qz.appendChild(document.createTextNode(row.einschraenkung + ' → '));
|
|
912
|
+
qz.appendChild(quelleLinkElement(row.quelle));
|
|
913
|
+
var standardRef = STANDARD_REF[quelleKurz(row.quelle)];
|
|
914
|
+
if (row.ref && standardRef && row.ref !== standardRef) qz.appendChild(e('span', 'ref', '@' + row.ref));
|
|
915
|
+
// Unterscheiden sich die Gründe je Harness, zählt „über Zeiger", sobald einer davon so gilt.
|
|
916
|
+
var zeiger = row.teile.some(function (z) { var g = z.gruende[row.name]; return g && g.art === 'zeiger'; });
|
|
917
|
+
if (zeiger) qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
918
|
+
}
|
|
919
|
+
li.appendChild(qz);
|
|
920
|
+
|
|
921
|
+
var entfernbar = row.teile.filter(function (z) {
|
|
922
|
+
var g = z.gruende[row.name];
|
|
923
|
+
return !(g && g.art === 'plugin');
|
|
924
|
+
});
|
|
925
|
+
var pluginNamen = [];
|
|
926
|
+
row.teile.forEach(function (z) {
|
|
927
|
+
var g = z.gruende[row.name];
|
|
928
|
+
if (g && g.art === 'plugin' && pluginNamen.indexOf(g.plugin.split('@')[0]) === -1) {
|
|
929
|
+
pluginNamen.push(g.plugin.split('@')[0]);
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
var nichtEntfernbar = row.teile
|
|
933
|
+
.filter(function (z) { var g = z.gruende[row.name]; return !!(g && g.art === 'plugin'); })
|
|
934
|
+
.map(function (z) { return z.ziel.harness; });
|
|
935
|
+
|
|
936
|
+
if (entfernbar.length > 0) {
|
|
937
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
938
|
+
weg.type = 'button';
|
|
939
|
+
// Die Reichweite gehört in die Beschriftung: unter „Alle" stehen zwei Zeilen gleichen
|
|
940
|
+
// Namens nebeneinander, sobald ein Skill je Harness aus verschiedener Quelle hängt.
|
|
941
|
+
weg.setAttribute('aria-label', 'Link auf ' + row.name + ' in '
|
|
942
|
+
+ entfernbar.map(function (z) { return harnessName(z.ziel.harness); }).join(', ') + ' entfernen');
|
|
943
|
+
weg.addEventListener('click', function () { frageEntfernenAlle(row.name, pfad, entfernbar, nichtEntfernbar); });
|
|
944
|
+
li.appendChild(weg);
|
|
945
|
+
} else {
|
|
946
|
+
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + pluginNamen.join('“, „') + '“'));
|
|
947
|
+
}
|
|
948
|
+
ul.appendChild(li);
|
|
949
|
+
});
|
|
950
|
+
teil.appendChild(ul);
|
|
951
|
+
|
|
952
|
+
var fuss = e('div', 'zielfuss');
|
|
953
|
+
fuss.appendChild(alleSkillsKnopf(pfad, teile));
|
|
954
|
+
teil.appendChild(fuss);
|
|
955
|
+
|
|
956
|
+
return teil;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/** Chip-Wahl einer Verzeichnis-Karte: merken, Liste neu bauen, Fokus zurück auf den Chip. */
|
|
960
|
+
function waehleChip(schluessel, wahl) {
|
|
961
|
+
VERZ_CHIP[schluessel] = wahl;
|
|
962
|
+
CHIP_FOKUS = { karte: schluessel, wahl: wahl };
|
|
963
|
+
baueZiele();
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/** Eine Akkordeon-Karte je Arbeitsverzeichnis (E49), im Körper eine Chip-Leiste „Alle" + je
|
|
967
|
+
beteiligtem Harness ein Chip (E53): „Alle" zeigt die gebündelte Liste (alleTeil), ein
|
|
968
|
+
Harness-Chip exakt dessen Einzel-Logik (linkTeil). Der Offen-Zustand läuft über verzKey(pfad). */
|
|
969
|
+
function verzeichnisKarte(pfad, teile) {
|
|
970
|
+
var karte = e('div', 'ziel verzeichnis');
|
|
971
|
+
var kopf = e('div', 'zielkopf');
|
|
972
|
+
kopf.setAttribute('role', 'button');
|
|
973
|
+
kopf.setAttribute('tabindex', '0');
|
|
974
|
+
var schluessel = verzKey(pfad);
|
|
975
|
+
var bodyId = 'zielkoerper-' + zielKoerperNr++;
|
|
976
|
+
var offen = zielFilter === 'achtung' || !!ZIEL_OFFEN[schluessel];
|
|
977
|
+
kopf.setAttribute('aria-controls', bodyId);
|
|
978
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
979
|
+
kopf.appendChild(e('h3', null, pfadKurz(pfad)));
|
|
980
|
+
|
|
981
|
+
// Harness-Zeichen aller beteiligten Harnesses statt eines einzelnen (kein Ziel hat hier nur
|
|
982
|
+
// einen Harness): klein, im Kopf, mit Sammel-aria-label statt einzelner Beschriftung je Icon.
|
|
983
|
+
var symbole = e('span', 'harnesssymbole');
|
|
984
|
+
var namen = teile.map(function (z) { return harnessName(z.ziel.harness); }).sort(function (a, b) { return a.localeCompare(b, 'de'); });
|
|
985
|
+
symbole.setAttribute('role', 'img');
|
|
986
|
+
symbole.setAttribute('aria-label', namen.join(', '));
|
|
987
|
+
teile
|
|
988
|
+
.slice()
|
|
989
|
+
.sort(function (a, b) { return harnessName(a.ziel.harness).localeCompare(harnessName(b.ziel.harness), 'de'); })
|
|
990
|
+
.forEach(function (z) { symbole.appendChild(harnessZeichen(z.ziel.harness)); });
|
|
991
|
+
kopf.appendChild(symbole);
|
|
992
|
+
|
|
993
|
+
if (teile.some(zielBraucht)) {
|
|
994
|
+
var warnspan = e('span', 'warnung', '⚠️');
|
|
995
|
+
warnspan.setAttribute('role', 'img');
|
|
996
|
+
warnspan.setAttribute('aria-label', 'braucht Aufmerksamkeit');
|
|
997
|
+
kopf.appendChild(warnspan);
|
|
998
|
+
}
|
|
999
|
+
var gesamtZahl = teile.reduce(function (n, z) {
|
|
1000
|
+
return n + z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
1001
|
+
}, 0);
|
|
1002
|
+
kopf.appendChild(e('span', 'zahl', gesamtZahl + (gesamtZahl === 1 ? ' Link' : ' Links')));
|
|
1003
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1004
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
1005
|
+
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
1006
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
1007
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
1008
|
+
kopf.appendChild(pfeil);
|
|
1009
|
+
karte.appendChild(kopf);
|
|
1010
|
+
|
|
1011
|
+
var koerper = e('div', 'zielkoerper');
|
|
1012
|
+
koerper.id = bodyId;
|
|
1013
|
+
koerper.hidden = !offen;
|
|
1014
|
+
|
|
1015
|
+
// Chip-Leiste „Alle" + je beteiligtem Harness einer, Wahl je Karte über VERZ_CHIP (E53 Pkt. 1).
|
|
1016
|
+
var harnesseBeteiligt = [];
|
|
1017
|
+
teile.forEach(function (z) { if (harnesseBeteiligt.indexOf(z.ziel.harness) === -1) harnesseBeteiligt.push(z.ziel.harness); });
|
|
1018
|
+
harnesseBeteiligt.sort(function (a, b) { return harnessName(a).localeCompare(harnessName(b), 'de'); });
|
|
1019
|
+
var aktivChip = VERZ_CHIP[schluessel] || 'alle';
|
|
1020
|
+
if (aktivChip !== 'alle' && harnesseBeteiligt.indexOf(aktivChip) === -1) aktivChip = 'alle';
|
|
1021
|
+
|
|
1022
|
+
var chipleiste = e('div', 'filter kartenchips');
|
|
1023
|
+
chipleiste.setAttribute('role', 'group');
|
|
1024
|
+
chipleiste.setAttribute('aria-label', 'Harness wählen');
|
|
1025
|
+
var chipAlle = e('button', 'chip', 'Alle');
|
|
1026
|
+
chipAlle.type = 'button';
|
|
1027
|
+
chipAlle.dataset.chipkarte = schluessel;
|
|
1028
|
+
chipAlle.dataset.chipwahl = 'alle';
|
|
1029
|
+
chipAlle.setAttribute('aria-pressed', aktivChip === 'alle' ? 'true' : 'false');
|
|
1030
|
+
chipAlle.addEventListener('click', function () { waehleChip(schluessel, 'alle'); });
|
|
1031
|
+
chipleiste.appendChild(chipAlle);
|
|
1032
|
+
harnesseBeteiligt.forEach(function (h) {
|
|
1033
|
+
var chip = e('button', 'chip', harnessName(h));
|
|
1034
|
+
chip.type = 'button';
|
|
1035
|
+
chip.dataset.chipkarte = schluessel;
|
|
1036
|
+
chip.dataset.chipwahl = h;
|
|
1037
|
+
chip.insertBefore(harnessZeichen(h), chip.firstChild);
|
|
1038
|
+
chip.setAttribute('aria-pressed', aktivChip === h ? 'true' : 'false');
|
|
1039
|
+
chip.addEventListener('click', function () { waehleChip(schluessel, h); });
|
|
1040
|
+
chipleiste.appendChild(chip);
|
|
1041
|
+
});
|
|
1042
|
+
if (aktivChip === 'alle') {
|
|
1043
|
+
koerper.appendChild(chipleiste);
|
|
1044
|
+
koerper.appendChild(alleTeil(pfad, teile));
|
|
1045
|
+
} else {
|
|
1046
|
+
var gewaehlt = teile.filter(function (z) { return z.ziel.harness === aktivChip; })[0];
|
|
1047
|
+
// Welcher Ordner im Verzeichnis gelesen wird, stand vorher im Abschnittskopf (25cddf6) und
|
|
1048
|
+
// gehört zum gewählten Harness, nicht zur Karte: er steht rechts in der Chip-Leiste, relativ
|
|
1049
|
+
// zum Verzeichnis, mit der Inaktiv-Marke daneben.
|
|
1050
|
+
if (gewaehlt) {
|
|
1051
|
+
var unterhalb = gewaehlt.verzeichnis.indexOf(pfad + '/') === 0;
|
|
1052
|
+
chipleiste.appendChild(e('span', 'kartenordner mono',
|
|
1053
|
+
unterhalb ? gewaehlt.verzeichnis.slice(pfad.length + 1) : pfadKurz(gewaehlt.verzeichnis)));
|
|
1054
|
+
if (!harnessAktiv(aktivChip)) chipleiste.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
1055
|
+
}
|
|
1056
|
+
koerper.appendChild(chipleiste);
|
|
1057
|
+
if (gewaehlt) koerper.appendChild(linkTeil(gewaehlt));
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Entfernen heißt nur vergessen (E52, E26): der Dialog versichert das, gelöscht wird nichts.
|
|
1061
|
+
var fuss = e('div', 'zielfuss');
|
|
1062
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen …');
|
|
1063
|
+
weg.type = 'button';
|
|
1064
|
+
weg.setAttribute('aria-label', pfadKurz(pfad) + ' aus hmgr entfernen');
|
|
1065
|
+
weg.addEventListener('click', function () { frageVergessen(pfad); });
|
|
1066
|
+
fuss.appendChild(weg);
|
|
1067
|
+
koerper.appendChild(fuss);
|
|
1068
|
+
karte.appendChild(koerper);
|
|
1069
|
+
|
|
1070
|
+
function schalte() {
|
|
1071
|
+
offen = !offen;
|
|
1072
|
+
ZIEL_OFFEN[schluessel] = offen;
|
|
1073
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
1074
|
+
koerper.hidden = !offen;
|
|
1075
|
+
}
|
|
1076
|
+
function ausBedienelement(ev) {
|
|
1077
|
+
return ev.target.closest('button, a, input, select, textarea') !== null;
|
|
1078
|
+
}
|
|
1079
|
+
kopf.addEventListener('click', function (ev) {
|
|
1080
|
+
if (!ausBedienelement(ev)) schalte();
|
|
1081
|
+
});
|
|
1082
|
+
kopf.addEventListener('keydown', function (ev) {
|
|
1083
|
+
if (ausBedienelement(ev)) return;
|
|
1084
|
+
if (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar') {
|
|
1085
|
+
ev.preventDefault();
|
|
1086
|
+
schalte();
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
return karte;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function baueZiele() {
|
|
1094
|
+
var liste = $('zielliste');
|
|
1095
|
+
liste.textContent = '';
|
|
1096
|
+
var karten = zielKarten();
|
|
1097
|
+
|
|
1098
|
+
var agentsSichtbar = karten.agents
|
|
1099
|
+
.filter(function (z) { return zielFilter === 'alle' || zielBraucht(z); })
|
|
1100
|
+
.sort(function (a, b) { return zielTitel(a).localeCompare(zielTitel(b), 'de'); });
|
|
1101
|
+
var verzeichnisseSichtbar = karten.verzeichnisse
|
|
1102
|
+
.filter(function (g) { return zielFilter === 'alle' || g.teile.some(zielBraucht); })
|
|
1103
|
+
.sort(function (a, b) { return pfadKurz(a.pfad).localeCompare(pfadKurz(b.pfad), 'de'); });
|
|
1104
|
+
|
|
1105
|
+
if (agentsSichtbar.length > 0) {
|
|
1106
|
+
liste.appendChild(e('h2', 'gruppenkopf', 'Agents'));
|
|
1107
|
+
agentsSichtbar.forEach(function (z) { liste.appendChild(zielKarte(z)); });
|
|
1108
|
+
}
|
|
1109
|
+
if (verzeichnisseSichtbar.length > 0) {
|
|
1110
|
+
liste.appendChild(e('h2', 'gruppenkopf', 'Arbeitsverzeichnisse'));
|
|
1111
|
+
verzeichnisseSichtbar.forEach(function (g) { liste.appendChild(verzeichnisKarte(g.pfad, g.teile)); });
|
|
1112
|
+
}
|
|
1113
|
+
var gezeigt = agentsSichtbar.length + verzeichnisseSichtbar.length;
|
|
1114
|
+
var gesamt = karten.agents.length + karten.verzeichnisse.length;
|
|
1115
|
+
if (gezeigt === 0)
|
|
1116
|
+
liste.appendChild(e('p', 'leerhinweis', zielFilter === 'achtung'
|
|
1117
|
+
? 'Kein Ziel braucht gerade Aufmerksamkeit.'
|
|
1118
|
+
: 'Noch keine Ziele. Füge unten ein Arbeitsverzeichnis hinzu oder verlinke einen Skill.'));
|
|
1119
|
+
|
|
1120
|
+
$('zielzahl').textContent = gezeigt + ' von ' + gesamt + ' Karten gezeigt';
|
|
1121
|
+
|
|
1122
|
+
if (CHIP_FOKUS !== null) {
|
|
1123
|
+
var merker = CHIP_FOKUS;
|
|
1124
|
+
CHIP_FOKUS = null;
|
|
1125
|
+
Array.prototype.forEach.call(liste.querySelectorAll('.kartenchips .chip'), function (c) {
|
|
1126
|
+
if (c.dataset.chipkarte === merker.karte && c.dataset.chipwahl === merker.wahl) c.focus();
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
document.querySelectorAll('#ansicht-ziele .chip').forEach(function (c) {
|
|
1132
|
+
c.addEventListener('click', function () {
|
|
1133
|
+
zielFilter = c.dataset.zielfilter;
|
|
1134
|
+
// Filterwechsel leert den gemerkten Offen-Zustand (E45): „Alle" beginnt wieder eingeklappt.
|
|
1135
|
+
// Ebenso die Chip-Wahl (E53): sonst zeigte eine Karte unter „Benötigt Aufmerksamkeit" einen
|
|
1136
|
+
// Harness, in dem gar nichts zu sehen ist, während der Grund im Chip daneben steckt.
|
|
1137
|
+
ZIEL_OFFEN = {};
|
|
1138
|
+
VERZ_CHIP = {};
|
|
1139
|
+
document.querySelectorAll('#ansicht-ziele .chip').forEach(function (a) {
|
|
1140
|
+
a.setAttribute('aria-pressed', a === c ? 'true' : 'false');
|
|
1141
|
+
});
|
|
1142
|
+
baueZiele();
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
// entfernenKontext.eintraege: [{harness, ort}], immer eines bei frageEntfernen (Einzel-Ziel),
|
|
1147
|
+
// mehrere bei frageEntfernenAlle (E53 Pkt. 2, „Alle"-Zeile über mehrere Harnesses).
|
|
1148
|
+
var entfernenKontext = null;
|
|
1149
|
+
function frageEntfernen(name, ziel) {
|
|
1150
|
+
entfernenKontext = { name: name, eintraege: [{ harness: ziel.harness, ort: ziel.ort }] };
|
|
1151
|
+
$('dx-titel').textContent = 'Link auf ' + name + ' entfernen?';
|
|
1152
|
+
$('dx-text').textContent = 'Der Link in ' + zielTitel({ ziel: ziel }) + ' verschwindet. Der Checkout bleibt liegen, bis kein Link mehr darauf zeigt.';
|
|
1153
|
+
zeige($('d-entfernen'));
|
|
1154
|
+
}
|
|
1155
|
+
/** Entfernen einer „Alle"-Zeile (E53 Pkt. 2): entfernbareZiele sind die Ziele (voller z.ziel), in
|
|
1156
|
+
denen der Link weg soll, nichtEntfernbareHarnesse die, in denen er nur über ein Plugin hängt
|
|
1157
|
+
und liegen bleibt. Der Dialog nennt beides. */
|
|
1158
|
+
function frageEntfernenAlle(name, pfad, entfernbareZiele, nichtEntfernbareHarnesse) {
|
|
1159
|
+
entfernenKontext = {
|
|
1160
|
+
name: name,
|
|
1161
|
+
eintraege: entfernbareZiele.map(function (z) { return { harness: z.ziel.harness, ort: z.ziel.ort }; }),
|
|
1162
|
+
};
|
|
1163
|
+
var namen = entfernbareZiele.map(function (z) { return harnessName(z.ziel.harness); });
|
|
1164
|
+
$('dx-titel').textContent = 'Link auf ' + name + ' entfernen?';
|
|
1165
|
+
var text = 'Der Link in ' + namen.join(', ') + ' (' + pfadKurz(pfad) + ') verschwindet.';
|
|
1166
|
+
if (nichtEntfernbareHarnesse.length > 0) {
|
|
1167
|
+
text += ' In ' + nichtEntfernbareHarnesse.map(harnessName).join(', ') + ' bleibt er bestehen (nur über das Plugin).';
|
|
1168
|
+
}
|
|
1169
|
+
text += ' Der Checkout bleibt liegen, bis kein Link mehr darauf zeigt.';
|
|
1170
|
+
$('dx-text').textContent = text;
|
|
1171
|
+
zeige($('d-entfernen'));
|
|
1172
|
+
}
|
|
1173
|
+
nachDialog($('d-entfernen'), async function (wert) {
|
|
1174
|
+
if (wert !== 'ja' || entfernenKontext === null) return;
|
|
1175
|
+
var kontext = entfernenKontext;
|
|
1176
|
+
// Ein Fehlschlag in einem Harness hält die übrigen nicht auf: rufe hat ihn schon gemeldet, die
|
|
1177
|
+
// Schleife läuft weiter, und die Liste wird danach in jedem Fall neu geladen. Sonst stünde nach
|
|
1178
|
+
// einem Fehler die halb erledigte Arbeit unsichtbar in einer veralteten Liste.
|
|
1179
|
+
var gescheitert = [];
|
|
1180
|
+
var hinweiseWeg = [];
|
|
1181
|
+
for (var i = 0; i < kontext.eintraege.length; i++) {
|
|
1182
|
+
var eintrag = kontext.eintraege[i];
|
|
1183
|
+
try {
|
|
1184
|
+
var weg = await rufe(function () {
|
|
1185
|
+
return window.hmgr.linkEntfernen({ skill: kontext.name, harness: eintrag.harness, ort: eintrag.ort });
|
|
1186
|
+
});
|
|
1187
|
+
if (weg && weg.hinweis && hinweiseWeg.indexOf(weg.hinweis) < 0) hinweiseWeg.push(weg.hinweis);
|
|
1188
|
+
} catch (fehler) {
|
|
1189
|
+
gescheitert.push(harnessName(eintrag.harness));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
await ladeAlles();
|
|
1193
|
+
if (gescheitert.length > 1) melde('Der Link auf ' + kontext.name + ' blieb in ' + gescheitert.join(', ') + ' liegen.');
|
|
1194
|
+
// Zeiger-Hinweis (F32) nur, wenn nichts gescheitert ist: sonst stünde er über der Fehlermeldung,
|
|
1195
|
+
// die rufe schon gesetzt hat.
|
|
1196
|
+
else if (gescheitert.length === 0 && hinweiseWeg.length > 0) melde(hinweiseWeg.join('\n'));
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
var loeschenKontext = null;
|
|
1200
|
+
var vergessenPfad = null;
|
|
1201
|
+
function frageVergessen(pfad) {
|
|
1202
|
+
vergessenPfad = pfad;
|
|
1203
|
+
$('dg-text').textContent = pfadKurz(pfad) + ' wird dann nicht mehr von hmgr verwaltet. Das Verzeichnis und alles darin bleiben unverändert erhalten. Zum Wiederaufnehmen fügst du es einfach erneut hinzu.';
|
|
1204
|
+
zeige($('d-vergessen'));
|
|
1205
|
+
}
|
|
1206
|
+
nachDialog($('d-vergessen'), async function (wert) {
|
|
1207
|
+
if (wert !== 'ja' || vergessenPfad === null) return;
|
|
1208
|
+
var pfad = vergessenPfad;
|
|
1209
|
+
vergessenPfad = null;
|
|
1210
|
+
await rufe(function () { return window.hmgr.arbeitsverzeichnisVergessen(pfad); });
|
|
1211
|
+
await ladeAlles();
|
|
1212
|
+
melde(pfadKurz(pfad) + ' aus hmgr entfernt, das Verzeichnis bleibt erhalten.');
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
function frageLoeschen(name, ziel, verzeichnis) {
|
|
1216
|
+
loeschenKontext = { name: name, ziel: ziel };
|
|
1217
|
+
$('dl-titel').textContent = '„' + name + '“ löschen?';
|
|
1218
|
+
$('dl-text').textContent = 'hmgr löscht ' + pfadKurz(verzeichnis) + '/' + name
|
|
1219
|
+
+ ' unwiderruflich von der Platte, ein Ordner mit allem Inhalt oder ein Link. Das lässt sich nicht rückgängig machen.';
|
|
1220
|
+
zeige($('d-loeschen'));
|
|
1221
|
+
}
|
|
1222
|
+
nachDialog($('d-loeschen'), async function (wert) {
|
|
1223
|
+
if (wert !== 'ja' || loeschenKontext === null) return;
|
|
1224
|
+
var kontext = loeschenKontext;
|
|
1225
|
+
await rufe(function () {
|
|
1226
|
+
return window.hmgr.eintragLoeschen({ harness: kontext.ziel.harness, ort: kontext.ziel.ort, name: kontext.name });
|
|
1227
|
+
});
|
|
1228
|
+
await ladeAlles();
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
var uebernahmeKontext = null;
|
|
1232
|
+
// Lesbare Fassung der Kern-Gründe (UebernahmeGrund), warum ein Eintrag liegen bleibt: eine kurze
|
|
1233
|
+
// Ursache, dazu Handreichungen (E43), die der Nutzer außerhalb von hmgr selbst erledigt.
|
|
1234
|
+
var UEBERNAHME_GRUENDE = {
|
|
1235
|
+
'kein-link': {
|
|
1236
|
+
ursache: 'Von Hand installiert, hmgr kennt die Herkunft nicht.',
|
|
1237
|
+
tipps: [
|
|
1238
|
+
'In dein Skills-Repo übernehmen und von dort verlinken',
|
|
1239
|
+
'Oder als Quelle aufnehmen, falls es ein Git-Repo dazu gibt',
|
|
1240
|
+
],
|
|
1241
|
+
},
|
|
1242
|
+
'keine-arbeitskopie': {
|
|
1243
|
+
ursache: 'Der Link führt in keine Git-Arbeitskopie.',
|
|
1244
|
+
tipps: ['Das Linkziel in ein Git-Repo bringen, dann erneut übernehmen'],
|
|
1245
|
+
},
|
|
1246
|
+
'keine-url': {
|
|
1247
|
+
ursache: 'Die Arbeitskopie hat keine Remote-URL.',
|
|
1248
|
+
tipps: ['Remote hinzufügen und pushen, dann erneut übernehmen'],
|
|
1249
|
+
},
|
|
1250
|
+
'kein-branch': {
|
|
1251
|
+
ursache: 'Die Arbeitskopie steht auf keinem Branch.',
|
|
1252
|
+
tipps: ['Branch auschecken, dann erneut übernehmen'],
|
|
1253
|
+
},
|
|
1254
|
+
'skill-unbekannt': {
|
|
1255
|
+
ursache: 'Die Quelle führt unter diesem Pfad keinen Skill.',
|
|
1256
|
+
tipps: ['Prüfen, ob der Skill im Repo verschoben oder entfernt wurde'],
|
|
1257
|
+
},
|
|
1258
|
+
'lokale-aenderungen': {
|
|
1259
|
+
ursache: 'Der Ordner weicht von der Quelle ab.',
|
|
1260
|
+
tipps: ['Änderungen committen und pushen, dann erneut übernehmen'],
|
|
1261
|
+
},
|
|
1262
|
+
'nicht-pruefbar': {
|
|
1263
|
+
ursache: 'hmgr kann nicht prüfen, ob der Ordner der Quelle entspricht.',
|
|
1264
|
+
tipps: ['Skill über die skills-CLI neu installieren, dann erneut prüfen'],
|
|
1265
|
+
},
|
|
1266
|
+
};
|
|
1267
|
+
async function oeffneUebernahme(ziel, name) {
|
|
1268
|
+
uebernahmeKontext = { ziel: ziel, name: name };
|
|
1269
|
+
$('du-etikett').textContent = 'Probelauf, nichts wird verändert';
|
|
1270
|
+
$('du-titel').textContent = '„' + name + '“ in ' + zielTitel({ ziel: ziel }) + ' übernehmen?';
|
|
1271
|
+
var ergebnis = await rufe(function () {
|
|
1272
|
+
return window.hmgr.uebernehmen({ harness: ziel.harness, ort: ziel.ort, name: name, probelauf: true });
|
|
1273
|
+
});
|
|
1274
|
+
var ul = $('du-liste');
|
|
1275
|
+
ul.textContent = '';
|
|
1276
|
+
ergebnis.eintraege.forEach(function (ei) {
|
|
1277
|
+
var li = e('li');
|
|
1278
|
+
if (ei.ergebnis === 'uebernommen') {
|
|
1279
|
+
li.appendChild(e('span', 'ok', 'wird ersetzt'));
|
|
1280
|
+
var s = e('span');
|
|
1281
|
+
s.appendChild(e('b', null, ei.name));
|
|
1282
|
+
s.appendChild(quelleSpan('mono', ei.quelle, ' @ ' + ei.ref + ' (' + ei.skill.pfad + ')'));
|
|
1283
|
+
if (ei.ordnerErsetzt) s.appendChild(e('p', 'fremderklaerung', 'Der Ordner wird gelöscht und durch den Link ersetzt.'));
|
|
1284
|
+
if (ei.gitignoriert && ei.gitignoriert.length > 0) {
|
|
1285
|
+
var maxAnzeige = 5;
|
|
1286
|
+
var angezeigt = ei.gitignoriert.slice(0, maxAnzeige).join(', ');
|
|
1287
|
+
var weitere = ei.gitignoriert.length > maxAnzeige ? ', …' : '';
|
|
1288
|
+
s.appendChild(e('p', 'fremderklaerung', zahlwort(ei.gitignoriert.length, 'gitignorierte Datei geht', 'gitignorierte Dateien gehen')
|
|
1289
|
+
+ ' beim Ersetzen verloren: ' + angezeigt + weitere));
|
|
1290
|
+
}
|
|
1291
|
+
li.appendChild(s);
|
|
1292
|
+
} else {
|
|
1293
|
+
li.appendChild(e('span', 'aus', 'bleibt liegen'));
|
|
1294
|
+
var s2 = e('span');
|
|
1295
|
+
s2.appendChild(e('b', null, ei.name));
|
|
1296
|
+
var grundInfo = UEBERNAHME_GRUENDE[ei.grund];
|
|
1297
|
+
s2.appendChild(e('span', 'mono', grundInfo ? grundInfo.ursache : ei.grund));
|
|
1298
|
+
if (grundInfo && grundInfo.tipps && grundInfo.tipps.length > 0) {
|
|
1299
|
+
var tippbox = e('div', 'tipps');
|
|
1300
|
+
tippbox.appendChild(e('p', 'tippkopf', 'Außerhalb von hmgr:'));
|
|
1301
|
+
var tul = e('ul', 'tippliste');
|
|
1302
|
+
grundInfo.tipps.forEach(function (t) { tul.appendChild(e('li', null, '→ ' + t)); });
|
|
1303
|
+
tippbox.appendChild(tul);
|
|
1304
|
+
s2.appendChild(tippbox);
|
|
1305
|
+
}
|
|
1306
|
+
li.appendChild(s2);
|
|
1307
|
+
}
|
|
1308
|
+
ul.appendChild(li);
|
|
1309
|
+
});
|
|
1310
|
+
if (ergebnis.eintraege.length === 0) ul.appendChild(e('li', null, 'Nichts mehr zu übernehmen.'));
|
|
1311
|
+
var ersetzt = ergebnis.eintraege.filter(function (ei) { return ei.ergebnis === 'uebernommen'; }).length;
|
|
1312
|
+
var liegen = ergebnis.eintraege.length - ersetzt;
|
|
1313
|
+
$('du-bilanz').textContent = ersetzt === 0
|
|
1314
|
+
? 'Nichts wird ersetzt.'
|
|
1315
|
+
: (ersetzt === 1 ? '1 wird ersetzt' : ersetzt + ' werden ersetzt') + ', '
|
|
1316
|
+
+ (liegen === 0 ? 'nichts bleibt liegen' : liegen === 1 ? '1 bleibt liegen' : liegen + ' bleiben liegen');
|
|
1317
|
+
// Ohne Übernehmbares gibt es nichts auszuführen: Knopf weg statt stumm deaktiviert.
|
|
1318
|
+
$('du-los').hidden = ersetzt === 0;
|
|
1319
|
+
zeige($('d-uebernahme'));
|
|
1320
|
+
}
|
|
1321
|
+
nachDialog($('d-uebernahme'), async function (wert) {
|
|
1322
|
+
if (wert !== 'los' || uebernahmeKontext === null) return;
|
|
1323
|
+
var ziel = uebernahmeKontext.ziel;
|
|
1324
|
+
var name = uebernahmeKontext.name;
|
|
1325
|
+
var ergebnis = await rufe(function () {
|
|
1326
|
+
return window.hmgr.uebernehmen({ harness: ziel.harness, ort: ziel.ort, name: name, probelauf: false });
|
|
1327
|
+
});
|
|
1328
|
+
var uebernommen = ergebnis.eintraege.filter(function (ei) { return ei.ergebnis === 'uebernommen'; }).length;
|
|
1329
|
+
var uebersprungen = ergebnis.eintraege.length - uebernommen;
|
|
1330
|
+
$('de-titel').textContent = 'Übernommen';
|
|
1331
|
+
$('de-text').textContent = uebernommen + ' übernommen, ' + uebersprungen + ' übersprungen.';
|
|
1332
|
+
setzeErfolgHinweise([ergebnis.hinweis]);
|
|
1333
|
+
var ul = $('de-liste');
|
|
1334
|
+
ul.textContent = '';
|
|
1335
|
+
ergebnis.eintraege.forEach(function (ei) {
|
|
1336
|
+
var li = e('li');
|
|
1337
|
+
li.appendChild(e('span', ei.ergebnis === 'uebernommen' ? 'ok' : 'aus', ei.ergebnis === 'uebernommen' ? 'Link' : 'Liegen gelassen'));
|
|
1338
|
+
var s = e('span');
|
|
1339
|
+
s.appendChild(e('b', null, ei.name));
|
|
1340
|
+
li.appendChild(s);
|
|
1341
|
+
ul.appendChild(li);
|
|
1342
|
+
});
|
|
1343
|
+
await ladeAlles();
|
|
1344
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
$('verzneu').addEventListener('click', function () {
|
|
1348
|
+
$('dv-pfad').value = '';
|
|
1349
|
+
zeige($('d-verzeichnis'));
|
|
1350
|
+
});
|
|
1351
|
+
enterBestaetigt($('dv-pfad'), $('dv-los'));
|
|
1352
|
+
$('dv-auswaehlen').addEventListener('click', async function () {
|
|
1353
|
+
var pfad = await rufe(function () { return window.hmgr.verzeichnisWaehlen(); });
|
|
1354
|
+
if (pfad) $('dv-pfad').value = pfad;
|
|
1355
|
+
});
|
|
1356
|
+
nachDialog($('d-verzeichnis'), async function (wert) {
|
|
1357
|
+
if (wert !== 'los') return;
|
|
1358
|
+
var pfad = $('dv-pfad').value.trim();
|
|
1359
|
+
if (!pfad) return;
|
|
1360
|
+
var ergebnis = await rufe(function () { return window.hmgr.arbeitsverzeichnisHinzufuegen(pfad); });
|
|
1361
|
+
await ladeAlles();
|
|
1362
|
+
$('de-titel').textContent = 'Arbeitsverzeichnis hinzugefügt';
|
|
1363
|
+
$('de-text').textContent = ergebnis.hinweis || ('Zeiger: ' + (ergebnis.zeigerPfad || 'keiner'));
|
|
1364
|
+
setzeErfolgHinweise([]);
|
|
1365
|
+
$('de-liste').textContent = '';
|
|
1366
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
/* ================= SKILLS ================= */
|
|
1370
|
+
|
|
1371
|
+
var QUELLFARBEN = ['', 'q2', 'q3'];
|
|
1372
|
+
function quellenIndex(quelleText) {
|
|
1373
|
+
var i = QUELLEN.findIndex(function (q) { return q.text === quelleText; });
|
|
1374
|
+
return i < 0 ? 0 : i % 3;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function baueQuellenfilter() {
|
|
1378
|
+
var box = $('quellenfilter');
|
|
1379
|
+
box.textContent = '';
|
|
1380
|
+
var alle = e('button', 'chip', 'Alle');
|
|
1381
|
+
alle.type = 'button';
|
|
1382
|
+
alle.dataset.filter = 'alle';
|
|
1383
|
+
alle.setAttribute('aria-pressed', quellenFilter === 'alle' ? 'true' : 'false');
|
|
1384
|
+
alle.addEventListener('click', function () { setzeQuellenfilter('alle'); });
|
|
1385
|
+
box.appendChild(alle);
|
|
1386
|
+
|
|
1387
|
+
var gesehen = {};
|
|
1388
|
+
SKILLS.forEach(function (s) {
|
|
1389
|
+
if (gesehen[s.quelleSchluessel]) return;
|
|
1390
|
+
gesehen[s.quelleSchluessel] = true;
|
|
1391
|
+
var idx = quellenIndex(s.quelleText);
|
|
1392
|
+
var chip = e('button', 'chip', s.quelleName);
|
|
1393
|
+
chip.title = s.quelleText;
|
|
1394
|
+
chip.type = 'button';
|
|
1395
|
+
chip.dataset.filter = s.quelleSchluessel;
|
|
1396
|
+
chip.setAttribute('aria-pressed', quellenFilter === s.quelleSchluessel ? 'true' : 'false');
|
|
1397
|
+
var punkt = e('i');
|
|
1398
|
+
punkt.style.background = 'var(--q' + (idx + 1) + ')';
|
|
1399
|
+
chip.insertBefore(punkt, chip.firstChild);
|
|
1400
|
+
chip.addEventListener('click', function () { setzeQuellenfilter(s.quelleSchluessel); });
|
|
1401
|
+
|
|
1402
|
+
// Quellen-Chip-Paar (E60): links filtert der Name, rechts öffnet das Pfeilsymbol die Quelle
|
|
1403
|
+
// (Repo-Seite im Browser, lokale Arbeitskopie im Finder). Kein <a> im <button>, deshalb
|
|
1404
|
+
// stehen sie nebeneinander in einer gemeinsamen Hülle.
|
|
1405
|
+
var quelle = QUELLEN.filter(function (q) { return q.text === s.quelleText; })[0];
|
|
1406
|
+
if (quelle) {
|
|
1407
|
+
var gruppe = e('span', 'chipgruppe');
|
|
1408
|
+
gruppe.appendChild(chip);
|
|
1409
|
+
var lokal = quelle.art === 'lokal';
|
|
1410
|
+
var oeffnen = document.createElement(lokal ? 'button' : 'a');
|
|
1411
|
+
if (lokal) oeffnen.type = 'button'; else oeffnen.href = '#';
|
|
1412
|
+
oeffnen.className = 'chipoeffnen';
|
|
1413
|
+
oeffnen.title = quelle.text;
|
|
1414
|
+
oeffnen.setAttribute('aria-label', s.quelleName + ' ' + (lokal ? 'im Finder öffnen' : 'im Browser öffnen'));
|
|
1415
|
+
oeffnen.appendChild(externSvg());
|
|
1416
|
+
oeffnen.addEventListener('click', function (ev) {
|
|
1417
|
+
ev.preventDefault();
|
|
1418
|
+
ev.stopPropagation();
|
|
1419
|
+
window.hmgr.quelleOeffnen(quelle.text).catch(function () {});
|
|
1420
|
+
});
|
|
1421
|
+
gruppe.appendChild(oeffnen);
|
|
1422
|
+
box.appendChild(gruppe);
|
|
1423
|
+
} else {
|
|
1424
|
+
box.appendChild(chip);
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function setzeQuellenfilter(wert) {
|
|
1430
|
+
quellenFilter = wert;
|
|
1431
|
+
document.querySelectorAll('#quellenfilter .chip').forEach(function (c) {
|
|
1432
|
+
c.setAttribute('aria-pressed', c.dataset.filter === wert ? 'true' : 'false');
|
|
1433
|
+
});
|
|
1434
|
+
filtereSkills();
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/** Immer sichtbare Marken einer Skill-Zeile aus dem Frontmatter (E55), nur wenn zutreffend.
|
|
1438
|
+
Hooks und vorab erlaubte Werkzeuge sind sicherheitsrelevant und deshalb warnend gefärbt (F11). */
|
|
1439
|
+
function skillMarken(skill) {
|
|
1440
|
+
var marken = [];
|
|
1441
|
+
if (skill.nurPerBefehl) marken.push({ text: 'nur per /' + skill.name });
|
|
1442
|
+
if (skill.nichtPerBefehl) marken.push({ text: 'nicht per /' + skill.name });
|
|
1443
|
+
if (skill.registriertHooks) marken.push({ text: 'registriert Hooks', warnend: true });
|
|
1444
|
+
if (skill.werkzeugeErlaubt) marken.push({ text: 'Werkzeuge vorab erlaubt', warnend: true });
|
|
1445
|
+
return marken;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/** Die Marken eines Skills als fertige Box, für Katalog und Skills-hinzufügen-Dialog (F13).
|
|
1449
|
+
Ohne Besonderheiten null, dann bleibt die Zeile unverändert. */
|
|
1450
|
+
function skillMarkenBox(skill) {
|
|
1451
|
+
var marken = skillMarken(skill);
|
|
1452
|
+
if (marken.length === 0) return null;
|
|
1453
|
+
var box = e('span', 'marken');
|
|
1454
|
+
marken.forEach(function (m) { box.appendChild(e('span', 'merker leise' + (m.warnend ? ' warnend' : ''), m.text)); });
|
|
1455
|
+
return box;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/** Zeilen des Aufklappbereichs einer Skill-Zeile aus dem Frontmatter (E55), nur wenn vorhanden. */
|
|
1459
|
+
function skillDetails(skill) {
|
|
1460
|
+
var zeilen = [];
|
|
1461
|
+
if (skill.kompatibilitaet) zeilen.push({ label: 'Kompatibilität', text: skill.kompatibilitaet });
|
|
1462
|
+
if (skill.modell) zeilen.push({ label: 'Modell', text: skill.modell });
|
|
1463
|
+
if (skill.eigenerKontext) zeilen.push({ label: 'Kontext', text: 'läuft in eigenem Kontext' });
|
|
1464
|
+
if (skill.argumentHinweis) zeilen.push({ label: 'Argumente', text: skill.argumentHinweis });
|
|
1465
|
+
return zeilen;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
function baueSkills() {
|
|
1469
|
+
var liste = $('katalog');
|
|
1470
|
+
liste.textContent = '';
|
|
1471
|
+
SKILLS.forEach(function (skill, i) {
|
|
1472
|
+
var li = e('li', 'zeile');
|
|
1473
|
+
li.dataset.index = i;
|
|
1474
|
+
var idx = quellenIndex(skill.quelleText);
|
|
1475
|
+
li.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1476
|
+
|
|
1477
|
+
var links = e('div');
|
|
1478
|
+
var titel = e('h3');
|
|
1479
|
+
var herkunftQuelle = QUELLEN.filter(function (q) { return q.text === skill.quelleText; })[0];
|
|
1480
|
+
// Quellen-Icon vor dem Namen, wie in den Ziel-Tabellen (E60). Ohne bekannte Quelle generisch.
|
|
1481
|
+
titel.appendChild(repoIcon(herkunftQuelle || { art: 'lokal', pfad: skill.quelleText }));
|
|
1482
|
+
titel.appendChild(document.createTextNode(skill.name));
|
|
1483
|
+
var markenBox = skillMarkenBox(skill);
|
|
1484
|
+
if (markenBox) titel.appendChild(markenBox);
|
|
1485
|
+
links.appendChild(titel);
|
|
1486
|
+
links.appendChild(e('p', 'beschreibung', skill.beschreibung || ''));
|
|
1487
|
+
// Herkunft: der Name der Quelle, anklickbar wie überall sonst (E60), die Adresse steht im
|
|
1488
|
+
// title. Fehlt die Quelle in der Liste (Katalog und Quellen kämen auseinander), bleibt es
|
|
1489
|
+
// beim reinen Text — angeschrieben ist die Zeile so oder so.
|
|
1490
|
+
var herkunft = e('p', 'herkunft mono');
|
|
1491
|
+
if (herkunftQuelle) {
|
|
1492
|
+
herkunft.appendChild(quelleLinkElement(herkunftQuelle));
|
|
1493
|
+
herkunft.appendChild(e('b', null, ' · Ref ' + skill.ref));
|
|
1494
|
+
} else {
|
|
1495
|
+
herkunft.title = skill.quelleText;
|
|
1496
|
+
herkunft.appendChild(e('b', null, skill.quelleName + ' · Ref ' + skill.ref));
|
|
1497
|
+
}
|
|
1498
|
+
links.appendChild(herkunft);
|
|
1499
|
+
|
|
1500
|
+
var rechts = e('div', 'rechts');
|
|
1501
|
+
var details = skillDetails(skill);
|
|
1502
|
+
var umschalter = null;
|
|
1503
|
+
var koerperId = 'skillkoerper-' + i;
|
|
1504
|
+
// Der Aufklappzustand überlebt den Neuaufbau durch ladeAlles (F10), wie ZIEL_OFFEN.
|
|
1505
|
+
var offen = !!SKILL_OFFEN[skillKey(skill)];
|
|
1506
|
+
if (details.length > 0) {
|
|
1507
|
+
umschalter = e('button', 'aufklappknopf');
|
|
1508
|
+
umschalter.type = 'button';
|
|
1509
|
+
umschalter.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
1510
|
+
umschalter.setAttribute('aria-controls', koerperId);
|
|
1511
|
+
umschalter.setAttribute('aria-label', 'Details zu ' + skill.name + ' ein- oder ausklappen');
|
|
1512
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1513
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
1514
|
+
pfeil.setAttribute('class', 'akkordeonpfeil dunkel');
|
|
1515
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
1516
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
1517
|
+
umschalter.appendChild(pfeil);
|
|
1518
|
+
rechts.appendChild(umschalter);
|
|
1519
|
+
}
|
|
1520
|
+
var knopf = e('button', 'knopf leise', 'Verlinken');
|
|
1521
|
+
knopf.type = 'button';
|
|
1522
|
+
knopf.addEventListener('click', function () { starteVerlinken(i); });
|
|
1523
|
+
rechts.appendChild(knopf);
|
|
1524
|
+
|
|
1525
|
+
li.appendChild(links);
|
|
1526
|
+
li.appendChild(rechts);
|
|
1527
|
+
|
|
1528
|
+
if (details.length > 0 && umschalter) {
|
|
1529
|
+
var koerper = e('div', 'skillkoerper');
|
|
1530
|
+
koerper.id = koerperId;
|
|
1531
|
+
koerper.hidden = !offen;
|
|
1532
|
+
details.forEach(function (d) {
|
|
1533
|
+
var zeile = e('p', 'skilldetail');
|
|
1534
|
+
zeile.appendChild(e('b', null, d.label + ': '));
|
|
1535
|
+
zeile.appendChild(document.createTextNode(d.text));
|
|
1536
|
+
koerper.appendChild(zeile);
|
|
1537
|
+
});
|
|
1538
|
+
li.appendChild(koerper);
|
|
1539
|
+
umschalter.addEventListener('click', function () {
|
|
1540
|
+
var war = umschalter.getAttribute('aria-expanded') === 'true';
|
|
1541
|
+
umschalter.setAttribute('aria-expanded', war ? 'false' : 'true');
|
|
1542
|
+
koerper.hidden = war;
|
|
1543
|
+
SKILL_OFFEN[skillKey(skill)] = !war;
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
liste.appendChild(li);
|
|
1548
|
+
});
|
|
1549
|
+
filtereSkills();
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function filtereSkills() {
|
|
1553
|
+
var suche = $('suchfeld').value.trim().toLowerCase();
|
|
1554
|
+
var sichtbar = 0;
|
|
1555
|
+
document.querySelectorAll('#katalog .zeile').forEach(function (li) {
|
|
1556
|
+
var skill = SKILLS[+li.dataset.index];
|
|
1557
|
+
var passtSuche = !suche || skill.name.toLowerCase().indexOf(suche) > -1 || skill.pfad.toLowerCase().indexOf(suche) > -1;
|
|
1558
|
+
var passtFilter = quellenFilter === 'alle' || skill.quelleSchluessel === quellenFilter;
|
|
1559
|
+
var an = passtSuche && passtFilter;
|
|
1560
|
+
li.hidden = !an;
|
|
1561
|
+
if (an) sichtbar++;
|
|
1562
|
+
});
|
|
1563
|
+
$('katalogzahl').textContent = sichtbar + ' von ' + SKILLS.length + ' Skills gezeigt';
|
|
1564
|
+
}
|
|
1565
|
+
$('suchfeld').addEventListener('input', filtereSkills);
|
|
1566
|
+
|
|
1567
|
+
var laufendeSkill = null;
|
|
1568
|
+
function starteVerlinken(index) {
|
|
1569
|
+
laufendeSkill = index;
|
|
1570
|
+
var skill = SKILLS[index];
|
|
1571
|
+
$('dz-skill').textContent = skill.name;
|
|
1572
|
+
var box = $('dz-ziele');
|
|
1573
|
+
box.textContent = '';
|
|
1574
|
+
ZIELE.forEach(function (z) {
|
|
1575
|
+
var schon = z.eintraege.some(function (ei) {
|
|
1576
|
+
var n = ei.art === 'link' ? ei.link.name : ei.name;
|
|
1577
|
+
return n === skill.name;
|
|
1578
|
+
});
|
|
1579
|
+
var label = e('label', 'wahlzeile');
|
|
1580
|
+
var input = document.createElement('input');
|
|
1581
|
+
input.type = 'checkbox';
|
|
1582
|
+
input.value = String(ZIELE.indexOf(z));
|
|
1583
|
+
input.disabled = schon;
|
|
1584
|
+
label.appendChild(input);
|
|
1585
|
+
var txt = e('span');
|
|
1586
|
+
txt.appendChild(beschriftet(e('b', null, zielTitel(z)), z.ziel.harness));
|
|
1587
|
+
txt.appendChild(e('span', 'mono', pfadKurz(z.verzeichnis)));
|
|
1588
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
1589
|
+
label.appendChild(txt);
|
|
1590
|
+
box.appendChild(label);
|
|
1591
|
+
});
|
|
1592
|
+
zeige($('d-ziel'));
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
$('d-ziel').addEventListener('submit', function (ev) {
|
|
1596
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
1597
|
+
var gewaehlt = Array.from($('dz-ziele').querySelectorAll('input:checked'));
|
|
1598
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
nachDialog($('d-ziel'), async function (wert) {
|
|
1603
|
+
if (wert !== 'los' || laufendeSkill === null) return;
|
|
1604
|
+
var skill = SKILLS[laufendeSkill];
|
|
1605
|
+
var indizes = Array.from($('dz-ziele').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
1606
|
+
var ergebnisZeilen = [];
|
|
1607
|
+
var hinweise = [];
|
|
1608
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
1609
|
+
var z = ZIELE[indizes[n]];
|
|
1610
|
+
try {
|
|
1611
|
+
var gelegt = await rufe(function () {
|
|
1612
|
+
return window.hmgr.linkLegen({ skill: skill.name, harness: z.ziel.harness, ort: z.ziel.ort, quelle: skill.quelleText });
|
|
1613
|
+
});
|
|
1614
|
+
hinweise.push(gelegt.hinweis);
|
|
1615
|
+
ergebnisZeilen.push({ ok: true, ziel: z });
|
|
1616
|
+
} catch (fehler) {
|
|
1617
|
+
ergebnisZeilen.push({ ok: false, ziel: z, fehler: fehler.message });
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
await ladeAlles();
|
|
1621
|
+
$('de-titel').textContent = 'Verlinkt';
|
|
1622
|
+
var erfolge = ergebnisZeilen.filter(function (r) { return r.ok; }).length;
|
|
1623
|
+
$('de-text').textContent = skill.name + ' hängt jetzt in ' + erfolge + (erfolge === 1 ? ' Ziel.' : ' Zielen.');
|
|
1624
|
+
setzeErfolgHinweise(hinweise);
|
|
1625
|
+
var ul = $('de-liste');
|
|
1626
|
+
ul.textContent = '';
|
|
1627
|
+
ergebnisZeilen.forEach(function (r) {
|
|
1628
|
+
var li = e('li');
|
|
1629
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1630
|
+
var s = e('span');
|
|
1631
|
+
s.appendChild(beschriftet(e('b', null, zielTitel(r.ziel)), r.ziel.ziel.harness));
|
|
1632
|
+
s.appendChild(e('span', 'mono', r.ok ? pfadKurz(r.ziel.verzeichnis) + '/' + skill.name : r.fehler));
|
|
1633
|
+
li.appendChild(s);
|
|
1634
|
+
ul.appendChild(li);
|
|
1635
|
+
});
|
|
1636
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
/* E47: Gegenrichtung zu starteVerlinken/d-ziel. Mehrfachauswahl über den ganzen Katalog, schon
|
|
1640
|
+
hier hängende Skills (gleicher Name, egal welche Art) sind ausgegraut. E53 erweitert das um
|
|
1641
|
+
einen zweiten Modus für den Chip „Alle" einer Verzeichnis-Karte: modus „alle" grauet einen
|
|
1642
|
+
Skill nur aus, wenn er in ALLEN beteiligten Harnesses hängt, und verlinkt je Harness, wo er fehlt. */
|
|
1643
|
+
var skillsKontext = null;
|
|
1644
|
+
function dskBilanzAktualisieren() {
|
|
1645
|
+
var n = $('dsk-liste').querySelectorAll('input:checked').length;
|
|
1646
|
+
$('dsk-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
1647
|
+
}
|
|
1648
|
+
function filtereDskListe() {
|
|
1649
|
+
var suche = $('dsk-suche').value.trim().toLowerCase();
|
|
1650
|
+
document.querySelectorAll('#dsk-liste .wahlzeile').forEach(function (label) {
|
|
1651
|
+
var skill = SKILLS[+label.dataset.index];
|
|
1652
|
+
label.hidden = !!suche && skill.name.toLowerCase().indexOf(suche) === -1;
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
$('dsk-suche').addEventListener('input', filtereDskListe);
|
|
1656
|
+
|
|
1657
|
+
function oeffneSkillsHinzufuegen(z) {
|
|
1658
|
+
skillsKontext = { modus: 'einzeln', z: z };
|
|
1659
|
+
$('dsk-ziel').textContent = zielTitel(z);
|
|
1660
|
+
$('dsk-suche').value = '';
|
|
1661
|
+
var box = $('dsk-liste');
|
|
1662
|
+
box.textContent = '';
|
|
1663
|
+
var vorhandeneNamen = z.eintraege.map(function (ei) { return ei.art === 'link' ? ei.link.name : ei.name; });
|
|
1664
|
+
SKILLS.forEach(function (skill, i) {
|
|
1665
|
+
var schon = vorhandeneNamen.indexOf(skill.name) > -1;
|
|
1666
|
+
var label = e('label', 'wahlzeile quellfarbe');
|
|
1667
|
+
label.dataset.index = i;
|
|
1668
|
+
var idx = quellenIndex(skill.quelleText);
|
|
1669
|
+
label.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1670
|
+
var input = document.createElement('input');
|
|
1671
|
+
input.type = 'checkbox';
|
|
1672
|
+
input.value = String(i);
|
|
1673
|
+
input.disabled = schon;
|
|
1674
|
+
input.addEventListener('change', dskBilanzAktualisieren);
|
|
1675
|
+
label.appendChild(input);
|
|
1676
|
+
var txt = e('span');
|
|
1677
|
+
txt.appendChild(e('b', null, skill.name));
|
|
1678
|
+
// Dieselben Marken wie im Katalog (F13): hier fällt die Entscheidung, was installiert wird.
|
|
1679
|
+
var marken = skillMarkenBox(skill);
|
|
1680
|
+
if (marken) txt.appendChild(marken);
|
|
1681
|
+
var quelleSpanEl = e('span', 'mono', skill.quelleName + ' · Ref ' + skill.ref);
|
|
1682
|
+
quelleSpanEl.title = skill.quelleText;
|
|
1683
|
+
txt.appendChild(quelleSpanEl);
|
|
1684
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
1685
|
+
label.appendChild(txt);
|
|
1686
|
+
box.appendChild(label);
|
|
1687
|
+
});
|
|
1688
|
+
dskBilanzAktualisieren();
|
|
1689
|
+
zeige($('d-skills'));
|
|
1690
|
+
$('dsk-suche').focus();
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/** Skills-hinzufügen-Dialog für den Chip „Alle" (E53 Pkt. 4): ausgegraut nur, wenn ein Skill in
|
|
1694
|
+
allen beteiligten Harnesses schon hängt. */
|
|
1695
|
+
function oeffneSkillsHinzufuegenAlle(pfad, teile) {
|
|
1696
|
+
skillsKontext = { modus: 'alle', pfad: pfad, teile: teile };
|
|
1697
|
+
$('dsk-ziel').textContent = pfadKurz(pfad) + ' (alle Harnesses)';
|
|
1698
|
+
$('dsk-suche').value = '';
|
|
1699
|
+
var box = $('dsk-liste');
|
|
1700
|
+
box.textContent = '';
|
|
1701
|
+
var vorhandeneJeHarness = teile.map(function (z) {
|
|
1702
|
+
return z.eintraege.map(function (ei) { return ei.art === 'link' ? ei.link.name : ei.name; });
|
|
1703
|
+
});
|
|
1704
|
+
SKILLS.forEach(function (skill, i) {
|
|
1705
|
+
var fehltIn = teile.filter(function (z, n) { return vorhandeneJeHarness[n].indexOf(skill.name) === -1; });
|
|
1706
|
+
var ueberall = fehltIn.length === 0;
|
|
1707
|
+
var label = e('label', 'wahlzeile quellfarbe');
|
|
1708
|
+
label.dataset.index = i;
|
|
1709
|
+
var idx = quellenIndex(skill.quelleText);
|
|
1710
|
+
label.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1711
|
+
var input = document.createElement('input');
|
|
1712
|
+
input.type = 'checkbox';
|
|
1713
|
+
input.value = String(i);
|
|
1714
|
+
input.disabled = ueberall;
|
|
1715
|
+
input.addEventListener('change', dskBilanzAktualisieren);
|
|
1716
|
+
label.appendChild(input);
|
|
1717
|
+
var txt = e('span');
|
|
1718
|
+
txt.appendChild(e('b', null, skill.name));
|
|
1719
|
+
// Dieselben Marken wie im Katalog (F13): hier fällt die Entscheidung, was installiert wird.
|
|
1720
|
+
var marken = skillMarkenBox(skill);
|
|
1721
|
+
if (marken) txt.appendChild(marken);
|
|
1722
|
+
var quelleSpanEl = e('span', 'mono', skill.quelleName + ' · Ref ' + skill.ref);
|
|
1723
|
+
quelleSpanEl.title = skill.quelleText;
|
|
1724
|
+
txt.appendChild(quelleSpanEl);
|
|
1725
|
+
if (ueberall) txt.appendChild(e('span', 'notiz', 'Hängt schon überall.'));
|
|
1726
|
+
else if (fehltIn.length < teile.length) {
|
|
1727
|
+
txt.appendChild(e('span', 'notiz', 'Hängt schon in ' + (teile.length - fehltIn.length) + ' von ' + teile.length + ' Harnesses.'));
|
|
1728
|
+
}
|
|
1729
|
+
label.appendChild(txt);
|
|
1730
|
+
box.appendChild(label);
|
|
1731
|
+
});
|
|
1732
|
+
dskBilanzAktualisieren();
|
|
1733
|
+
zeige($('d-skills'));
|
|
1734
|
+
$('dsk-suche').focus();
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
$('d-skills').addEventListener('submit', function (ev) {
|
|
1738
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
1739
|
+
var gewaehlt = Array.from($('dsk-liste').querySelectorAll('input:checked'));
|
|
1740
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
nachDialog($('d-skills'), async function (wert) {
|
|
1745
|
+
if (wert !== 'los' || skillsKontext === null) return;
|
|
1746
|
+
var kontext = skillsKontext;
|
|
1747
|
+
var indizes = Array.from($('dsk-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
1748
|
+
|
|
1749
|
+
if (kontext.modus === 'einzeln') {
|
|
1750
|
+
var z = kontext.z;
|
|
1751
|
+
var ergebnisZeilen = [];
|
|
1752
|
+
// Zwei Skills gleichen Namens aus verschiedenen Quellen stehen beide im Katalog (E32), am Ziel
|
|
1753
|
+
// gilt der Name aber nur einmal (E12), und der Kern ersetzte den eben gelegten Link stumm.
|
|
1754
|
+
// Der zweite gehört daher als Fehler in die Bilanz, sonst meldet der Dialog Links, die es nicht gibt.
|
|
1755
|
+
var vergeben = [];
|
|
1756
|
+
var hinweiseZiel = [];
|
|
1757
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
1758
|
+
var skill = SKILLS[indizes[n]];
|
|
1759
|
+
if (vergeben.indexOf(skill.name) > -1) {
|
|
1760
|
+
ergebnisZeilen.push({
|
|
1761
|
+
ok: false,
|
|
1762
|
+
skill: skill,
|
|
1763
|
+
fehler: 'Ein Skill dieses Namens ist gerade verlinkt worden, hier gibt es den Namen nur einmal.',
|
|
1764
|
+
});
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
try {
|
|
1768
|
+
var gelegtZiel = await rufe(function () {
|
|
1769
|
+
return window.hmgr.linkLegen({ skill: skill.name, harness: z.ziel.harness, ort: z.ziel.ort, quelle: skill.quelleText });
|
|
1770
|
+
});
|
|
1771
|
+
hinweiseZiel.push(gelegtZiel.hinweis);
|
|
1772
|
+
vergeben.push(skill.name);
|
|
1773
|
+
ergebnisZeilen.push({ ok: true, skill: skill });
|
|
1774
|
+
} catch (fehler) {
|
|
1775
|
+
ergebnisZeilen.push({ ok: false, skill: skill, fehler: fehler.message });
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
// Nach dem Verlinken bleibt die Karte offen, in der das Ziel steckt: bei einem
|
|
1779
|
+
// Arbeitsverzeichnis ist das die gebündelte Verzeichnis-Karte, nicht das Ziel allein (E49).
|
|
1780
|
+
ZIEL_OFFEN[kartenKey(z.ziel)] = true;
|
|
1781
|
+
await ladeAlles();
|
|
1782
|
+
var erfolge = ergebnisZeilen.filter(function (r) { return r.ok; }).length;
|
|
1783
|
+
$('de-titel').textContent = 'Verlinkt';
|
|
1784
|
+
$('de-text').textContent = zahlwort(erfolge, 'Skill hängt', 'Skills hängen') + ' jetzt in ' + zielTitel(z) + '.';
|
|
1785
|
+
setzeErfolgHinweise(hinweiseZiel);
|
|
1786
|
+
var ul = $('de-liste');
|
|
1787
|
+
ul.textContent = '';
|
|
1788
|
+
ergebnisZeilen.forEach(function (r) {
|
|
1789
|
+
var li = e('li');
|
|
1790
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1791
|
+
var s = e('span');
|
|
1792
|
+
s.appendChild(e('b', null, r.skill.name));
|
|
1793
|
+
if (!r.ok) s.appendChild(e('span', 'mono', r.fehler));
|
|
1794
|
+
li.appendChild(s);
|
|
1795
|
+
ul.appendChild(li);
|
|
1796
|
+
});
|
|
1797
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// modus „alle" (E53 Pkt. 4): je gewähltem Skill in jedem Harness verlinken, wo er noch fehlt.
|
|
1802
|
+
// Erfolgsliste je Harness eine Zeile, schon vorhandene Harnesses bleiben ohne Zeile.
|
|
1803
|
+
var pfad = kontext.pfad;
|
|
1804
|
+
var teile = kontext.teile;
|
|
1805
|
+
var zeilenErgebnis = [];
|
|
1806
|
+
// Der Duplikat-Schutz aus dem Einzel-Modus gilt hier je Harness, nicht über die Auswahl hinweg:
|
|
1807
|
+
// derselbe Skill gehört absichtlich in mehrere Harnesses, zwei gleichnamige Skills aus
|
|
1808
|
+
// verschiedenen Quellen (E32) gehören aber in keinem Harness zweimal (E12), der Kern ersetzte
|
|
1809
|
+
// den eben gelegten Link sonst stumm.
|
|
1810
|
+
var vergebenJeHarness = {};
|
|
1811
|
+
var hinweiseAlle = [];
|
|
1812
|
+
for (var m = 0; m < indizes.length; m++) {
|
|
1813
|
+
var sk = SKILLS[indizes[m]];
|
|
1814
|
+
for (var t = 0; t < teile.length; t++) {
|
|
1815
|
+
var zt = teile[t];
|
|
1816
|
+
var vorhandene = zt.eintraege.map(function (ei) { return ei.art === 'link' ? ei.link.name : ei.name; });
|
|
1817
|
+
if (vorhandene.indexOf(sk.name) > -1) continue;
|
|
1818
|
+
var vergebenKey = zt.ziel.harness + '\u0000' + sk.name;
|
|
1819
|
+
if (vergebenJeHarness[vergebenKey]) {
|
|
1820
|
+
zeilenErgebnis.push({
|
|
1821
|
+
ok: false,
|
|
1822
|
+
skill: sk,
|
|
1823
|
+
harness: zt.ziel.harness,
|
|
1824
|
+
fehler: 'Ein Skill dieses Namens ist gerade verlinkt worden, hier gibt es den Namen nur einmal.',
|
|
1825
|
+
});
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1828
|
+
try {
|
|
1829
|
+
var gelegtAlle = await rufe(function () {
|
|
1830
|
+
return window.hmgr.linkLegen({ skill: sk.name, harness: zt.ziel.harness, ort: zt.ziel.ort, quelle: sk.quelleText });
|
|
1831
|
+
});
|
|
1832
|
+
hinweiseAlle.push(gelegtAlle.hinweis);
|
|
1833
|
+
vergebenJeHarness[vergebenKey] = true;
|
|
1834
|
+
zeilenErgebnis.push({ ok: true, skill: sk, harness: zt.ziel.harness });
|
|
1835
|
+
} catch (fehler) {
|
|
1836
|
+
zeilenErgebnis.push({ ok: false, skill: sk, harness: zt.ziel.harness, fehler: fehler.message });
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
VERZ_CHIP[verzKey(pfad)] = 'alle';
|
|
1841
|
+
ZIEL_OFFEN[verzKey(pfad)] = true;
|
|
1842
|
+
await ladeAlles();
|
|
1843
|
+
var erfolgeAlle = zeilenErgebnis.filter(function (r) { return r.ok; }).length;
|
|
1844
|
+
$('de-titel').textContent = 'Verlinkt';
|
|
1845
|
+
$('de-text').textContent = zahlwort(erfolgeAlle, 'Link angelegt', 'Links angelegt') + ' in ' + pfadKurz(pfad) + '.';
|
|
1846
|
+
setzeErfolgHinweise(hinweiseAlle);
|
|
1847
|
+
var ul2 = $('de-liste');
|
|
1848
|
+
ul2.textContent = '';
|
|
1849
|
+
zeilenErgebnis.forEach(function (r) {
|
|
1850
|
+
var li = e('li');
|
|
1851
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1852
|
+
var s = e('span');
|
|
1853
|
+
// Derselbe Skill steht hier je Harness einmal. Der Harness gehört als Text dazu, das Zeichen
|
|
1854
|
+
// allein trägt keine Beschriftung und die Zeilen wären sonst nicht zu unterscheiden.
|
|
1855
|
+
s.appendChild(beschriftet(e('b', null, r.skill.name), r.harness));
|
|
1856
|
+
s.appendChild(e('span', 'mono', harnessName(r.harness) + (r.ok ? '' : ' · ' + r.fehler)));
|
|
1857
|
+
li.appendChild(s);
|
|
1858
|
+
ul2.appendChild(li);
|
|
1859
|
+
});
|
|
1860
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
/* ================= AKTUALISIEREN ================= */
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* Ein Aktualisieren-Lauf, von Hand über den Knopf oder selbsttätig angestoßen (E44). Eine Sperre
|
|
1867
|
+
* über den disabled-Zustand des Knopfs verhindert, dass ein Anstoß während eines laufenden Laufs
|
|
1868
|
+
* etwas tut. Automatische Läufe rufen den Kern ohne Rückfrage, sie räumen also nichts weg.
|
|
1869
|
+
*/
|
|
1870
|
+
/* Stand-Zeile der Seitenleiste (Entwurf): hervorgehoben „N Quellen mit Neuem" nach einem
|
|
1871
|
+
automatischen Lauf mit Änderungen, bis die Update-Ansicht den Bericht zeigt; sonst ruhig
|
|
1872
|
+
„Alles aktuell, HH:MM geprüft", oder bei Fehlschlägen (E51) „Prüfung fehlgeschlagen, HH:MM"
|
|
1873
|
+
(alle Quellen-Abrufe gescheitert) bzw. „Geprüft mit Fehlern, HH:MM" (Mischung). Das „mit
|
|
1874
|
+
Neuem"-Signal hat Vorrang: es kündigt sich sofort nach einem automatischen Lauf an, die
|
|
1875
|
+
Fehlerkennzeichnung gilt erst wieder im ruhigen Zustand danach. */
|
|
1876
|
+
var standZeit = null;
|
|
1877
|
+
var standFehlerZustand = 'keine';
|
|
1878
|
+
function standRuhig() {
|
|
1879
|
+
var text = 'Alles aktuell, ' + standZeit + ' geprüft';
|
|
1880
|
+
if (standZeit === null) text = 'Noch nicht aktualisiert';
|
|
1881
|
+
else if (standFehlerZustand === 'alle') text = 'Prüfung fehlgeschlagen, ' + standZeit;
|
|
1882
|
+
else if (standFehlerZustand === 'gemischt') text = 'Geprüft mit Fehlern, ' + standZeit;
|
|
1883
|
+
$('standtext').textContent = text;
|
|
1884
|
+
$('standknopf').classList.remove('neu');
|
|
1885
|
+
$('standknopf').textContent = 'Prüfen';
|
|
1886
|
+
}
|
|
1887
|
+
function standNachLauf(ergebnis, automatisch, uhrzeit) {
|
|
1888
|
+
standZeit = uhrzeit;
|
|
1889
|
+
var quellen = {};
|
|
1890
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
1891
|
+
if (c.aktualisierung.ergebnis === 'aktualisiert') quellen[quelleKurz(c.checkout.quelle)] = true;
|
|
1892
|
+
});
|
|
1893
|
+
var neue = Object.keys(quellen).length;
|
|
1894
|
+
var fehlgeschlagen = ergebnis.checkouts.filter(function (c) { return c.aktualisierung.ergebnis === 'fehlgeschlagen'; }).length;
|
|
1895
|
+
var gesamt = ergebnis.checkouts.length;
|
|
1896
|
+
standFehlerZustand = fehlgeschlagen === 0 ? 'keine' : fehlgeschlagen === gesamt ? 'alle' : 'gemischt';
|
|
1897
|
+
// Von Hand läuft es in der Update-Ansicht, der Bericht ist schon sichtbar: nichts anzukündigen.
|
|
1898
|
+
if (neue === 0 || !automatisch || !$('ansicht-update').hidden) return standRuhig();
|
|
1899
|
+
var p = $('standtext');
|
|
1900
|
+
p.textContent = '';
|
|
1901
|
+
p.appendChild(e('b', null, zahlwort(neue, 'Quelle', 'Quellen')));
|
|
1902
|
+
p.appendChild(document.createTextNode(' mit Neuem'));
|
|
1903
|
+
$('standknopf').classList.add('neu');
|
|
1904
|
+
$('standknopf').textContent = 'Ansehen';
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
async function aktualisierenLauf(automatisch) {
|
|
1908
|
+
var knopf = $('updatestart');
|
|
1909
|
+
if (knopf.disabled) return;
|
|
1910
|
+
knopf.disabled = true;
|
|
1911
|
+
knopf.textContent = 'Läuft …';
|
|
1912
|
+
$('standtext').textContent = 'Prüft …';
|
|
1913
|
+
try {
|
|
1914
|
+
var ergebnis = await rufe(function () {
|
|
1915
|
+
return automatisch ? window.hmgr.aktualisieren({ rueckfragen: false }) : window.hmgr.aktualisieren();
|
|
1916
|
+
});
|
|
1917
|
+
baueBericht(ergebnis);
|
|
1918
|
+
var uhrzeit = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
|
1919
|
+
var ausloeser = automatisch ? 'automatisch' : 'von Hand';
|
|
1920
|
+
$('updatestand').textContent = 'Zuletzt ' + uhrzeit + ', ' + ausloeser + ', ' + ergebnis.checkouts.length + ' Checkouts.';
|
|
1921
|
+
standNachLauf(ergebnis, automatisch, uhrzeit);
|
|
1922
|
+
await ladeAlles();
|
|
1923
|
+
} finally {
|
|
1924
|
+
knopf.disabled = false;
|
|
1925
|
+
knopf.textContent = 'Jetzt aktualisieren';
|
|
1926
|
+
// Scheiterte der Lauf, bliebe sonst „Prüft …" stehen.
|
|
1927
|
+
if ($('standtext').textContent === 'Prüft …') standRuhig();
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
$('updatestart').addEventListener('click', function () { aktualisierenLauf(false); });
|
|
1932
|
+
// Der Standknopf sagt, was ein Klick tut: ruhig „Prüfen" (Ansicht öffnen und Lauf starten),
|
|
1933
|
+
// mit Neuem „Ansehen" (nur den Bericht zeigen, der Lauf ist schon gelaufen). Die Klasse muss
|
|
1934
|
+
// vor zeigeAnsicht gelesen werden, denn die räumt den neu-Zustand ab.
|
|
1935
|
+
$('standknopf').addEventListener('click', function () {
|
|
1936
|
+
var neu = this.classList.contains('neu');
|
|
1937
|
+
zeigeAnsicht('update');
|
|
1938
|
+
if (!neu) aktualisierenLauf(false);
|
|
1939
|
+
});
|
|
1940
|
+
window.hmgr.onAktualisierenAnstossen(function () { aktualisierenLauf(true); });
|
|
1941
|
+
|
|
1942
|
+
function baueBericht(ergebnis) {
|
|
1943
|
+
var box = $('bericht');
|
|
1944
|
+
box.textContent = '';
|
|
1945
|
+
box.hidden = false;
|
|
1946
|
+
|
|
1947
|
+
var checkoutBlock = e('div', 'block');
|
|
1948
|
+
checkoutBlock.appendChild(e('span', 'etikett', 'Checkouts'));
|
|
1949
|
+
var cul = e('ul');
|
|
1950
|
+
if (ergebnis.checkouts.length === 0) cul.appendChild(e('li', null, 'Keine Checkouts.'));
|
|
1951
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
1952
|
+
var li = e('li');
|
|
1953
|
+
var a = c.aktualisierung;
|
|
1954
|
+
var art = a.ergebnis === 'aktualisiert' || a.ergebnis === 'unveraendert' ? 'ok' : 'aus';
|
|
1955
|
+
var text = a.ergebnis === 'aktualisiert' ? 'aktualisiert'
|
|
1956
|
+
: a.ergebnis === 'unveraendert' ? 'unverändert'
|
|
1957
|
+
: a.ergebnis === 'fehlgeschlagen' ? 'fehlgeschlagen (' + a.fehler + ')'
|
|
1958
|
+
: 'übersprungen (lokale Änderung)';
|
|
1959
|
+
li.appendChild(e('span', 'marke-' + art, text));
|
|
1960
|
+
li.appendChild(quelleSpan('pfad', c.checkout.quelle, ' @ ' + c.checkout.ref));
|
|
1961
|
+
cul.appendChild(li);
|
|
1962
|
+
});
|
|
1963
|
+
checkoutBlock.appendChild(cul);
|
|
1964
|
+
box.appendChild(checkoutBlock);
|
|
1965
|
+
|
|
1966
|
+
if (ergebnis.pluginHarnesses.length > 0) {
|
|
1967
|
+
var pluginBlock = e('div', 'block');
|
|
1968
|
+
pluginBlock.appendChild(e('span', 'etikett', 'Plugins'));
|
|
1969
|
+
var pul = e('ul');
|
|
1970
|
+
ergebnis.pluginHarnesses.forEach(function (name) {
|
|
1971
|
+
var li = e('li');
|
|
1972
|
+
li.appendChild(e('span', 'marke-ok', 'aktualisiert'));
|
|
1973
|
+
li.appendChild(e('span', 'pfad', name));
|
|
1974
|
+
pul.appendChild(li);
|
|
1975
|
+
});
|
|
1976
|
+
pluginBlock.appendChild(pul);
|
|
1977
|
+
box.appendChild(pluginBlock);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
if (ergebnis.aufraeumen.verwaiste.length > 0) {
|
|
1981
|
+
var raeumBlock = e('div', 'block entfernt');
|
|
1982
|
+
raeumBlock.appendChild(e('span', 'etikett', 'Verwaiste Checkouts'));
|
|
1983
|
+
var rul = e('ul');
|
|
1984
|
+
ergebnis.aufraeumen.verwaiste.forEach(function (v) {
|
|
1985
|
+
var li = e('li');
|
|
1986
|
+
var text = v.geloescht ? 'gelöscht' : v.lokaleAenderung ? 'liegen gelassen (lokale Änderung)' : 'liegen gelassen';
|
|
1987
|
+
li.appendChild(e('span', v.geloescht ? 'marke-aus' : 'marke-aus', text));
|
|
1988
|
+
li.appendChild(quelleSpan('pfad', v.checkout.quelle, ' @ ' + v.checkout.ref));
|
|
1989
|
+
rul.appendChild(li);
|
|
1990
|
+
});
|
|
1991
|
+
raeumBlock.appendChild(rul);
|
|
1992
|
+
box.appendChild(raeumBlock);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/* ================= PLUGINS ================= */
|
|
1997
|
+
|
|
1998
|
+
function baueMarketplaces() {
|
|
1999
|
+
var box = $('pluginliste');
|
|
2000
|
+
box.textContent = '';
|
|
2001
|
+
if (MARKETPLACES.length === 0) {
|
|
2002
|
+
box.appendChild(e('p', 'leerhinweis', 'Keine Quelle mit marketplace.json bekannt.'));
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
MARKETPLACES.forEach(function (m) {
|
|
2006
|
+
var markt = e('div', 'markt');
|
|
2007
|
+
var kopf = e('div', 'marktkopf');
|
|
2008
|
+
kopf.appendChild(e('h3', null, m.name));
|
|
2009
|
+
kopf.appendChild(e('span', 'mono', m.quelleText));
|
|
2010
|
+
markt.appendChild(kopf);
|
|
2011
|
+
m.plugins.forEach(function (plugin) {
|
|
2012
|
+
var zeile = e('div', 'pluginzeile');
|
|
2013
|
+
var links = e('div');
|
|
2014
|
+
links.appendChild(e('h4', null, plugin.name));
|
|
2015
|
+
links.appendChild(e('p', null, plugin.skills.length + (plugin.skills.length === 1 ? ' Skill' : ' Skills') +
|
|
2016
|
+
': ' + plugin.skills.map(function (s) { return s.name; }).join(', ')));
|
|
2017
|
+
if (plugin.dependencies.length > 0) {
|
|
2018
|
+
var kette = e('div', 'kette');
|
|
2019
|
+
kette.appendChild(e('span', 'glied', plugin.name));
|
|
2020
|
+
plugin.dependencies.forEach(function (dep) {
|
|
2021
|
+
kette.appendChild(e('span', 'pfeil', '→'));
|
|
2022
|
+
kette.appendChild(e('span', 'glied', dep));
|
|
2023
|
+
});
|
|
2024
|
+
links.appendChild(kette);
|
|
2025
|
+
}
|
|
2026
|
+
zeile.appendChild(links);
|
|
2027
|
+
markt.appendChild(zeile);
|
|
2028
|
+
});
|
|
2029
|
+
box.appendChild(markt);
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/* ================= EINSTELLUNGEN ================= */
|
|
2034
|
+
|
|
2035
|
+
/** Ziele eines Harness-Typs mit mindestens einem eigenen Link oder liegen gebliebener
|
|
2036
|
+
harness-eigener Registrierung ohne Links (E38 R7): die Konfiguration, die „Säubern" wegräumt.
|
|
2037
|
+
Aus istZustand abgelesen, wie GRILL.md Entwurfskritik 4 es vorgibt. */
|
|
2038
|
+
function harnessBilanz(h) {
|
|
2039
|
+
var ziele = ZIELE.filter(function (z) {
|
|
2040
|
+
if (z.ziel.harness !== h) return false;
|
|
2041
|
+
return z.eintraege.some(function (ei) { return ei.art !== 'fremd'; }) || z.istRegistriert;
|
|
2042
|
+
});
|
|
2043
|
+
var links = 0, eintraege = 0;
|
|
2044
|
+
ziele.forEach(function (z) {
|
|
2045
|
+
links += z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
2046
|
+
if (z.istRegistriert) eintraege += 1;
|
|
2047
|
+
});
|
|
2048
|
+
return { ziele: ziele, links: links, eintraege: eintraege };
|
|
2049
|
+
}
|
|
2050
|
+
function zahlwort(n, ein, viele) { return n + ' ' + (n === 1 ? ein : viele); }
|
|
2051
|
+
function bilanzText(b) {
|
|
2052
|
+
// Wer den harness-eigenen Eintrag angelegt hat, weiß niemand (E38 R7), also wird er benannt,
|
|
2053
|
+
// nicht hmgr zugeschrieben.
|
|
2054
|
+
var teile = [b.links
|
|
2055
|
+
? zahlwort(b.links, 'Link', 'Links') + ' in ' + zahlwort(b.ziele.length, 'Ziel', 'Zielen')
|
|
2056
|
+
: zahlwort(b.ziele.length, 'leeres Ziel', 'leere Ziele')];
|
|
2057
|
+
if (b.eintraege) teile.push(zahlwort(b.eintraege, 'harness-eigener Eintrag', 'harness-eigene Einträge'));
|
|
2058
|
+
return teile.join(', ');
|
|
2059
|
+
}
|
|
2060
|
+
function fuelleBilanzListe(ul, bilanz) {
|
|
2061
|
+
ul.textContent = '';
|
|
2062
|
+
bilanz.ziele.forEach(function (z) {
|
|
2063
|
+
var linkZahl = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
2064
|
+
ul.appendChild(e('li', null, pfadKurz(z.verzeichnis) + ' · ' + zahlwort(linkZahl, 'Link', 'Links')));
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
/** Was „Säubern" weggeräumt hat, aus dem Ergebnis des Anwendungsfalls statt aus einer Schätzung. */
|
|
2069
|
+
function saeuberMeldung(kopf, ergebnis) {
|
|
2070
|
+
var ziele = ergebnis.ziele.filter(function (z) { return z.entfernteLinks.length > 0; });
|
|
2071
|
+
var links = 0;
|
|
2072
|
+
ziele.forEach(function (z) { links += z.entfernteLinks.length; });
|
|
2073
|
+
var eintraege = ergebnis.ziele.filter(function (z) { return z.konfigurationZurueckgebaut; }).length;
|
|
2074
|
+
var teile = [];
|
|
2075
|
+
if (links) teile.push(zahlwort(links, 'Link', 'Links') + ' in ' + zahlwort(ziele.length, 'Ziel', 'Zielen'));
|
|
2076
|
+
if (eintraege) teile.push(zahlwort(eintraege, 'harness-eigener Eintrag', 'harness-eigene Einträge'));
|
|
2077
|
+
return kopf + ' '
|
|
2078
|
+
+ (teile.length ? teile.join(' und ') + ' entfernt' : 'Es war nichts wegzuräumen')
|
|
2079
|
+
+ '. Nicht von hmgr verwaltete Einträge liegen unverändert in den Verzeichnissen.';
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
function baueHarnessliste() {
|
|
2083
|
+
var ul = $('harnessliste');
|
|
2084
|
+
// Der Neuaufbau wirft den Schalter weg, auf dem der Fokus steht. Nach einer Rückfrage ist genau
|
|
2085
|
+
// das der Fall, also merken und wieder hinsetzen.
|
|
2086
|
+
var vorher = document.activeElement;
|
|
2087
|
+
var merk = vorher && vorher.dataset && ul.contains(vorher) ? vorher.dataset.harness : null;
|
|
2088
|
+
ul.textContent = '';
|
|
2089
|
+
HARNESSE_ALLE.forEach(function (h) {
|
|
2090
|
+
var li = document.createElement('li');
|
|
2091
|
+
var label = e('label', 'harnessschalter');
|
|
2092
|
+
var input = document.createElement('input');
|
|
2093
|
+
input.type = 'checkbox';
|
|
2094
|
+
input.checked = harnessAktiv(h);
|
|
2095
|
+
input.dataset.harness = h;
|
|
2096
|
+
input.addEventListener('change', function () { schalteHarness(h, input.checked); });
|
|
2097
|
+
label.appendChild(input);
|
|
2098
|
+
|
|
2099
|
+
var txt = e('span');
|
|
2100
|
+
txt.appendChild(beschriftet(e('b', null, harnessName(h)), h));
|
|
2101
|
+
var bilanz = harnessBilanz(h);
|
|
2102
|
+
var stand = harnessAktiv(h)
|
|
2103
|
+
? (bilanz.ziele.length
|
|
2104
|
+
? zahlwort(bilanz.ziele.length, 'Ziel', 'Ziele') + ' · ' + zahlwort(bilanz.links, 'Link', 'Links')
|
|
2105
|
+
: 'aktiv, noch kein Ziel')
|
|
2106
|
+
: (bilanz.ziele.length ? 'inaktiv, Konfiguration liegt noch' : 'inaktiv');
|
|
2107
|
+
// Der Schalter bleibt auch ohne Installation stehen, damit Säubern erreichbar ist (E42).
|
|
2108
|
+
if (HARNESSE_NICHT_INSTALLIERT.indexOf(h) !== -1) stand += ' · nicht installiert';
|
|
2109
|
+
txt.appendChild(e('small', null, stand));
|
|
2110
|
+
label.appendChild(txt);
|
|
2111
|
+
li.appendChild(label);
|
|
2112
|
+
ul.appendChild(li);
|
|
2113
|
+
});
|
|
2114
|
+
if (merk) {
|
|
2115
|
+
var zurueck = ul.querySelector('input[data-harness="' + merk + '"]');
|
|
2116
|
+
if (zurueck) zurueck.focus();
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
var harnessKandidat = null;
|
|
2121
|
+
function schalteHarness(h, an) {
|
|
2122
|
+
var bilanz = harnessBilanz(h);
|
|
2123
|
+
if (an) {
|
|
2124
|
+
rufe(function () { return window.hmgr.harnessAktivieren(h); }).then(async function () {
|
|
2125
|
+
await ladeAlles();
|
|
2126
|
+
melde(harnessName(h) + ' ist wieder aktiv. ' + (bilanz.ziele.length
|
|
2127
|
+
? 'hmgr pflegt ' + zahlwort(bilanz.ziele.length, 'vorhandenes Ziel', 'vorhandene Ziele') + ' wieder.'
|
|
2128
|
+
: 'Die Plätze zum Einrichten stehen wieder offen.'));
|
|
2129
|
+
});
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
if (bilanz.ziele.length === 0) {
|
|
2133
|
+
rufe(function () { return window.hmgr.harnessDeaktivieren(h); }).then(async function () {
|
|
2134
|
+
await ladeAlles();
|
|
2135
|
+
melde(harnessName(h) + ' ist inaktiv. hmgr hat hier keine Links liegen.');
|
|
2136
|
+
});
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
harnessKandidat = h;
|
|
2140
|
+
$('dh-titel').textContent = harnessName(h) + ' deaktivieren?';
|
|
2141
|
+
beschriftet($('dh-titel'), h);
|
|
2142
|
+
$('dh-text').textContent = 'Es liegt noch Konfiguration (' + bilanzText(bilanz)
|
|
2143
|
+
+ '). Säubern entfernt die hmgr-Links und harness-eigene Einträge, nicht von hmgr verwaltete Einträge bleiben.';
|
|
2144
|
+
fuelleBilanzListe($('dh-liste'), bilanz);
|
|
2145
|
+
zeige($('d-harness'));
|
|
2146
|
+
}
|
|
2147
|
+
nachDialog($('d-harness'), async function (wert) {
|
|
2148
|
+
var h = harnessKandidat;
|
|
2149
|
+
harnessKandidat = null;
|
|
2150
|
+
if (!h) return;
|
|
2151
|
+
if (wert === 'saeubern') {
|
|
2152
|
+
await rufe(function () { return window.hmgr.harnessDeaktivieren(h); });
|
|
2153
|
+
var ergebnis = await rufe(function () { return window.hmgr.saeubern(h); });
|
|
2154
|
+
await ladeAlles();
|
|
2155
|
+
melde(saeuberMeldung(harnessName(h) + ' deaktiviert und gesäubert.', ergebnis));
|
|
2156
|
+
} else if (wert === 'nur') {
|
|
2157
|
+
var liegen = bilanzText(harnessBilanz(h));
|
|
2158
|
+
await rufe(function () { return window.hmgr.harnessDeaktivieren(h); });
|
|
2159
|
+
await ladeAlles();
|
|
2160
|
+
melde(harnessName(h) + ' deaktiviert. ' + liegen + ' bleiben liegen, hmgr pflegt sie nicht mehr. Im Ziel steht ein Säubern-Knopf, wenn du sie doch loswerden willst.');
|
|
2161
|
+
} else {
|
|
2162
|
+
// Abbruch: der Schalter im DOM steht noch auf aus, die Einstellung nicht. Neu aufbauen setzt ihn zurück.
|
|
2163
|
+
baueHarnessliste();
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
/* Derselbe Weg für Konfiguration, die schon ohne aktiven Harness dalag (Säubern-Knopf im Ziel). */
|
|
2168
|
+
var saeuberKandidat = null;
|
|
2169
|
+
function frageSaeubern(h) {
|
|
2170
|
+
saeuberKandidat = h;
|
|
2171
|
+
var bilanz = harnessBilanz(h);
|
|
2172
|
+
$('ds-titel').textContent = harnessName(h) + ' säubern?';
|
|
2173
|
+
beschriftet($('ds-titel'), h);
|
|
2174
|
+
$('ds-text').textContent = harnessName(h) + ' ist inaktiv, Konfiguration liegt noch (' + bilanzText(bilanz)
|
|
2175
|
+
+ '). Säubern entfernt die hmgr-Links und harness-eigene Einträge, nicht von hmgr verwaltete Einträge bleiben.';
|
|
2176
|
+
fuelleBilanzListe($('ds-liste'), bilanz);
|
|
2177
|
+
zeige($('d-saeubern'));
|
|
2178
|
+
}
|
|
2179
|
+
nachDialog($('d-saeubern'), async function (wert) {
|
|
2180
|
+
var h = saeuberKandidat;
|
|
2181
|
+
saeuberKandidat = null;
|
|
2182
|
+
if (wert !== 'los' || !h) return;
|
|
2183
|
+
var ergebnis = await rufe(function () { return window.hmgr.saeubern(h); });
|
|
2184
|
+
await ladeAlles();
|
|
2185
|
+
melde(saeuberMeldung(harnessName(h) + ' gesäubert.', ergebnis));
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2188
|
+
function baueEinstellungen() {
|
|
2189
|
+
baueHarnessliste();
|
|
2190
|
+
var liste = $('quellenliste');
|
|
2191
|
+
liste.textContent = '';
|
|
2192
|
+
if (QUELLEN.length === 0) {
|
|
2193
|
+
liste.appendChild(e('li', null, 'Noch keine Quelle. „Quelle hinzufügen …" legt die erste an.'));
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
QUELLEN.forEach(function (q) {
|
|
2197
|
+
var li = document.createElement('li');
|
|
2198
|
+
li.appendChild(repoIcon(q));
|
|
2199
|
+
var beschriftung = e('b', null, q.name);
|
|
2200
|
+
beschriftung.title = q.text;
|
|
2201
|
+
li.appendChild(beschriftung);
|
|
2202
|
+
|
|
2203
|
+
// Zweite Zeile, zurückgenommen: volle Adresse, Ref, Skill-Zahl (E60). Ref und Zahl kommen aus
|
|
2204
|
+
// dem schon geladenen Katalog (SKILLS), eine lokale Quelle hat keinen Ref.
|
|
2205
|
+
var ref = STANDARD_REF[q.text];
|
|
2206
|
+
var zahl = SKILLS.filter(function (s) { return s.quelleText === q.text; }).length;
|
|
2207
|
+
var teile = [q.text, q.art === 'lokal' ? 'Arbeitskopie' : (ref || '–'), zahlwort(zahl, 'Skill', 'Skills')];
|
|
2208
|
+
li.appendChild(e('small', null, teile.join(' · ')));
|
|
2209
|
+
|
|
2210
|
+
// Beide Knöpfe in einem Kasten: die Zeile ist ein Grid mit drei Spalten, zwei einzelne
|
|
2211
|
+
// Knöpfe lägen sonst beide in derselben Zelle (Spalte 3, Zeile 1) übereinander.
|
|
2212
|
+
var knoepfe = e('span', 'quellenknoepfe');
|
|
2213
|
+
var stift = e('button', 'umbenennen');
|
|
2214
|
+
stift.type = 'button';
|
|
2215
|
+
stift.title = 'Namen ändern';
|
|
2216
|
+
stift.setAttribute('aria-label', q.name + ' umbenennen');
|
|
2217
|
+
var stiftSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2218
|
+
stiftSvg.setAttribute('viewBox', '0 0 16 16');
|
|
2219
|
+
stiftSvg.setAttribute('fill', 'none');
|
|
2220
|
+
stiftSvg.setAttribute('stroke', 'currentColor');
|
|
2221
|
+
stiftSvg.setAttribute('stroke-width', '1.5');
|
|
2222
|
+
stiftSvg.setAttribute('stroke-linejoin', 'round');
|
|
2223
|
+
stiftSvg.setAttribute('aria-hidden', 'true');
|
|
2224
|
+
stiftSvg.innerHTML = '<path d="M2.6 13.4v-2.5l8-8 2.5 2.5-8 8z"/><path d="M8.9 4.6 11.4 7.1"/>';
|
|
2225
|
+
stift.appendChild(stiftSvg);
|
|
2226
|
+
stift.addEventListener('click', function () { oeffneQuelleUmbenennen(q); });
|
|
2227
|
+
knoepfe.appendChild(stift);
|
|
2228
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen …');
|
|
2229
|
+
weg.type = 'button';
|
|
2230
|
+
weg.setAttribute('aria-label', q.name + ' entfernen');
|
|
2231
|
+
weg.addEventListener('click', function () { frageQuelleVergessen(q); });
|
|
2232
|
+
knoepfe.appendChild(weg);
|
|
2233
|
+
li.appendChild(knoepfe);
|
|
2234
|
+
liste.appendChild(li);
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
$('einstellungen').addEventListener('click', function () { zeige($('d-einstellungen')); });
|
|
2238
|
+
|
|
2239
|
+
/* ---------------- Quelle hinzufügen (E54, E59) ---------------- */
|
|
2240
|
+
|
|
2241
|
+
// Vorschlag für den Anzeigenamen, aus der getippten URL/dem Pfad abgeleitet (E59), so lange der
|
|
2242
|
+
// Nutzer das Namensfeld nicht selbst angefasst hat (dqNameBeruehrt). Kleine Kopie derselben Regel
|
|
2243
|
+
// wie quelleAnzeigename/quelleNameAbleiten im Kern: letztes Segment ohne .git-Suffix, dazu die
|
|
2244
|
+
// SSH-Kurzschreibweise git@host:pfad, deren Trenner ein Doppelpunkt statt eines Schrägstrichs ist.
|
|
2245
|
+
function vorschlagAusText(text) {
|
|
2246
|
+
var t = text.trim();
|
|
2247
|
+
if (!t) return '';
|
|
2248
|
+
var scp = /^[^@/]+@[^@/:]+:(.+)$/.exec(t);
|
|
2249
|
+
var pfadteil = scp ? scp[1] : t;
|
|
2250
|
+
var ohneSchraeg = pfadteil.replace(/\/+$/, '');
|
|
2251
|
+
var ohneGit = ohneSchraeg.endsWith('.git') ? ohneSchraeg.slice(0, -4) : ohneSchraeg;
|
|
2252
|
+
return letztesSegment(ohneGit);
|
|
2253
|
+
}
|
|
2254
|
+
var dqNameBeruehrt = false;
|
|
2255
|
+
$('dq-name').addEventListener('input', function () { dqNameBeruehrt = true; });
|
|
2256
|
+
$('dq-text').addEventListener('input', function () {
|
|
2257
|
+
if (dqNameBeruehrt) return;
|
|
2258
|
+
$('dq-name').value = vorschlagAusText($('dq-text').value);
|
|
2259
|
+
});
|
|
2260
|
+
|
|
2261
|
+
$('quelleneu').addEventListener('click', function () {
|
|
2262
|
+
$('dq-text').value = '';
|
|
2263
|
+
$('dq-name').value = '';
|
|
2264
|
+
dqNameBeruehrt = false;
|
|
2265
|
+
$('dq-fehler').hidden = true;
|
|
2266
|
+
zeige($('d-quelle'));
|
|
2267
|
+
});
|
|
2268
|
+
enterBestaetigt($('dq-text'), $('dq-los'));
|
|
2269
|
+
enterBestaetigt($('dq-name'), $('dq-los'));
|
|
2270
|
+
|
|
2271
|
+
// Kein nachDialog: bei einem Fehler (Quelle nicht erreichbar) muss der Dialog offen bleiben und
|
|
2272
|
+
// den Fehler zeigen, statt sich wie bei Erfolg zu schließen. Deshalb der IPC-Aufruf direkt hier,
|
|
2273
|
+
// ohne rufe() (das würde immer die Meldungsleiste befüllen und werfen).
|
|
2274
|
+
$('d-quelle').addEventListener('submit', function (ev) {
|
|
2275
|
+
if (!ev.submitter || ev.submitter.value !== 'los') return;
|
|
2276
|
+
ev.preventDefault();
|
|
2277
|
+
quelleHinzufuegenLos();
|
|
2278
|
+
});
|
|
2279
|
+
// Das Hinzufügen holt die Quelle aus dem Netz, das dauert. Ohne Sperre legte ein zweiter Anstoß
|
|
2280
|
+
// in der Zwischenzeit einen zweiten Lauf an, der denselben Zustand liest und schreibt: die Quelle
|
|
2281
|
+
// stünde zweimal im Zustand. Sperre über den disabled-Zustand wie beim Aktualisieren-Knopf.
|
|
2282
|
+
async function quelleHinzufuegenLos() {
|
|
2283
|
+
var knopf = $('dq-los');
|
|
2284
|
+
if (knopf.disabled) return;
|
|
2285
|
+
var text = $('dq-text').value.trim();
|
|
2286
|
+
var name = $('dq-name').value.trim();
|
|
2287
|
+
$('dq-fehler').hidden = true;
|
|
2288
|
+
if (!text) return;
|
|
2289
|
+
knopf.disabled = true;
|
|
2290
|
+
knopf.textContent = 'Läuft …';
|
|
2291
|
+
var ergebnis;
|
|
2292
|
+
try {
|
|
2293
|
+
ergebnis = await window.hmgr.quelleHinzufuegen(text, name || undefined);
|
|
2294
|
+
} finally {
|
|
2295
|
+
knopf.disabled = false;
|
|
2296
|
+
knopf.textContent = 'Hinzufügen';
|
|
2297
|
+
}
|
|
2298
|
+
if (!ergebnis.ok) {
|
|
2299
|
+
$('dq-fehler').textContent = ergebnis.fehler;
|
|
2300
|
+
$('dq-fehler').hidden = false;
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
$('d-quelle').close('los');
|
|
2304
|
+
var daten = ergebnis.daten;
|
|
2305
|
+
await ladeAlles();
|
|
2306
|
+
if (daten.bekannt) {
|
|
2307
|
+
// Die Adresse gehört dazu (E54): gemeldet wird, unter welcher Schreibweise die Quelle geführt
|
|
2308
|
+
// wird, der Anzeigename allein sagt das nicht.
|
|
2309
|
+
melde('Quelle ist schon bekannt, geführt als „' + daten.quelle.name + '“ (' + daten.quelle.text + ').');
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
var teile = [zahlwort(daten.skills.length, 'Skill', 'Skills')];
|
|
2313
|
+
if (daten.marketplace) teile.push('Marketplace „' + daten.marketplace.name + '“');
|
|
2314
|
+
melde('Quelle „' + daten.quelle.name + '“ hinzugefügt, ' + teile.join(', ') + '.');
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
/* ---------------- Quelle umbenennen (E59) ---------------- */
|
|
2318
|
+
|
|
2319
|
+
var quelleUmbenennenKontext = null;
|
|
2320
|
+
function oeffneQuelleUmbenennen(quelle) {
|
|
2321
|
+
quelleUmbenennenKontext = quelle;
|
|
2322
|
+
$('dqu-text').textContent = '„' + quelle.name + '“ (' + quelle.text + ')';
|
|
2323
|
+
$('dqu-name').value = quelle.name;
|
|
2324
|
+
$('dqu-fehler').hidden = true;
|
|
2325
|
+
zeige($('d-quelle-umbenennen'));
|
|
2326
|
+
}
|
|
2327
|
+
enterBestaetigt($('dqu-name'), $('dqu-los'));
|
|
2328
|
+
|
|
2329
|
+
// Wie bei d-quelle: kein nachDialog, ein Fehler (Namenskollision) muss den Dialog offen halten
|
|
2330
|
+
// und den Fehler dort zeigen (E59), statt sich wie bei Erfolg zu schließen.
|
|
2331
|
+
$('d-quelle-umbenennen').addEventListener('submit', function (ev) {
|
|
2332
|
+
if (!ev.submitter || ev.submitter.value !== 'los') return;
|
|
2333
|
+
ev.preventDefault();
|
|
2334
|
+
quelleUmbenennenLos();
|
|
2335
|
+
});
|
|
2336
|
+
async function quelleUmbenennenLos() {
|
|
2337
|
+
var knopf = $('dqu-los');
|
|
2338
|
+
if (knopf.disabled || quelleUmbenennenKontext === null) return;
|
|
2339
|
+
var quelle = quelleUmbenennenKontext;
|
|
2340
|
+
var name = $('dqu-name').value.trim();
|
|
2341
|
+
$('dqu-fehler').hidden = true;
|
|
2342
|
+
if (!name) return;
|
|
2343
|
+
knopf.disabled = true;
|
|
2344
|
+
knopf.textContent = 'Läuft …';
|
|
2345
|
+
var ergebnis;
|
|
2346
|
+
try {
|
|
2347
|
+
ergebnis = await window.hmgr.quelleUmbenennen(quelle.text, name);
|
|
2348
|
+
} finally {
|
|
2349
|
+
knopf.disabled = false;
|
|
2350
|
+
knopf.textContent = 'Umbenennen';
|
|
2351
|
+
}
|
|
2352
|
+
if (!ergebnis.ok) {
|
|
2353
|
+
$('dqu-fehler').textContent = ergebnis.fehler;
|
|
2354
|
+
$('dqu-fehler').hidden = false;
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
quelleUmbenennenKontext = null;
|
|
2358
|
+
$('d-quelle-umbenennen').close('los');
|
|
2359
|
+
await ladeAlles();
|
|
2360
|
+
melde('Quelle heißt jetzt „' + ergebnis.daten.name + '“.');
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/* ---------------- Quelle entfernen (E54) ---------------- */
|
|
2364
|
+
|
|
2365
|
+
var quelleVergessenKontext = null;
|
|
2366
|
+
/** Zahl der Links, die nach dem Entfernen als „Link ohne Quelle" liegen bleiben. Über alle Ziele,
|
|
2367
|
+
auch die von abgeschalteten Harnesses und aus Arbeitsverzeichnissen. Die Quelle eines Links
|
|
2368
|
+
ist derselbe Zustandseintrag, den die Quellenliste zeigt (der Adapter setzt sie von dort ein),
|
|
2369
|
+
also ist der Textvergleich hier die Identität, keine Anzeige-Näherung. */
|
|
2370
|
+
function betroffeneLinksZahl(quelle) {
|
|
2371
|
+
var n = 0;
|
|
2372
|
+
ZIELE.forEach(function (z) {
|
|
2373
|
+
z.eintraege.forEach(function (ei) {
|
|
2374
|
+
if (ei.art !== 'link') return;
|
|
2375
|
+
var q = ei.link.checkout.quelle;
|
|
2376
|
+
var textq = q.art === 'git' ? q.url : q.pfad;
|
|
2377
|
+
if (q.art === quelle.art && textq === quelle.text) n++;
|
|
2378
|
+
});
|
|
2379
|
+
});
|
|
2380
|
+
return n;
|
|
2381
|
+
}
|
|
2382
|
+
function frageQuelleVergessen(quelle) {
|
|
2383
|
+
quelleVergessenKontext = quelle;
|
|
2384
|
+
var n = betroffeneLinksZahl(quelle);
|
|
2385
|
+
$('dqv-titel').textContent = '„' + quelle.name + '“ entfernen?';
|
|
2386
|
+
$('dqv-text').textContent = 'Checkouts und Links bleiben liegen. '
|
|
2387
|
+
+ (n === 0
|
|
2388
|
+
? 'Es gibt aktuell keine Links dazu.'
|
|
2389
|
+
: zahlwort(n, 'Link gilt', 'Links gelten') + ' danach als „Link ohne Quelle“, hmgr aktualisiert '
|
|
2390
|
+
+ (n === 1 ? 'ihn' : 'sie') + ' nicht mehr. Wieder hinzufügen macht '
|
|
2391
|
+
+ (n === 1 ? 'ihn' : 'sie') + ' wieder zu ' + (n === 1 ? 'einem normalen Link' : 'normalen Links') + '.');
|
|
2392
|
+
zeige($('d-quelle-vergessen'));
|
|
2393
|
+
}
|
|
2394
|
+
/** Nach dem „entfernen?“ vergisst hmgr die Quelle, Checkouts und Links bleiben liegen (E54). */
|
|
2395
|
+
async function quelleVergessen(quelle) {
|
|
2396
|
+
await rufe(function () { return window.hmgr.quelleEntfernen(quelle.text); });
|
|
2397
|
+
await ladeAlles();
|
|
2398
|
+
// Der Neuaufbau der Liste wirft den Knopf weg, auf dem der Fokus nach dem Dialog steht. Der
|
|
2399
|
+
// nächstliegende Platz ist der Knopf, der eine neue Quelle anlegt; er steht fest im Markup.
|
|
2400
|
+
$('quelleneu').focus();
|
|
2401
|
+
melde('Quelle „' + quelle.name + '“ aus hmgr entfernt.');
|
|
2402
|
+
}
|
|
2403
|
+
nachDialog($('d-quelle-vergessen'), async function (wert) {
|
|
2404
|
+
if (wert !== 'ja' || quelleVergessenKontext === null) return;
|
|
2405
|
+
var quelle = quelleVergessenKontext;
|
|
2406
|
+
quelleVergessenKontext = null;
|
|
2407
|
+
// Inventar (E58) VOR jeder Wirkung erheben: quelleEntfernen macht ihre Links zu „Link ohne
|
|
2408
|
+
// Quelle“, danach ist die Zuordnung weg. Bei leerem Inventar keine Rückfrage (F31a): sofort
|
|
2409
|
+
// vergessen wie bisher.
|
|
2410
|
+
var inventar = await rufe(function () { return window.hmgr.quelleAufraeumInventar(quelle.text); });
|
|
2411
|
+
if (inventar.links.length === 0 && inventar.checkouts.length === 0) {
|
|
2412
|
+
await quelleVergessen(quelle);
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
frageQuelleAufraeumen(quelle, inventar);
|
|
2416
|
+
});
|
|
2417
|
+
|
|
2418
|
+
/* ---------------- Skills der Quelle komplett löschen (E58, F31a) ---------------- */
|
|
2419
|
+
|
|
2420
|
+
var quelleAufraeumenKontext = null;
|
|
2421
|
+
function frageQuelleAufraeumen(quelle, inventar) {
|
|
2422
|
+
quelleAufraeumenKontext = { quelle: quelle, inventar: inventar };
|
|
2423
|
+
var teile = [
|
|
2424
|
+
zahlwort(inventar.links.length, 'Link', 'Links'),
|
|
2425
|
+
zahlwort(inventar.checkouts.length, 'Checkout', 'Checkouts'),
|
|
2426
|
+
];
|
|
2427
|
+
function bleibt(n) { return n === 1 ? ', bleibt liegen' : ', bleiben liegen'; }
|
|
2428
|
+
if (inventar.plugins.length > 0) {
|
|
2429
|
+
teile.push(zahlwort(inventar.plugins.length, 'installiertes Plugin', 'installierte Plugins') + bleibt(inventar.plugins.length));
|
|
2430
|
+
}
|
|
2431
|
+
if (inventar.zeigerPfade.length > 0) {
|
|
2432
|
+
teile.push(zahlwort(inventar.zeigerPfade.length, 'Zeiger-Eintrag', 'Zeiger-Einträge') + bleibt(inventar.zeigerPfade.length));
|
|
2433
|
+
}
|
|
2434
|
+
var text = 'Skills von „' + quelle.name + '“ komplett vom Rechner löschen (' + teile.join(', ') + ')?';
|
|
2435
|
+
// Warnung bei lokalen Änderungen (E15, F31a): ein Ja bricht dann ohne zu löschen ab, auch die
|
|
2436
|
+
// Quelle bleibt bekannt, sichtbar VOR der Entscheidung, damit „Behalten“ die naheliegende Wahl ist.
|
|
2437
|
+
var lok = inventar.checkoutsMitLokalenAenderungen || [];
|
|
2438
|
+
if (lok.length > 0) {
|
|
2439
|
+
text += ' Achtung: ' + zahlwort(lok.length, 'Checkout hat', 'Checkouts haben') + ' lokale Änderungen: '
|
|
2440
|
+
+ lok.map(function (c) { return pfadKurz(c.pfad); }).join(', ')
|
|
2441
|
+
+ '. Ein Ja bricht dann ab, ohne zu löschen, die Quelle bleibt bekannt.';
|
|
2442
|
+
}
|
|
2443
|
+
$('dqa-text').textContent = text;
|
|
2444
|
+
// Erst nach einer kurzen Pause, wie beim Skill-Ordner-Lösch-Angebot (E56): zwei <dialog>
|
|
2445
|
+
// lassen sich nicht im selben close-Ereignis nacheinander öffnen.
|
|
2446
|
+
setTimeout(function () { zeige($('d-quelle-aufraeumen')); }, 90);
|
|
2447
|
+
}
|
|
2448
|
+
nachDialog($('d-quelle-aufraeumen'), async function (wert) {
|
|
2449
|
+
var kontext = quelleAufraeumenKontext;
|
|
2450
|
+
quelleAufraeumenKontext = null;
|
|
2451
|
+
if (kontext === null) return;
|
|
2452
|
+
// Nein: wie bisher vergessen, alles bleibt liegen (E54).
|
|
2453
|
+
if (wert !== 'loeschen') {
|
|
2454
|
+
await quelleVergessen(kontext.quelle);
|
|
2455
|
+
return;
|
|
2456
|
+
}
|
|
2457
|
+
// Ja: erst aufräumen. Bei lokalen Änderungen bricht quelleAufraeumen ab (F31a), dann bleibt die
|
|
2458
|
+
// Quelle bekannt und nichts wird gelöscht, nicht einmal vergessen.
|
|
2459
|
+
var ergebnis;
|
|
2460
|
+
try {
|
|
2461
|
+
ergebnis = await rufe(function () { return window.hmgr.quelleAufraeumen(kontext.inventar); });
|
|
2462
|
+
} catch (fehler) {
|
|
2463
|
+
await ladeAlles();
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
await rufe(function () { return window.hmgr.quelleEntfernen(kontext.quelle.text); });
|
|
2467
|
+
await ladeAlles();
|
|
2468
|
+
$('quelleneu').focus();
|
|
2469
|
+
melde(
|
|
2470
|
+
zahlwort(ergebnis.entfernteLinks, 'Link', 'Links') + ' und '
|
|
2471
|
+
+ zahlwort(ergebnis.entfernteCheckouts, 'Checkout', 'Checkouts') + ' von „' + kontext.quelle.name + '“ gelöscht, die Quelle ist vergessen.',
|
|
2472
|
+
);
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
/* ---------------- Skill-Ordner festlegen (E56) ---------------- */
|
|
2476
|
+
|
|
2477
|
+
var skillOrdnerZiel = null;
|
|
2478
|
+
function oeffneSkillOrdnerFestlegen(z) {
|
|
2479
|
+
skillOrdnerZiel = z;
|
|
2480
|
+
$('dso-pfad').value = '';
|
|
2481
|
+
$('dso-fehler').hidden = true;
|
|
2482
|
+
zeige($('d-skillordner'));
|
|
2483
|
+
}
|
|
2484
|
+
enterBestaetigt($('dso-pfad'), $('dso-los'));
|
|
2485
|
+
|
|
2486
|
+
// Wie bei d-quelle: bei einem Fehler (Pfad ungültig, schon verwaltet) muss der Dialog offen
|
|
2487
|
+
// bleiben und den Fehler zeigen, statt sich wie bei Erfolg zu schließen.
|
|
2488
|
+
$('d-skillordner').addEventListener('submit', function (ev) {
|
|
2489
|
+
if (!ev.submitter || ev.submitter.value !== 'los') return;
|
|
2490
|
+
ev.preventDefault();
|
|
2491
|
+
skillOrdnerFestlegenLos();
|
|
2492
|
+
});
|
|
2493
|
+
async function skillOrdnerFestlegenLos() {
|
|
2494
|
+
var knopf = $('dso-los');
|
|
2495
|
+
if (knopf.disabled || skillOrdnerZiel === null) return;
|
|
2496
|
+
var pfad = $('dso-pfad').value.trim();
|
|
2497
|
+
$('dso-fehler').hidden = true;
|
|
2498
|
+
if (!pfad) return;
|
|
2499
|
+
var z = skillOrdnerZiel;
|
|
2500
|
+
knopf.disabled = true;
|
|
2501
|
+
knopf.textContent = 'Läuft …';
|
|
2502
|
+
var ergebnis;
|
|
2503
|
+
try {
|
|
2504
|
+
ergebnis = await window.hmgr.skillOrdnerFestlegen({ harness: z.ziel.harness, ort: z.ziel.ort, pfad: pfad });
|
|
2505
|
+
} finally {
|
|
2506
|
+
knopf.disabled = false;
|
|
2507
|
+
knopf.textContent = 'Festlegen';
|
|
2508
|
+
}
|
|
2509
|
+
if (!ergebnis.ok) {
|
|
2510
|
+
$('dso-fehler').textContent = ergebnis.fehler;
|
|
2511
|
+
$('dso-fehler').hidden = false;
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
$('d-skillordner').close('los');
|
|
2515
|
+
skillOrdnerZiel = null;
|
|
2516
|
+
await ladeAlles();
|
|
2517
|
+
melde('Liest Skills jetzt aus ' + pfadKurz(ergebnis.daten) + '.');
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
/* ---------------- Skill-Ordner entfernen und löschen (E56) ---------------- */
|
|
2521
|
+
|
|
2522
|
+
var skillOrdnerEntfernenZiel = null;
|
|
2523
|
+
function frageSkillOrdnerEntfernen(z) {
|
|
2524
|
+
skillOrdnerEntfernenZiel = z;
|
|
2525
|
+
$('dsoe-titel').textContent = pfadKurz(z.verwalteterSkillOrdner) + ' nicht mehr verwalten?';
|
|
2526
|
+
$('dsoe-text').textContent = 'Der Eintrag in Hermes\' config.yaml verschwindet, der Ordner bleibt zunächst '
|
|
2527
|
+
+ 'erhalten. Danach fragt hmgr, ob er samt Inhalt gelöscht werden soll.';
|
|
2528
|
+
zeige($('d-skillordner-entfernen'));
|
|
2529
|
+
}
|
|
2530
|
+
nachDialog($('d-skillordner-entfernen'), async function (wert) {
|
|
2531
|
+
if (wert !== 'ja' || skillOrdnerEntfernenZiel === null) return;
|
|
2532
|
+
var z = skillOrdnerEntfernenZiel;
|
|
2533
|
+
skillOrdnerEntfernenZiel = null;
|
|
2534
|
+
var ergebnis = await rufe(function () {
|
|
2535
|
+
return window.hmgr.skillOrdnerEntfernen({ harness: z.ziel.harness, ort: z.ziel.ort });
|
|
2536
|
+
});
|
|
2537
|
+
await ladeAlles();
|
|
2538
|
+
melde('Skill-Ordner-Eintrag für ' + zielTitel(z) + ' entfernt.');
|
|
2539
|
+
skillOrdnerLoeschenKontext = { ziel: z.ziel, pfad: ergebnis.pfad };
|
|
2540
|
+
$('dsol-text').textContent = 'Ordner ' + pfadKurz(ergebnis.pfad) + ' enthält noch '
|
|
2541
|
+
+ zahlwort(ergebnis.hmgrLinks, 'hmgr-Link', 'hmgr-Links') + ' und '
|
|
2542
|
+
+ zahlwort(ergebnis.fremdeEintraege, 'fremden Eintrag', 'fremde Einträge') + '.';
|
|
2543
|
+
setTimeout(function () { zeige($('d-skillordner-loeschen')); }, 90);
|
|
2544
|
+
});
|
|
2545
|
+
|
|
2546
|
+
var skillOrdnerLoeschenKontext = null;
|
|
2547
|
+
nachDialog($('d-skillordner-loeschen'), async function (wert) {
|
|
2548
|
+
var kontext = skillOrdnerLoeschenKontext;
|
|
2549
|
+
skillOrdnerLoeschenKontext = null;
|
|
2550
|
+
if (wert !== 'loeschen' || kontext === null) return;
|
|
2551
|
+
await rufe(function () {
|
|
2552
|
+
return window.hmgr.skillOrdnerLoeschen({ harness: kontext.ziel.harness, ort: kontext.ziel.ort, pfad: kontext.pfad });
|
|
2553
|
+
});
|
|
2554
|
+
await ladeAlles();
|
|
2555
|
+
melde('Ordner ' + pfadKurz(kontext.pfad) + ' gelöscht.');
|
|
2556
|
+
});
|
|
2557
|
+
|
|
2558
|
+
/* ================= RÜCKFRAGE VOM MAIN-PROZESS (E28, E34) ================= */
|
|
2559
|
+
|
|
2560
|
+
// Die Fragen kommen fertig formuliert aus dem Kern; für die Anzeige werden Home-Pfade darin
|
|
2561
|
+
// wie überall sonst auf ~ gekürzt (nur Anzeige, die Antwort transportiert keinen Text zurück).
|
|
2562
|
+
function frageKurz(text) { return HOME ? text.split(HOME + '/').join('~/') : text; }
|
|
2563
|
+
window.hmgr.onRueckfrage(function (nachricht) {
|
|
2564
|
+
if (nachricht.art === 'bestaetigen') {
|
|
2565
|
+
$('db-frage').textContent = frageKurz(nachricht.frage);
|
|
2566
|
+
zeige($('d-bestaetigen'));
|
|
2567
|
+
$('d-bestaetigen').addEventListener(
|
|
2568
|
+
'close',
|
|
2569
|
+
function () { window.hmgr.antwortenRueckfrage(nachricht.id, $('d-bestaetigen').returnValue === 'ja'); },
|
|
2570
|
+
{ once: true },
|
|
2571
|
+
);
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
$('da-frage').textContent = frageKurz(nachricht.frage);
|
|
2575
|
+
var box = $('da-optionen');
|
|
2576
|
+
box.textContent = '';
|
|
2577
|
+
(nachricht.optionen || []).forEach(function (option, i) {
|
|
2578
|
+
var label = e('label', 'wahlzeile');
|
|
2579
|
+
label.appendChild(e('span', 'nummer', String(i + 1)));
|
|
2580
|
+
var innen = e('span');
|
|
2581
|
+
var input = document.createElement('input');
|
|
2582
|
+
input.type = 'radio';
|
|
2583
|
+
input.name = 'auswahl-' + nachricht.id;
|
|
2584
|
+
input.value = String(i);
|
|
2585
|
+
input.className = 'versteckt';
|
|
2586
|
+
if (i === 0) input.checked = true;
|
|
2587
|
+
innen.appendChild(input);
|
|
2588
|
+
innen.appendChild(e('b', null, option));
|
|
2589
|
+
label.appendChild(innen);
|
|
2590
|
+
box.appendChild(label);
|
|
2591
|
+
});
|
|
2592
|
+
zeige($('d-auswahl'));
|
|
2593
|
+
$('d-auswahl').addEventListener(
|
|
2594
|
+
'close',
|
|
2595
|
+
function () {
|
|
2596
|
+
var wert = $('d-auswahl').returnValue;
|
|
2597
|
+
if (wert !== 'weiter') {
|
|
2598
|
+
window.hmgr.antwortenRueckfrage(nachricht.id, undefined);
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
var gewaehlt = box.querySelector('input:checked');
|
|
2602
|
+
window.hmgr.antwortenRueckfrage(nachricht.id, gewaehlt ? +gewaehlt.value : undefined);
|
|
2603
|
+
},
|
|
2604
|
+
{ once: true },
|
|
2605
|
+
);
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
/* ---------------- Start ---------------- */
|
|
2609
|
+
|
|
2610
|
+
ladeHome().then(ladeAlles).catch(function (fehler) { melde(fehler.message || String(fehler)); });
|
|
2611
|
+
})();
|