hmgr 0.0.4 → 0.0.6
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/README.md +10 -0
- package/dist/adapter/checkout-pfad.js +10 -2
- package/dist/adapter/claude-code.js +251 -23
- package/dist/adapter/composer.js +4 -3
- package/dist/adapter/datei-text.js +18 -0
- package/dist/adapter/dateisystem.js +146 -8
- package/dist/adapter/git.js +129 -17
- package/dist/adapter/hermes.js +235 -10
- package/dist/adapter/marketplace-json.js +2 -1
- package/dist/adapter/mcp-katalog.js +124 -0
- package/dist/adapter/quelle-text.js +10 -1
- package/dist/adapter/skill-md.js +96 -19
- package/dist/adapter/terminal.js +1 -1
- package/dist/adapter/umgebung.js +43 -0
- package/dist/adapter/vergleich.js +20 -0
- package/dist/adapter/zustand-json.js +6 -1
- package/dist/cli/hilfe.js +227 -26
- package/dist/cli/main.js +362 -20
- package/dist/gui/beobachter.js +108 -0
- package/dist/gui/icons.js +114 -0
- package/dist/gui/login-pfad.js +120 -0
- package/dist/gui/main.js +823 -0
- package/dist/gui/mcp-ansicht.js +57 -0
- package/dist/gui/oeffnen.js +33 -0
- package/dist/gui/preload.cjs +70 -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 +581 -0
- package/dist/gui/renderer/renderer.js +3227 -0
- package/dist/gui/renderer/styles.css +701 -0
- package/dist/kern/anwendungsfaelle.js +859 -65
- package/dist/kern/modell.js +297 -0
- package/dist/kern/quelle-ort.js +14 -1
- package/dist/ports/harness.js +16 -0
- package/dist/ports/mcp-katalog.js +1 -0
- package/dist/ports/umgebung.js +1 -0
- package/dist/ports/zustand.js +1 -1
- package/package.json +47 -2
|
@@ -0,0 +1,3227 @@
|
|
|
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 und Plattform, einmal beim Laden über hmgr:home und hmgr:plattform geholt
|
|
16
|
+
// (siehe ladeUmgebung unten). Nur für die Anzeige: beides fließt nie in einen IPC-Aufruf oder
|
|
17
|
+
// einen zielKey zurück.
|
|
18
|
+
var HOME = '';
|
|
19
|
+
// Pfadtrenner der Anzeige (E68). Aus der Plattform und nicht aus dem Home-Pfad abgeleitet: die
|
|
20
|
+
// Pfade, die hier angezeigt werden, baut der Kern mit `node:path`, also mit dem Trenner der
|
|
21
|
+
// Plattform — auch dann, wenn HOME selbst in POSIX-Schreibweise gesetzt ist (MSYS/Git-Bash).
|
|
22
|
+
var TRENNER = '/';
|
|
23
|
+
/** Trenner einer Plattform, wie `process.platform` sie nennt. Rein, nur für die Anzeige. */
|
|
24
|
+
function plattformTrenner(plattform) { return plattform === 'win32' ? '\\' : '/'; }
|
|
25
|
+
/**
|
|
26
|
+
* Kürzt einen absoluten Pfad auf ~, wenn er mit dem Home-Verzeichnis beginnt. Grenze ist einer
|
|
27
|
+
* der beiden Trenner (E68): unter Windows steht dort „\", und der Rest des Pfads bleibt wörtlich
|
|
28
|
+
* stehen, die Kürzung heißt dort also von selbst `~\…` statt `~/…`.
|
|
29
|
+
*/
|
|
30
|
+
function pfadKurz(p) {
|
|
31
|
+
if (!HOME || typeof p !== 'string') return p;
|
|
32
|
+
if (p === HOME) return '~';
|
|
33
|
+
var grenze = p.charAt(HOME.length);
|
|
34
|
+
if (p.indexOf(HOME) === 0 && (grenze === '/' || grenze === '\\')) return '~' + p.slice(HOME.length);
|
|
35
|
+
return p;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Schreibt einen fest im Quelltext stehenden POSIX-Pfad in der Schreibweise der Plattform (E68),
|
|
39
|
+
* damit `~/.claude.json` unter Windows nicht neben `~\Code\…` steht. Nur für Anzeigetexte, die
|
|
40
|
+
* hmgr selbst formuliert — nie für einen Pfad, der aus dem Kern kommt.
|
|
41
|
+
*/
|
|
42
|
+
function pfadSchreibweise(p) { return TRENNER === '/' ? p : p.split('/').join(TRENNER); }
|
|
43
|
+
|
|
44
|
+
function melde(text) {
|
|
45
|
+
$('meldungtext').textContent = text;
|
|
46
|
+
$('meldung').hidden = false;
|
|
47
|
+
}
|
|
48
|
+
$('meldungzu').addEventListener('click', function () { $('meldung').hidden = true; });
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Hinweiszeile im Erfolgsdialog (E26, F32). Jeder Aufruf, der d-erfolg füllt, ruft das auf, auch
|
|
52
|
+
* mit leerer Liste: sonst stünde der Hinweis des vorigen Laufs noch da. Doppelte Hinweise stehen
|
|
53
|
+
* einmal, ein Lauf über mehrere Skills sagt dieselbe Sache sonst N-mal.
|
|
54
|
+
*/
|
|
55
|
+
function setzeErfolgHinweise(hinweise) {
|
|
56
|
+
var einmalig = [];
|
|
57
|
+
(hinweise || []).forEach(function (h) {
|
|
58
|
+
if (h && einmalig.indexOf(h) < 0) einmalig.push(h);
|
|
59
|
+
});
|
|
60
|
+
$('de-hinweis').textContent = einmalig.join('\n');
|
|
61
|
+
$('de-hinweis').hidden = einmalig.length === 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Ruft einen window.hmgr-Kanal auf, meldet einen Fehler in der Leiste statt in der Konsole. */
|
|
65
|
+
async function rufe(fn) {
|
|
66
|
+
var ergebnis = await fn();
|
|
67
|
+
if (!ergebnis.ok) {
|
|
68
|
+
melde(ergebnis.fehler);
|
|
69
|
+
throw new Error(ergebnis.fehler);
|
|
70
|
+
}
|
|
71
|
+
return ergebnis.daten;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ---------------- Dialoge ---------------- */
|
|
75
|
+
|
|
76
|
+
var fokusStapel = [];
|
|
77
|
+
function zeige(dlg) {
|
|
78
|
+
fokusStapel.push(document.activeElement);
|
|
79
|
+
dlg.returnValue = '';
|
|
80
|
+
dlg.showModal();
|
|
81
|
+
}
|
|
82
|
+
document.querySelectorAll('dialog').forEach(function (dlg) {
|
|
83
|
+
dlg.addEventListener('close', function () {
|
|
84
|
+
var ziel = fokusStapel.pop();
|
|
85
|
+
if (ziel && ziel.focus && ziel.isConnected) ziel.focus();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
function nachDialog(dlg, fn) {
|
|
89
|
+
dlg.addEventListener('close', function () { fn(dlg.returnValue); });
|
|
90
|
+
}
|
|
91
|
+
// Enter im einzigen Textfeld meint Bestätigen. Ohne das drückt die implizite Formular-Absendung
|
|
92
|
+
// den ersten Submit-Knopf, und der ist in den Dialogen „Abbrechen".
|
|
93
|
+
function enterBestaetigt(feld, knopf) {
|
|
94
|
+
feld.addEventListener('keydown', function (ev) {
|
|
95
|
+
if (ev.key !== 'Enter') return;
|
|
96
|
+
ev.preventDefault();
|
|
97
|
+
knopf.click();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ---------------- Harness-Zeichen ---------------- */
|
|
102
|
+
|
|
103
|
+
// Originalzeichen der Harnesses: Claude-Stern (Anthropics Claude-Symbol, Pfad aus simple-icons,
|
|
104
|
+
// identisch mit dem offiziellen Logo-SVG; Füllpfad, kein Strich), Agent-Skills-Hexagon (Favicon
|
|
105
|
+
// von agentskills.io, dem Standard hinter ~/.agents). Beide einfarbig über currentColor.
|
|
106
|
+
var HARNESS_ZEICHEN = {
|
|
107
|
+
'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"/>' },
|
|
108
|
+
agents: { viewBox: '0 0 32 32', inhalt: '<path d="M16 0.5L29.4234 8.25V23.75L16 31.5L2.57661 23.75V8.25L16 0.5Z"/>' },
|
|
109
|
+
};
|
|
110
|
+
// Hermes hat kein reduzierbares Vektorzeichen (das Original ist eine Manga-Illustration); hier
|
|
111
|
+
// das 32px-Favicon aus Tims lokaler Hermes-Installation, weißer Grund inklusive.
|
|
112
|
+
var HARNESS_BILD = {
|
|
113
|
+
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=',
|
|
114
|
+
};
|
|
115
|
+
/** Anzeigename je Harness-Slug (Slugs bleiben in Daten, Schlüsseln und IPC). */
|
|
116
|
+
var HARNESS_NAME = { 'claude-code': 'Claude Code', hermes: 'Hermes', agents: 'Agents' };
|
|
117
|
+
function harnessName(h) { return HARNESS_NAME[h] || h; }
|
|
118
|
+
function harnessZeichen(h) {
|
|
119
|
+
if (HARNESS_BILD[h]) {
|
|
120
|
+
var img = document.createElement('img');
|
|
121
|
+
img.src = HARNESS_BILD[h];
|
|
122
|
+
img.className = 'hsymbol';
|
|
123
|
+
img.alt = '';
|
|
124
|
+
img.setAttribute('aria-hidden', 'true');
|
|
125
|
+
return img;
|
|
126
|
+
}
|
|
127
|
+
var z = HARNESS_ZEICHEN[h];
|
|
128
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
129
|
+
svg.setAttribute('viewBox', z ? z.viewBox : '0 0 16 16');
|
|
130
|
+
svg.setAttribute('fill', 'currentColor');
|
|
131
|
+
svg.setAttribute('class', 'hsymbol');
|
|
132
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
133
|
+
svg.innerHTML = z ? z.inhalt : '';
|
|
134
|
+
return svg;
|
|
135
|
+
}
|
|
136
|
+
function beschriftet(el, h) {
|
|
137
|
+
el.insertBefore(harnessZeichen(h), el.firstChild);
|
|
138
|
+
return el;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Kleine Harness-Zeichen einer Zeile (E53), ein Zeichen je genannten Harness, mit
|
|
142
|
+
Sammel-aria-label statt einzelner Beschriftung je Icon. */
|
|
143
|
+
function harnesseZeichenSammel(harnesse) {
|
|
144
|
+
var wrap = e('span', 'zeilenzeichen');
|
|
145
|
+
wrap.setAttribute('role', 'img');
|
|
146
|
+
wrap.setAttribute('aria-label', harnesse.map(harnessName).join(', '));
|
|
147
|
+
harnesse.forEach(function (h) { wrap.appendChild(harnessZeichen(h)); });
|
|
148
|
+
return wrap;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Ordner-Symbol vom „Verzeichnis hinzufügen"-Knopf, hier für den karten-weiten
|
|
152
|
+
„Skills hinzufügen"-Knopf im Chip „Alle" (E53 Pkt. 4). */
|
|
153
|
+
function ordnerZeichen() {
|
|
154
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
155
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
156
|
+
svg.setAttribute('fill', 'none');
|
|
157
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
158
|
+
svg.setAttribute('stroke-width', '1.5');
|
|
159
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
160
|
+
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"/>';
|
|
161
|
+
return svg;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ---------------- Ort- und Grund-Texte ---------------- */
|
|
165
|
+
|
|
166
|
+
function ortName(ort) {
|
|
167
|
+
if (ort.art === 'home') return 'Home';
|
|
168
|
+
if (ort.art === 'profil') return 'Profil „' + ort.name + '“';
|
|
169
|
+
return 'Arbeitsverzeichnis ' + pfadKurz(ort.pfad);
|
|
170
|
+
}
|
|
171
|
+
function zielTitel(z) { return harnessName(z.ziel.harness) + ' · ' + ortName(z.ziel.ort); }
|
|
172
|
+
function zielKey(ziel) {
|
|
173
|
+
var o = ziel.ort;
|
|
174
|
+
var ort = o.art === 'home' ? 'home' : o.art === 'profil' ? 'profil:' + o.name : 'dir:' + o.pfad;
|
|
175
|
+
return ziel.harness + '/' + ort;
|
|
176
|
+
}
|
|
177
|
+
function quelleKurz(quelle) { return quelle.art === 'git' ? quelle.url : quelle.pfad; }
|
|
178
|
+
/**
|
|
179
|
+
* Adresse einer Quelle als Text, egal woher sie kommt: aus dem Ist-Zustand trägt sie `url` bzw.
|
|
180
|
+
* `pfad`, aus `hmgr:quellen` (Einstellungen) nur das schon fertige Feld `text`. Beides ist
|
|
181
|
+
* dasselbe (`quelleAlsText`), aber nur so findet auch die Einstellungen-Liste ihr Icon.
|
|
182
|
+
*/
|
|
183
|
+
function quelleAdresse(quelle) { return quelle.text != null ? quelle.text : quelleKurz(quelle); }
|
|
184
|
+
/**
|
|
185
|
+
* Letztes nicht leeres Segment eines Pfads oder einer URL, ohne abschließende Schrägstriche.
|
|
186
|
+
* Trenner sind `/` und `\`, wie in `letztesSegment` im Kern (E65/B8, dort führend).
|
|
187
|
+
*/
|
|
188
|
+
function letztesSegment(text) {
|
|
189
|
+
var bereinigt = text.replace(/[/\\]+$/, '');
|
|
190
|
+
var segmente = bereinigt.split(/[/\\]/).filter(function (s) { return s.length > 0; });
|
|
191
|
+
return segmente.length > 0 ? segmente[segmente.length - 1] : bereinigt;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Anzeigename einer Quelle (E59): der gespeicherte Name (Feld `quelle.name`, kommt über IPC mit,
|
|
195
|
+
* wo der Main-Prozess ihn nicht schon direkt liefert), sonst abgeleitet wie im Kern
|
|
196
|
+
* (`quelleName`/`quelleNameAbleiten` in modell.ts, dort führend): letztes Pfadsegment der URL
|
|
197
|
+
* ohne `.git`-Suffix, bei einer lokalen Quelle der Ordnername. Kleine Kopie, weil der
|
|
198
|
+
* Renderer-Prozess den Kern nicht importieren kann (kein Node-Zugriff, contextIsolation).
|
|
199
|
+
*/
|
|
200
|
+
function quelleAnzeigename(quelle) {
|
|
201
|
+
if (quelle.name) return quelle.name;
|
|
202
|
+
if (quelle.art === 'lokal') return letztesSegment(quelle.pfad);
|
|
203
|
+
var ohneSchraeg = quelle.url.replace(/\/+$/, '');
|
|
204
|
+
var ohneGit = ohneSchraeg.endsWith('.git') ? ohneSchraeg.slice(0, -4) : ohneSchraeg;
|
|
205
|
+
return letztesSegment(ohneGit);
|
|
206
|
+
}
|
|
207
|
+
/** Ein `<span>` mit dem Anzeigenamen der Quelle, die volle Adresse als title-Tooltip (E59). */
|
|
208
|
+
function quelleSpan(klasse, quelle, rest, praefix) {
|
|
209
|
+
var el = e('span', klasse, (praefix || '') + quelleAnzeigename(quelle) + (rest || ''));
|
|
210
|
+
el.title = quelleKurz(quelle);
|
|
211
|
+
return el;
|
|
212
|
+
}
|
|
213
|
+
/* ---------------- Quellen: Icon und Sprung nach draussen (E60) ---------------- */
|
|
214
|
+
|
|
215
|
+
/** Das kleine Pfeilsymbol hinter einem Quellennamen: „das geht nach draussen". */
|
|
216
|
+
function externSvg() {
|
|
217
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
218
|
+
svg.setAttribute('viewBox', '0 0 12 12');
|
|
219
|
+
svg.setAttribute('class', 'extern');
|
|
220
|
+
svg.setAttribute('fill', 'none');
|
|
221
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
222
|
+
svg.setAttribute('stroke-width', '1.6');
|
|
223
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
224
|
+
svg.innerHTML = '<path d="M3.6 8.4 8.4 3.6"/><path d="M4.9 3.6h3.5v3.5"/>';
|
|
225
|
+
return svg;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Generisches Ordner-/Repo-Symbol (E60 F35): lokale Quellen und jeder Icon-Fehlschlag. */
|
|
229
|
+
function repoSvgGenerisch() {
|
|
230
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
231
|
+
svg.setAttribute('viewBox', '0 0 20 20');
|
|
232
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
233
|
+
svg.innerHTML = '<rect width="20" height="20" rx="5" fill="#FFFFFF"/>'
|
|
234
|
+
+ '<path d="M4.2 14.3V5.4h4.1l1.3 1.8h6.2v7.1z" fill="none" stroke="#4E5B66" stroke-width="1.5" stroke-linejoin="round"/>';
|
|
235
|
+
return svg;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Icon-Cache im Renderer, Schlüssel wie quelleKurz: dataURL nach Erfolg, null nach Fehlschlag
|
|
239
|
+
oder bei einer lokalen Quelle, sonst ein noch laufendes Promise. Verhindert doppelte
|
|
240
|
+
IPC-Aufrufe für dieselbe Quelle, wenn sie in mehreren Zeilen auftaucht. */
|
|
241
|
+
var ICON_CACHE = {};
|
|
242
|
+
function ladeIcon(quelleText) {
|
|
243
|
+
var vorhanden = ICON_CACHE[quelleText];
|
|
244
|
+
if (vorhanden !== undefined) return vorhanden instanceof Promise ? vorhanden : Promise.resolve(vorhanden);
|
|
245
|
+
var p = window.hmgr.icon(quelleText).then(function (ergebnis) {
|
|
246
|
+
var url = ergebnis && ergebnis.ok ? ergebnis.daten : null;
|
|
247
|
+
ICON_CACHE[quelleText] = url;
|
|
248
|
+
return url;
|
|
249
|
+
}).catch(function () {
|
|
250
|
+
ICON_CACHE[quelleText] = null;
|
|
251
|
+
return null;
|
|
252
|
+
});
|
|
253
|
+
ICON_CACHE[quelleText] = p;
|
|
254
|
+
return p;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Das 20px-Icon einer Quelle (E60): erst generisch, tauscht asynchron, sobald geladen. Blockiert
|
|
258
|
+
nie den Aufbau der Liste. Lokale Quellen fragen gar nicht erst ab. */
|
|
259
|
+
function repoIcon(quelle) {
|
|
260
|
+
var span = e('span', 'repo');
|
|
261
|
+
span.appendChild(repoSvgGenerisch());
|
|
262
|
+
if (quelle.art === 'lokal') return span;
|
|
263
|
+
ladeIcon(quelleAdresse(quelle)).then(function (url) {
|
|
264
|
+
if (!url || !span.isConnected) return;
|
|
265
|
+
span.textContent = '';
|
|
266
|
+
var img = document.createElement('img');
|
|
267
|
+
img.src = url;
|
|
268
|
+
img.alt = '';
|
|
269
|
+
img.width = 20;
|
|
270
|
+
img.height = 20;
|
|
271
|
+
img.setAttribute('aria-hidden', 'true');
|
|
272
|
+
span.appendChild(img);
|
|
273
|
+
});
|
|
274
|
+
return span;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Eine Quelle, angeschrieben mit ihrem Namen und anklickbar (E60): keine Unterstreichung, das
|
|
279
|
+
* kleine Pfeilsymbol dahinter trägt das Signalorange. Klick öffnet über den Main-Prozess die
|
|
280
|
+
* Repo-Seite im Browser oder den Ordner im Dateimanager (`window.hmgr.quelleOeffnen`), still im
|
|
281
|
+
* Fehlerfall statt in der Meldungsleiste, ein Öffnen-Fehlschlag soll die Ansicht nicht stören.
|
|
282
|
+
*/
|
|
283
|
+
function quelleLinkElement(quelle) {
|
|
284
|
+
var lokal = quelle.art === 'lokal';
|
|
285
|
+
var el = document.createElement(lokal ? 'button' : 'a');
|
|
286
|
+
if (lokal) el.type = 'button'; else el.href = '#';
|
|
287
|
+
el.className = 'qlink';
|
|
288
|
+
el.title = quelleAdresse(quelle);
|
|
289
|
+
// „Dateimanager" statt „Finder" (E68): die GUI läuft auch unter Windows, wo das Ding Explorer
|
|
290
|
+
// heißt, und unter Linux gibt es weder das eine noch das andere.
|
|
291
|
+
el.setAttribute('aria-label', quelleAnzeigename(quelle) + ' ' + (lokal ? 'im Dateimanager öffnen' : 'im Browser öffnen'));
|
|
292
|
+
el.appendChild(document.createTextNode(quelleAnzeigename(quelle)));
|
|
293
|
+
el.appendChild(externSvg());
|
|
294
|
+
el.addEventListener('click', function (ev) {
|
|
295
|
+
ev.preventDefault();
|
|
296
|
+
window.hmgr.quelleOeffnen(quelleAdresse(quelle)).catch(function () {});
|
|
297
|
+
});
|
|
298
|
+
return el;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* ---------------- Zustand (im Renderer gehalten) ---------------- */
|
|
302
|
+
|
|
303
|
+
var ZIELE = [];
|
|
304
|
+
var SKILLS = [];
|
|
305
|
+
var MARKETPLACES = [];
|
|
306
|
+
var QUELLEN = [];
|
|
307
|
+
var HARNESSE_ALLE = [];
|
|
308
|
+
var AKTIVE_HARNESSE = [];
|
|
309
|
+
var HARNESSE_NICHT_INSTALLIERT = [];
|
|
310
|
+
// MCP-Katalog (E61-Folgerunde): je Eintrag { eintrag, eingehaengt, fehlendeVariablen }, dazu
|
|
311
|
+
// Einträge in Harness-Konfigurationen, die nicht im Katalog stehen (nur genannt). Seit F46 nur
|
|
312
|
+
// noch Grundlage für den MCP-Abschnitt der Ziel-Karten (mcpZeilenFuerZiel), die MCP-Ansicht selbst
|
|
313
|
+
// zeigt keine Fundorte mehr.
|
|
314
|
+
var MCP_KATALOG = [];
|
|
315
|
+
var MCP_FREMD = [];
|
|
316
|
+
// Nicht-Katalog-Einträge aus geteilten, nur lesbaren Fundorten (E61-Nachtrag), etwa der
|
|
317
|
+
// .mcp.json einer Projektwurzel.
|
|
318
|
+
var MCP_FREMD_GETEILT = [];
|
|
319
|
+
// Alle Orte, an denen grundsätzlich ein MCP-Server hängen könnte (E61-GUI-Nachtrag), roh aus dem
|
|
320
|
+
// Kern (F42, E16): Grundlage für den MCP-Abschnitt und den „MCP hinzufügen …"-Knopf an den
|
|
321
|
+
// Ziel-Karten — nie am Harness-Namen entschieden, sondern an dieser Liste.
|
|
322
|
+
var MCP_PLAETZE = [];
|
|
323
|
+
var quellenFilter = 'alle';
|
|
324
|
+
var zielFilter = 'alle';
|
|
325
|
+
// Standard-Ref je Quelle (E60), aus dem Katalog abgeleitet (SKILLS trägt ihn schon je Skill): ein
|
|
326
|
+
// Ref an einem Link erscheint nur, wenn er davon abweicht (`@v2.1`). Schlüssel wie quelleKurz.
|
|
327
|
+
var STANDARD_REF = {};
|
|
328
|
+
|
|
329
|
+
function harnessAktiv(h) { return AKTIVE_HARNESSE.indexOf(h) > -1; }
|
|
330
|
+
|
|
331
|
+
// Ignorierte fremde Einträge zählen hier nicht mit (E43): Ignorieren blendet die Warnung aus,
|
|
332
|
+
// sonst bliebe das Ziel im Filter „braucht Aufmerksamkeit" stehen.
|
|
333
|
+
function zielBraucht(z) {
|
|
334
|
+
var ignoriert = z.ignoriert || [];
|
|
335
|
+
return z.eintraege.some(function (ei) {
|
|
336
|
+
if (ei.art === 'fremd') return ignoriert.indexOf(ei.name) === -1;
|
|
337
|
+
return ei.art === 'link-ohne-quelle';
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// E45: ein Ziel eines inaktiven Harness verschwindet, sobald dort nichts liegt, weder eigene noch
|
|
342
|
+
// fremde Einträge noch eine harness-eigene Registrierung. Ein aktiver Harness zeigt sein Ziel immer.
|
|
343
|
+
function zielSichtbar(z) {
|
|
344
|
+
if (harnessAktiv(z.ziel.harness)) return true;
|
|
345
|
+
return z.eintraege.length > 0 || z.istRegistriert;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Von Hand aufgeklappte Ziele bleiben über Neuaufbauten der Liste offen (E45), Schlüssel wie
|
|
349
|
+
// zielKey. Verzeichnis-Karten (E49) nutzen stattdessen verzKey (reiner Pfad, kollidiert nicht mit
|
|
350
|
+
// zielKey, der immer mit "harness/" beginnt). Nicht persistiert, nur für die Dauer der Sitzung.
|
|
351
|
+
var ZIEL_OFFEN = {};
|
|
352
|
+
function verzKey(pfad) { return 'dir:' + pfad; }
|
|
353
|
+
|
|
354
|
+
// Aktiver Harness-Chip je Verzeichnis-Karte (E53), gleicher Schlüssel wie ZIEL_OFFEN
|
|
355
|
+
// (verzKey(pfad)); fehlt der Eintrag oder zeigt er auf einen nicht mehr beteiligten Harness,
|
|
356
|
+
// gilt „alle". Nicht persistiert, nur für die Dauer der Sitzung.
|
|
357
|
+
var VERZ_CHIP = {};
|
|
358
|
+
|
|
359
|
+
// Aufgeklappte Skill-Details im Katalog (F10), Schlüssel Quelle + Pfad: der Index in SKILLS
|
|
360
|
+
// verschiebt sich, wenn ladeAlles Quellen oder Skills anders sortiert oder eine wegfällt.
|
|
361
|
+
// Nicht persistiert, nur für die Dauer der Sitzung.
|
|
362
|
+
var SKILL_OFFEN = {};
|
|
363
|
+
function skillKey(skill) { return skill.quelleSchluessel + '|' + skill.pfad; }
|
|
364
|
+
// Ein Chip-Klick baut die ganze Liste neu und zerstört dabei den geklickten Knopf. Damit die
|
|
365
|
+
// Tastatur-Bedienung nicht am Seitenanfang landet, merkt sich der Klick, welcher Chip nach dem
|
|
366
|
+
// Neuaufbau den Fokus bekommt.
|
|
367
|
+
var CHIP_FOKUS = null;
|
|
368
|
+
/** Schlüssel der Karte, in der ein Ziel steckt: Arbeitsverzeichnisse sind gebündelt (E49). */
|
|
369
|
+
function kartenKey(ziel) {
|
|
370
|
+
return ziel.ort.art === 'arbeitsverzeichnis' ? verzKey(ziel.ort.pfad) : zielKey(ziel);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Laufende Nummer für die ids der Körper: aus dem Schlüssel abgeleitete ids kollidieren, sobald
|
|
374
|
+
// sich zwei Verzeichnisse nur in Zeichen unterscheiden, die eine id nicht tragen kann.
|
|
375
|
+
var zielKoerperNr = 0;
|
|
376
|
+
|
|
377
|
+
/* ---------------- Laden ---------------- */
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Home-Verzeichnis und Plattform einmal beim Start holen (nur für die Anzeige, siehe oben).
|
|
381
|
+
* Danach stehen die fest formulierten Pfade im Bild in der Schreibweise der Plattform (E68).
|
|
382
|
+
*/
|
|
383
|
+
async function ladeUmgebung() {
|
|
384
|
+
var werte = await Promise.all([
|
|
385
|
+
rufe(function () { return window.hmgr.home(); }),
|
|
386
|
+
rufe(function () { return window.hmgr.plattform(); }),
|
|
387
|
+
]);
|
|
388
|
+
HOME = werte[0];
|
|
389
|
+
TRENNER = plattformTrenner(werte[1]);
|
|
390
|
+
$('mcpkatalogpfad').textContent = pfadSchreibweise('~/.hmgr/mcp.yaml');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function ladeAlles() {
|
|
394
|
+
var version = await rufe(function () { return window.hmgr.version(); });
|
|
395
|
+
$('version').textContent = version;
|
|
396
|
+
|
|
397
|
+
var ergebnisse = await Promise.all([
|
|
398
|
+
rufe(function () { return window.hmgr.istZustand(); }),
|
|
399
|
+
rufe(function () { return window.hmgr.skills(); }),
|
|
400
|
+
rufe(function () { return window.hmgr.marketplaces(); }),
|
|
401
|
+
rufe(function () { return window.hmgr.quellen(); }),
|
|
402
|
+
rufe(function () { return window.hmgr.harnessTypen(); }),
|
|
403
|
+
rufe(function () { return window.hmgr.aktiveHarnesses(); }),
|
|
404
|
+
rufe(function () { return window.hmgr.nichtVorhandeneHarnesses(); }),
|
|
405
|
+
rufe(function () { return window.hmgr.mcpUebersicht(); }),
|
|
406
|
+
rufe(function () { return window.hmgr.mcpPlaetze(); }),
|
|
407
|
+
]);
|
|
408
|
+
ZIELE = ergebnisse[0];
|
|
409
|
+
SKILLS = ergebnisse[1];
|
|
410
|
+
// Eine alphabetische Liste über alle Quellen; wer nach Quelle sucht, hat die Filter-Chips.
|
|
411
|
+
SKILLS.sort(function (a, b) { return a.name.localeCompare(b.name, 'de') || a.quelleText.localeCompare(b.quelleText, 'de'); });
|
|
412
|
+
MARKETPLACES = ergebnisse[2];
|
|
413
|
+
QUELLEN = ergebnisse[3];
|
|
414
|
+
HARNESSE_ALLE = ergebnisse[4];
|
|
415
|
+
AKTIVE_HARNESSE = ergebnisse[5];
|
|
416
|
+
HARNESSE_NICHT_INSTALLIERT = ergebnisse[6];
|
|
417
|
+
MCP_KATALOG = ergebnisse[7].katalog;
|
|
418
|
+
MCP_FREMD = ergebnisse[7].unbekannt;
|
|
419
|
+
MCP_FREMD_GETEILT = ergebnisse[7].unbekannteGeteilteFundorte;
|
|
420
|
+
MCP_PLAETZE = ergebnisse[8];
|
|
421
|
+
STANDARD_REF = {};
|
|
422
|
+
SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
|
|
423
|
+
|
|
424
|
+
baueZiele();
|
|
425
|
+
baueSkills();
|
|
426
|
+
baueQuellenfilter();
|
|
427
|
+
baueMarketplaces();
|
|
428
|
+
baueEinstellungen();
|
|
429
|
+
baueMcp();
|
|
430
|
+
aktualisiereKopf();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Karten, wie die Zielliste sie zeigt: je Agent-Ziel eine, je Arbeitsverzeichnis eine gebündelte
|
|
434
|
+
(E49). Grundlage für "N von M Karten gezeigt" und den Zähler am Achtung-Chip. */
|
|
435
|
+
function zielKarten() {
|
|
436
|
+
var sichtbar = ZIELE.filter(zielSichtbar);
|
|
437
|
+
var agents = sichtbar.filter(function (z) { return z.ziel.ort.art !== 'arbeitsverzeichnis'; });
|
|
438
|
+
var gruppen = {};
|
|
439
|
+
sichtbar.forEach(function (z) {
|
|
440
|
+
if (z.ziel.ort.art !== 'arbeitsverzeichnis') return;
|
|
441
|
+
var pfad = z.ziel.ort.pfad;
|
|
442
|
+
(gruppen[pfad] = gruppen[pfad] || []).push(z);
|
|
443
|
+
});
|
|
444
|
+
var verzeichnisse = Object.keys(gruppen).map(function (pfad) { return { pfad: pfad, teile: gruppen[pfad] }; });
|
|
445
|
+
return { agents: agents, verzeichnisse: verzeichnisse };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Kopfzahl und Nav-Zahl tragen beide das Wort "Ziele" und stehen gleichzeitig im Bild, also zählen
|
|
449
|
+
// beide im Kern-Sinn: jedes Harness+Ort-Paar einzeln, unabhängig von der Bündelung. Karten zählt
|
|
450
|
+
// nur, was sie auch so nennt: "N von M Karten gezeigt" und der Achtung-Chip (E49).
|
|
451
|
+
function aktualisiereKopf() {
|
|
452
|
+
var sichtbar = ZIELE.filter(zielSichtbar);
|
|
453
|
+
var linkZahl = 0, fremdZahl = 0;
|
|
454
|
+
sichtbar.forEach(function (z) {
|
|
455
|
+
var ignoriert = z.ignoriert || [];
|
|
456
|
+
z.eintraege.forEach(function (ei) {
|
|
457
|
+
if (ei.art === 'fremd') { if (ignoriert.indexOf(ei.name) === -1) fremdZahl++; }
|
|
458
|
+
else linkZahl++;
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
$('kopfzahlen').textContent = zahlwort(sichtbar.length, 'Ziel', 'Ziele') + ' · ' +
|
|
462
|
+
zahlwort(linkZahl, 'Link', 'Links') + ' · ' +
|
|
463
|
+
zahlwort(fremdZahl, 'nicht von hmgr verwalteter Eintrag', 'nicht von hmgr verwaltete Einträge');
|
|
464
|
+
$('navzahlziele').textContent = sichtbar.length;
|
|
465
|
+
$('navzahlskills').textContent = SKILLS.length;
|
|
466
|
+
var pluginZahl = MARKETPLACES.reduce(function (n, m) { return n + m.plugins.length; }, 0);
|
|
467
|
+
$('navzahlplugins').textContent = pluginZahl;
|
|
468
|
+
|
|
469
|
+
var karten = zielKarten();
|
|
470
|
+
// Betroffene Karten für den Chip-Zähler: eine Verzeichnis-Karte zählt, wenn mindestens ein
|
|
471
|
+
// Abschnitt (Harness) darin zielBraucht ist.
|
|
472
|
+
var betroffen = karten.agents.filter(zielBraucht).length
|
|
473
|
+
+ karten.verzeichnisse.filter(function (g) { return g.teile.some(zielBraucht); }).length;
|
|
474
|
+
var achtungChip = document.querySelector('#ansicht-ziele .chip[data-zielfilter="achtung"]');
|
|
475
|
+
if (achtungChip) achtungChip.textContent = 'Benötigt Aufmerksamkeit (' + betroffen + ')';
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* ---------------- Navigation ---------------- */
|
|
479
|
+
|
|
480
|
+
var ANSICHTEN = ['ziele', 'katalog', 'update', 'plugins', 'mcp'];
|
|
481
|
+
function zeigeAnsicht(name) {
|
|
482
|
+
document.querySelectorAll('.plakette').forEach(function (a) {
|
|
483
|
+
if (a.dataset.ansicht === name) a.setAttribute('aria-current', 'page');
|
|
484
|
+
else a.removeAttribute('aria-current');
|
|
485
|
+
});
|
|
486
|
+
ANSICHTEN.forEach(function (x) { $('ansicht-' + x).hidden = x !== name; });
|
|
487
|
+
// Der Bericht ist jetzt sichtbar, die Ankündigung „mit Neuem" hat ihren Dienst getan.
|
|
488
|
+
if (name === 'update' && standZeit !== null) standRuhig();
|
|
489
|
+
window.scrollTo(0, 0);
|
|
490
|
+
}
|
|
491
|
+
document.querySelectorAll('[data-ansicht]').forEach(function (k) {
|
|
492
|
+
k.addEventListener('click', function () { zeigeAnsicht(k.dataset.ansicht); });
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
/* ================= ZIELE ================= */
|
|
496
|
+
|
|
497
|
+
/** Hinweiszeile, wenn ein inaktiver Harness noch Links oder eine eigene Registrierung an diesem
|
|
498
|
+
Ziel hat (E38 R7): Säubern-Weg direkt in der Karte. Von linkTeil und alleTeil geteilt. */
|
|
499
|
+
function ruheZeile(z, hatEigene) {
|
|
500
|
+
var rz = e('div', 'ruhezeile');
|
|
501
|
+
var rsvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
502
|
+
rsvg.setAttribute('viewBox', '0 0 16 16');
|
|
503
|
+
rsvg.setAttribute('stroke-width', '1.6');
|
|
504
|
+
rsvg.setAttribute('aria-hidden', 'true');
|
|
505
|
+
rsvg.innerHTML = '<circle cx="8" cy="8" r="6.5"/><path d="M8 4.5v4M8 10.5v.5"/>';
|
|
506
|
+
rz.appendChild(rsvg);
|
|
507
|
+
rz.appendChild(e('p', null, harnessName(z.ziel.harness) + ' ist inaktiv. '
|
|
508
|
+
+ (hatEigene ? 'hmgr pflegt diese Links nicht mehr.' : 'Ein harness-eigener Eintrag verweist noch auf dieses Ziel.')));
|
|
509
|
+
var rk = e('button', 'knopf leise klein', 'Säubern');
|
|
510
|
+
rk.type = 'button';
|
|
511
|
+
rk.setAttribute('aria-label', harnessName(z.ziel.harness) + ' säubern');
|
|
512
|
+
rk.addEventListener('click', function () { frageSaeubern(z.ziel.harness); });
|
|
513
|
+
rz.appendChild(rk);
|
|
514
|
+
return rz;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Eine Zeile im Fremdkasten für einen fremden Eintrag x an einem Ziel z. Mit mitZeichen=true
|
|
518
|
+
steht davor das kleine Harness-Zeichen seines Fundorts (E53 Pkt. 5, „Alle"-Ansicht). Gibt
|
|
519
|
+
neben dem li auch zurück, ob eine Quelle erkannt wurde (für den Erklärtext). */
|
|
520
|
+
function fremdZeile(x, z, mitZeichen) {
|
|
521
|
+
var prognosen = z.uebernahme || {};
|
|
522
|
+
var prognose = prognosen[x.name];
|
|
523
|
+
var erkannt = prognose && prognose.art === 'erkannt';
|
|
524
|
+
var li = e('li');
|
|
525
|
+
var kopf = e('span', 'fremdkopf');
|
|
526
|
+
// Nur die „Alle"-Ansicht braucht die Zeile als Flex-Kasten neben dem Harness-Zeichen. Unter
|
|
527
|
+
// einem Harness-Chip bleibt es der schlichte Span von vorher: ein Flex-Kasten bricht lange
|
|
528
|
+
// Namen anders um als der Fließtext.
|
|
529
|
+
var namezeile = e('span', mitZeichen ? 'fremdname' : null);
|
|
530
|
+
if (mitZeichen) namezeile.appendChild(harnesseZeichenSammel([z.ziel.harness]));
|
|
531
|
+
namezeile.appendChild(document.createTextNode(x.name));
|
|
532
|
+
kopf.appendChild(namezeile);
|
|
533
|
+
if (erkannt) kopf.appendChild(quelleSpan('mono', prognose.quelle, ' @ ' + prognose.ref));
|
|
534
|
+
li.appendChild(kopf);
|
|
535
|
+
var knoepfe = e('span', 'fremdknoepfe');
|
|
536
|
+
if (erkannt) {
|
|
537
|
+
var ue = e('button', 'knopf leise klein', 'Übernehmen');
|
|
538
|
+
ue.type = 'button';
|
|
539
|
+
ue.setAttribute('aria-label', x.name + ' übernehmen');
|
|
540
|
+
ue.addEventListener('click', function () { oeffneUebernahme(z.ziel, x.name); });
|
|
541
|
+
knoepfe.appendChild(ue);
|
|
542
|
+
}
|
|
543
|
+
var ig = e('button', 'knopf leise klein', 'Ignorieren');
|
|
544
|
+
ig.type = 'button';
|
|
545
|
+
ig.setAttribute('aria-label', x.name + ' ignorieren');
|
|
546
|
+
ig.addEventListener('click', async function () {
|
|
547
|
+
await rufe(function () {
|
|
548
|
+
return window.hmgr.eintragIgnorieren({ harness: z.ziel.harness, ort: z.ziel.ort, name: x.name });
|
|
549
|
+
});
|
|
550
|
+
await ladeAlles();
|
|
551
|
+
});
|
|
552
|
+
knoepfe.appendChild(ig);
|
|
553
|
+
var lo = e('button', 'knopf leise klein', 'Löschen');
|
|
554
|
+
lo.type = 'button';
|
|
555
|
+
lo.setAttribute('aria-label', x.name + ' löschen');
|
|
556
|
+
lo.addEventListener('click', function () { frageLoeschen(x.name, z.ziel, z.verzeichnis); });
|
|
557
|
+
knoepfe.appendChild(lo);
|
|
558
|
+
li.appendChild(knoepfe);
|
|
559
|
+
// Ohne erkannte Quelle: Ursache und Handreichungen direkt im Kasten statt eines Vorschau-Dialogs (E43, E48).
|
|
560
|
+
if (!erkannt) {
|
|
561
|
+
var grundInfo = UEBERNAHME_GRUENDE[prognose ? prognose.grund : 'kein-link'];
|
|
562
|
+
if (grundInfo) {
|
|
563
|
+
var ursache = e('div', 'fremdursache');
|
|
564
|
+
ursache.appendChild(e('p', 'mono', grundInfo.ursache));
|
|
565
|
+
if (grundInfo.tipps && grundInfo.tipps.length > 0) {
|
|
566
|
+
var tippbox = e('div', 'tipps');
|
|
567
|
+
tippbox.appendChild(e('p', 'tippkopf', 'Außerhalb von hmgr:'));
|
|
568
|
+
var tul = e('ul', 'tippliste');
|
|
569
|
+
grundInfo.tipps.forEach(function (t) { tul.appendChild(e('li', null, '→ ' + t)); });
|
|
570
|
+
tippbox.appendChild(tul);
|
|
571
|
+
ursache.appendChild(tippbox);
|
|
572
|
+
}
|
|
573
|
+
li.appendChild(ursache);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return { li: li, erkannt: erkannt };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Fremdkasten über eine Liste von {x, z}-Paaren (ein Ziel bei linkTeil, mehrere bei alleTeil). */
|
|
580
|
+
function fremdKasten(paare, mitZeichen) {
|
|
581
|
+
var kasten = e('div', 'fremdkasten');
|
|
582
|
+
var text = e('div', 'fremdtext');
|
|
583
|
+
var p = e('p');
|
|
584
|
+
p.appendChild(e('strong', null, paare.length + (paare.length === 1
|
|
585
|
+
? ' nicht von hmgr verwalteter Eintrag'
|
|
586
|
+
: ' nicht von hmgr verwaltete Einträge')));
|
|
587
|
+
text.appendChild(p);
|
|
588
|
+
var namen = e('ul', 'fremdnamen');
|
|
589
|
+
var einErkannt = false;
|
|
590
|
+
paare.forEach(function (paar) {
|
|
591
|
+
var r = fremdZeile(paar.x, paar.z, mitZeichen);
|
|
592
|
+
if (r.erkannt) einErkannt = true;
|
|
593
|
+
namen.appendChild(r.li);
|
|
594
|
+
});
|
|
595
|
+
text.appendChild(namen);
|
|
596
|
+
// Der Erklärtext nennt Übernehmen nur, wo der Knopf auch steht (E48).
|
|
597
|
+
text.appendChild(e('p', 'fremderklaerung',
|
|
598
|
+
(einErkannt ? 'Übernehmen zeigt zuerst eine Vorschau und macht daraus einen Link auf die Quelle. ' : '')
|
|
599
|
+
+ (paare.length === 1
|
|
600
|
+
? 'Ignorieren blendet ihn aus, er bleibt liegen. Löschen entfernt ihn endgültig von der Platte.'
|
|
601
|
+
: 'Ignorieren blendet einen Eintrag aus, er bleibt liegen. Löschen entfernt ihn endgültig von der Platte.')));
|
|
602
|
+
kasten.appendChild(text);
|
|
603
|
+
return kasten;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Zeile ignorierter fremder Einträge über eine Liste von {x, z}-Paaren, analog fremdKasten. */
|
|
607
|
+
function ignoriertZeile(paare, mitZeichen) {
|
|
608
|
+
var izeile = e('div', 'ignoriertzeile');
|
|
609
|
+
izeile.appendChild(e('span', 'ignoriertzahl', paare.length + ' ignoriert'));
|
|
610
|
+
paare.forEach(function (paar) {
|
|
611
|
+
var x = paar.x, z = paar.z;
|
|
612
|
+
var spanne = e('span', 'ignoriertname');
|
|
613
|
+
if (mitZeichen) spanne.appendChild(harnesseZeichenSammel([z.ziel.harness]));
|
|
614
|
+
spanne.appendChild(e('span', null, x.name));
|
|
615
|
+
var wb = e('button', 'knopf leise klein', 'Wieder beachten');
|
|
616
|
+
wb.type = 'button';
|
|
617
|
+
wb.setAttribute('aria-label', x.name + ' wieder beachten');
|
|
618
|
+
wb.addEventListener('click', async function () {
|
|
619
|
+
await rufe(function () {
|
|
620
|
+
return window.hmgr.eintragBeachten({ harness: z.ziel.harness, ort: z.ziel.ort, name: x.name });
|
|
621
|
+
});
|
|
622
|
+
await ladeAlles();
|
|
623
|
+
});
|
|
624
|
+
spanne.appendChild(wb);
|
|
625
|
+
izeile.appendChild(spanne);
|
|
626
|
+
});
|
|
627
|
+
return izeile;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** Sichtbare Konfiguration eines Ziels mit der Fähigkeit „verwalteter Skill-Ordner": bei Hermes
|
|
631
|
+
der external_dirs-Ordner, zum Festlegen oder Entfernen (E56). Ob ein Ziel das kann, sagt der
|
|
632
|
+
Kern mit kannSkillOrdner; der Harness-Name ist hier kein Kriterium (E16). */
|
|
633
|
+
function konfigZeile(z) {
|
|
634
|
+
if (!z.kannSkillOrdner) return null;
|
|
635
|
+
var kz = e('div', 'konfigzeile');
|
|
636
|
+
if (z.verwalteterSkillOrdner) {
|
|
637
|
+
kz.appendChild(e('b', null, 'Liest Skills aus'));
|
|
638
|
+
kz.appendChild(e('span', 'mono', pfadKurz(z.verwalteterSkillOrdner)));
|
|
639
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen …');
|
|
640
|
+
weg.type = 'button';
|
|
641
|
+
weg.setAttribute('aria-label', 'Skill-Ordner für ' + zielTitel(z) + ' entfernen');
|
|
642
|
+
weg.addEventListener('click', function () { frageSkillOrdnerEntfernen(z); });
|
|
643
|
+
kz.appendChild(weg);
|
|
644
|
+
} else {
|
|
645
|
+
kz.appendChild(e('span', 'konfigquelle', 'Kein Skill-Ordner verwaltet.'));
|
|
646
|
+
var fest = e('button', 'knopf leise klein', 'Skill-Ordner festlegen …');
|
|
647
|
+
fest.type = 'button';
|
|
648
|
+
fest.setAttribute('aria-label', 'Skill-Ordner für ' + zielTitel(z) + ' festlegen');
|
|
649
|
+
fest.addEventListener('click', function () { oeffneSkillOrdnerFestlegen(z); });
|
|
650
|
+
kz.appendChild(fest);
|
|
651
|
+
}
|
|
652
|
+
return kz;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function linkTeil(z) {
|
|
656
|
+
var teil = document.createDocumentFragment();
|
|
657
|
+
var eigene = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; });
|
|
658
|
+
var fremde = z.eintraege.filter(function (ei) { return ei.art === 'fremd'; });
|
|
659
|
+
|
|
660
|
+
// Harness abgeschaltet, Konfiguration liegt noch (E38, GRILL.md Entwurfskritik 4): der Weg,
|
|
661
|
+
// sie loszuwerden, steht hier oben in der Karte. Zählt auch ohne Links, wenn die harness-eigene
|
|
662
|
+
// Registrierung noch da ist (E38 R7), etwa Hermes' external_dirs-Eintrag ohne Links darunter.
|
|
663
|
+
if (!harnessAktiv(z.ziel.harness) && (eigene.length > 0 || z.istRegistriert)) {
|
|
664
|
+
teil.appendChild(ruheZeile(z, eigene.length > 0));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
var kz = konfigZeile(z);
|
|
668
|
+
if (kz) teil.appendChild(kz);
|
|
669
|
+
|
|
670
|
+
if (fremde.length > 0) {
|
|
671
|
+
var ignoriertNamen = z.ignoriert || [];
|
|
672
|
+
var beachtete = fremde.filter(function (x) { return ignoriertNamen.indexOf(x.name) === -1; });
|
|
673
|
+
var ignorierte = fremde.filter(function (x) { return ignoriertNamen.indexOf(x.name) !== -1; });
|
|
674
|
+
|
|
675
|
+
if (beachtete.length > 0) {
|
|
676
|
+
teil.appendChild(fremdKasten(beachtete.map(function (x) { return { x: x, z: z }; }), false));
|
|
677
|
+
}
|
|
678
|
+
if (ignorierte.length > 0) {
|
|
679
|
+
teil.appendChild(ignoriertZeile(ignorierte.map(function (x) { return { x: x, z: z }; }), false));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
var fussContainer;
|
|
684
|
+
if (eigene.length === 0) {
|
|
685
|
+
var leer = e('div', 'leerzeile');
|
|
686
|
+
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
687
|
+
teil.appendChild(leer);
|
|
688
|
+
fussContainer = leer;
|
|
689
|
+
} else {
|
|
690
|
+
// Fünf Spalten (E60): Icon, Skill, Beschreibung (die breiteste, sie beantwortet die Frage,
|
|
691
|
+
// wegen der die Tabelle gelesen wird), Quelle, Aktion.
|
|
692
|
+
var kopf = e('div', 'linkkopf');
|
|
693
|
+
kopf.appendChild(e('span'));
|
|
694
|
+
kopf.appendChild(e('span', 'etikett', 'Skill'));
|
|
695
|
+
kopf.appendChild(e('span', 'etikett', 'Beschreibung'));
|
|
696
|
+
kopf.appendChild(e('span', 'etikett', 'Quelle'));
|
|
697
|
+
kopf.appendChild(e('span'));
|
|
698
|
+
teil.appendChild(kopf);
|
|
699
|
+
|
|
700
|
+
var ul = e('ul', 'linkliste');
|
|
701
|
+
// Defekte Links (ohne Quelle) zuerst, sie brauchen Aufmerksamkeit (E45), sonst alphabetisch.
|
|
702
|
+
eigene
|
|
703
|
+
.slice()
|
|
704
|
+
.sort(function (a, b) {
|
|
705
|
+
var da = a.art === 'link-ohne-quelle', db = b.art === 'link-ohne-quelle';
|
|
706
|
+
if (da !== db) return da ? -1 : 1;
|
|
707
|
+
var na = a.art === 'link' ? a.link.name : a.name;
|
|
708
|
+
var nb = b.art === 'link' ? b.link.name : b.name;
|
|
709
|
+
return na.localeCompare(nb, 'de');
|
|
710
|
+
})
|
|
711
|
+
.forEach(function (ei) {
|
|
712
|
+
var name = ei.art === 'link' ? ei.link.name : ei.name;
|
|
713
|
+
var li = e('li');
|
|
714
|
+
|
|
715
|
+
if (ei.art === 'link') li.appendChild(repoIcon(ei.link.checkout.quelle));
|
|
716
|
+
else li.appendChild(repoIcon({ art: 'lokal', pfad: ei.checkoutPfad }));
|
|
717
|
+
|
|
718
|
+
li.appendChild(e('span', 'name', name));
|
|
719
|
+
|
|
720
|
+
// Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
|
|
721
|
+
// deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
|
|
722
|
+
li.appendChild(wasZelle(ei.art === 'link' ? ei.link.skill : null));
|
|
723
|
+
|
|
724
|
+
// Quelle: der Name, anklickbar, dahinter nur, was vom Normalfall abweicht — ein Ref, das
|
|
725
|
+
// vom Standard-Ref der Quelle abweicht, und der Grund „über Zeiger" (E57). „Manuell" (der
|
|
726
|
+
// Normalfall) entfällt ersatzlos, ein Plugin-Grund steht stattdessen in der Aktionsspalte.
|
|
727
|
+
var qz = e('span', 'quelle');
|
|
728
|
+
if (ei.art === 'link') {
|
|
729
|
+
var einschraenkung = z.einschraenkungen[name];
|
|
730
|
+
if (einschraenkung) qz.appendChild(document.createTextNode(einschraenkung + ' → '));
|
|
731
|
+
qz.appendChild(quelleLinkElement(ei.link.checkout.quelle));
|
|
732
|
+
var standardRef = STANDARD_REF[quelleKurz(ei.link.checkout.quelle)];
|
|
733
|
+
if (ei.link.checkout.ref && standardRef && ei.link.checkout.ref !== standardRef) {
|
|
734
|
+
qz.appendChild(e('span', 'ref', '@' + ei.link.checkout.ref));
|
|
735
|
+
}
|
|
736
|
+
} else {
|
|
737
|
+
qz.appendChild(e('span', 'mono', pfadKurz(ei.checkoutPfad)));
|
|
738
|
+
}
|
|
739
|
+
var grund = z.gruende[name];
|
|
740
|
+
if (grund && grund.art === 'zeiger') qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
741
|
+
li.appendChild(qz);
|
|
742
|
+
|
|
743
|
+
// Aus einem Plugin verlinkt: kein Entfernen-Knopf, das geht nur über das Plugin — der
|
|
744
|
+
// Grund erklärt hier zugleich, warum der Knopf fehlt.
|
|
745
|
+
if (grund && grund.art === 'plugin') {
|
|
746
|
+
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + grund.plugin.split('@')[0] + '“'));
|
|
747
|
+
} else {
|
|
748
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
749
|
+
weg.type = 'button';
|
|
750
|
+
weg.setAttribute('aria-label', 'Link auf ' + name + ' entfernen');
|
|
751
|
+
weg.addEventListener('click', function () { frageEntfernen(name, z.ziel); });
|
|
752
|
+
li.appendChild(weg);
|
|
753
|
+
}
|
|
754
|
+
ul.appendChild(li);
|
|
755
|
+
});
|
|
756
|
+
teil.appendChild(ul);
|
|
757
|
+
|
|
758
|
+
// MCP-Abschnitt (E61-GUI-Nachtrag): unter der Skill-Link-Tabelle, vor dem Kartenfuß mit den
|
|
759
|
+
// Knöpfen, Gegenrichtung zur MCP-Ansicht.
|
|
760
|
+
var mcpAbschnMitTabelle = mcpAbschnitt(z.ziel);
|
|
761
|
+
if (mcpAbschnMitTabelle) teil.appendChild(mcpAbschnMitTabelle);
|
|
762
|
+
|
|
763
|
+
var fuss = e('div', 'zielfuss');
|
|
764
|
+
teil.appendChild(fuss);
|
|
765
|
+
fussContainer = fuss;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Ohne eigene Links (leer-Fall) steht der MCP-Abschnitt hinter der Leerzeile, die dort schon den
|
|
769
|
+
// Kartenfuß bildet (Nachricht + Knöpfe in einer Zeile, wie überall sonst im leeren Zustand).
|
|
770
|
+
if (eigene.length === 0) {
|
|
771
|
+
var mcpAbschnOhneTabelle = mcpAbschnitt(z.ziel);
|
|
772
|
+
if (mcpAbschnOhneTabelle) teil.appendChild(mcpAbschnOhneTabelle);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
fussContainer.appendChild(skillsKnopf(z));
|
|
776
|
+
var mcpKnopf = mcpHinzufuegenKnopf(z.ziel);
|
|
777
|
+
if (mcpKnopf) fussContainer.appendChild(mcpKnopf);
|
|
778
|
+
|
|
779
|
+
return teil;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** Knopf „Skills hinzufügen" im Körper einer Ziel-Karte (E47), Gegenrichtung zu starteVerlinken.
|
|
783
|
+
Reichweite ist immer dieser eine Harness, das Zeichen steht davor (E53 Pkt. 4, 7, 8). */
|
|
784
|
+
function skillsKnopf(z) {
|
|
785
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'Skills hinzufügen');
|
|
786
|
+
knopf.type = 'button';
|
|
787
|
+
knopf.insertBefore(harnessZeichen(z.ziel.harness), knopf.firstChild);
|
|
788
|
+
knopf.setAttribute('aria-label', 'Skills zu ' + zielTitel(z) + ' hinzufügen (' + harnessName(z.ziel.harness) + ')');
|
|
789
|
+
knopf.addEventListener('click', function () { oeffneSkillsHinzufuegen(z); });
|
|
790
|
+
return knopf;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** Knopf „Skills hinzufügen" für eine Verzeichnis-Karte im Chip „Alle" (E53 Pkt. 4): Reichweite
|
|
794
|
+
sind alle beteiligten Harnesses, das Ordner-Symbol steht davor. */
|
|
795
|
+
function alleSkillsKnopf(pfad, teile) {
|
|
796
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'Skills hinzufügen');
|
|
797
|
+
knopf.type = 'button';
|
|
798
|
+
knopf.insertBefore(ordnerZeichen(), knopf.firstChild);
|
|
799
|
+
knopf.setAttribute('aria-label', 'Skills zu ' + pfadKurz(pfad) + ' hinzufügen (alle Harnesses)');
|
|
800
|
+
knopf.addEventListener('click', function () { oeffneSkillsHinzufuegenAlle(pfad, teile); });
|
|
801
|
+
return knopf;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* ---------------- MCP an der Ziel-Karte (E61-GUI-Nachtrag) ---------------- */
|
|
805
|
+
/* Gegenrichtung zur MCP-Ansicht (E47-Muster): ob ein Harness an einem Ort MCP kann, entscheidet
|
|
806
|
+
nie der Harness-Name (E16), sondern einzig, ob MCP_PLAETZE (roh aus dem Kern, F42) diesen Ort
|
|
807
|
+
nennt. */
|
|
808
|
+
function mcpKannAn(ziel) {
|
|
809
|
+
var zk = zielKey(ziel);
|
|
810
|
+
return MCP_PLAETZE.some(function (p) { return zielKey(p) === zk; });
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/** Die MCP-Zeilen eines Ziels, aus den drei Quellen der MCP-Ansicht zusammengesucht: eingehängte
|
|
814
|
+
Katalog-Einträge (mit Entfernen-Knopf), ihre Fundorte in der geteilten .mcp.json des Projekts
|
|
815
|
+
(E61-Nachtrag, seit E64 mit Übernehmen-Knopf) und Einträge, die dort hängen, aber nicht im
|
|
816
|
+
Katalog stehen (stiller Vermerk). */
|
|
817
|
+
function mcpZeilenFuerZiel(ziel) {
|
|
818
|
+
var zk = zielKey(ziel);
|
|
819
|
+
var zeilen = [];
|
|
820
|
+
MCP_KATALOG.forEach(function (k) {
|
|
821
|
+
if (k.eingehaengt.some(function (z) { return zielKey(z) === zk; })) {
|
|
822
|
+
zeilen.push({ art: 'katalog', name: k.eintrag.name, beschreibung: k.eintrag.beschreibung, eintrag: k.eintrag });
|
|
823
|
+
}
|
|
824
|
+
k.geteilteFundorte.forEach(function (g) {
|
|
825
|
+
if (zielKey(g.ziel) === zk)
|
|
826
|
+
zeilen.push({ art: 'geteilt', name: k.eintrag.name, beschreibung: k.eintrag.beschreibung, datei: g.datei });
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
MCP_FREMD.forEach(function (x) { if (zielKey(x.ziel) === zk) zeilen.push({ art: 'fremd', name: x.name }); });
|
|
830
|
+
// Ein Eintrag aus der geteilten .mcp.json, den der Katalog nicht kennt: „nicht im Katalog" allein
|
|
831
|
+
// läse sich wie eine Einladung, ihn in die mcp.yaml zu schreiben. Wo er wirklich steht, gehört
|
|
832
|
+
// daneben — und übernehmen lässt er sich auch ohne Katalog-Eintrag (E64, der Block kommt aus der
|
|
833
|
+
// Datei).
|
|
834
|
+
MCP_FREMD_GETEILT.forEach(function (x) {
|
|
835
|
+
if (zielKey(x.ziel) === zk) zeilen.push({ art: 'fremd-geteilt', name: x.name, datei: x.datei });
|
|
836
|
+
});
|
|
837
|
+
zeilen.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); });
|
|
838
|
+
return zeilen;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/** Der MCP-Abschnitt unter der Skill-Link-Tabelle einer Ziel-Karte (E61-GUI-Nachtrag): nur, wenn
|
|
842
|
+
dort etwas eingehängt ist oder der „hinzufügen"-Knopf ohnehin sichtbar wäre (mcpKannAn). */
|
|
843
|
+
function mcpAbschnitt(ziel) {
|
|
844
|
+
var zeilen = mcpZeilenFuerZiel(ziel);
|
|
845
|
+
if (zeilen.length === 0 && !mcpKannAn(ziel)) return null;
|
|
846
|
+
var abschnitt = e('div', 'mcpkarteabschnitt');
|
|
847
|
+
abschnitt.appendChild(e('span', 'etikett', 'MCP'));
|
|
848
|
+
if (zeilen.length === 0) {
|
|
849
|
+
abschnitt.appendChild(e('p', 'ohne mcpkarteleer', 'Noch nichts eingehängt.'));
|
|
850
|
+
return abschnitt;
|
|
851
|
+
}
|
|
852
|
+
var ul = e('ul', 'mcpkartezeilen');
|
|
853
|
+
zeilen.forEach(function (row) {
|
|
854
|
+
var li = e('li');
|
|
855
|
+
li.appendChild(e('span', 'name', row.name));
|
|
856
|
+
if (row.art === 'katalog') {
|
|
857
|
+
li.appendChild(e('span', 'was', row.beschreibung || ''));
|
|
858
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
859
|
+
weg.type = 'button';
|
|
860
|
+
weg.setAttribute('aria-label', row.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen');
|
|
861
|
+
weg.addEventListener('click', function () { frageMcpAus(row.eintrag, ziel); });
|
|
862
|
+
li.appendChild(weg);
|
|
863
|
+
} else if (row.art === 'geteilt' || row.art === 'fremd-geteilt') {
|
|
864
|
+
// E64: der Vermerk nennt die Bedeutung („steht im Projekt, nicht bei dir"), nicht mehr bloß
|
|
865
|
+
// den Dateinamen — und daneben steht der Weg, das zu ändern.
|
|
866
|
+
li.appendChild(row.art === 'geteilt'
|
|
867
|
+
? e('span', 'was', row.beschreibung || '')
|
|
868
|
+
: e('span', 'was fehlt', 'nicht im Katalog'));
|
|
869
|
+
li.appendChild(e('span', 'geteiltvermerk', 'in der .mcp.json des Projekts'));
|
|
870
|
+
var her = e('button', 'knopf leise klein', 'Übernehmen …');
|
|
871
|
+
her.type = 'button';
|
|
872
|
+
her.setAttribute('aria-label', row.name + ' aus der .mcp.json des Projekts in deine private Konfiguration übernehmen');
|
|
873
|
+
her.addEventListener('click', function () { frageMcpUebernahme(row.name, ziel, row.datei); });
|
|
874
|
+
li.appendChild(her);
|
|
875
|
+
} else {
|
|
876
|
+
li.appendChild(e('span', 'was fehlt', 'nicht im Katalog'));
|
|
877
|
+
li.appendChild(e('span'));
|
|
878
|
+
}
|
|
879
|
+
ul.appendChild(li);
|
|
880
|
+
});
|
|
881
|
+
abschnitt.appendChild(ul);
|
|
882
|
+
return abschnitt;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** Knopf „MCP hinzufügen …" im Kartenfuß neben „Skills hinzufügen" (E61-GUI-Nachtrag, Gegenrichtung
|
|
886
|
+
zur MCP-Ansicht): nur, wo der Harness an diesem Ort MCP kann (mcpKannAn, E16). */
|
|
887
|
+
function mcpHinzufuegenKnopf(ziel) {
|
|
888
|
+
if (!mcpKannAn(ziel)) return null;
|
|
889
|
+
var knopf = e('button', 'knopf leise klein mitsymbol', 'MCP hinzufügen …');
|
|
890
|
+
knopf.type = 'button';
|
|
891
|
+
knopf.insertBefore(harnessZeichen(ziel.harness), knopf.firstChild);
|
|
892
|
+
knopf.setAttribute('aria-label', 'MCP-Server zu ' + zielTitel({ ziel: ziel }) + ' hinzufügen (' + harnessName(ziel.harness) + ')');
|
|
893
|
+
knopf.addEventListener('click', function () { oeffneMcpHinzufuegen(ziel); });
|
|
894
|
+
return knopf;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function zielKarte(z) {
|
|
898
|
+
var karte = e('div', 'ziel');
|
|
899
|
+
var kopf = e('div', 'zielkopf');
|
|
900
|
+
kopf.setAttribute('role', 'button');
|
|
901
|
+
kopf.setAttribute('tabindex', '0');
|
|
902
|
+
var schluessel = zielKey(z.ziel);
|
|
903
|
+
var bodyId = 'zielkoerper-' + zielKoerperNr++;
|
|
904
|
+
// Der Filter „Benötigt Aufmerksamkeit" ist eine Absichtserklärung (E45): er zeigt nur betroffene
|
|
905
|
+
// Ziele und klappt sie auf, unabhängig vom gemerkten Zustand, der ihm nicht gehört.
|
|
906
|
+
var offen = zielFilter === 'achtung' || !!ZIEL_OFFEN[schluessel];
|
|
907
|
+
kopf.setAttribute('aria-controls', bodyId);
|
|
908
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
909
|
+
kopf.appendChild(beschriftet(e('h3', null, zielTitel(z)), z.ziel.harness));
|
|
910
|
+
kopf.appendChild(e('span', 'mono', pfadKurz(z.verzeichnis)));
|
|
911
|
+
if (!harnessAktiv(z.ziel.harness)) kopf.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
912
|
+
if (zielBraucht(z)) {
|
|
913
|
+
// Zeichen statt Text-Badge: trägt die Farbe selbst (Emoji-Darstellung), aria-label hält den
|
|
914
|
+
// Akkordeon-Knopf-Namen brauchbar (role="img", die Bedeutung steht nicht im Glyph selbst).
|
|
915
|
+
var warnspan = e('span', 'warnung', '⚠️');
|
|
916
|
+
warnspan.setAttribute('role', 'img');
|
|
917
|
+
warnspan.setAttribute('aria-label', 'braucht Aufmerksamkeit');
|
|
918
|
+
kopf.appendChild(warnspan);
|
|
919
|
+
}
|
|
920
|
+
var zahl = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
921
|
+
kopf.appendChild(e('span', 'zahl', zahl + (zahl === 1 ? ' Link' : ' Links')));
|
|
922
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
923
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
924
|
+
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
925
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
926
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
927
|
+
kopf.appendChild(pfeil);
|
|
928
|
+
karte.appendChild(kopf);
|
|
929
|
+
var koerper = e('div', 'zielkoerper');
|
|
930
|
+
koerper.id = bodyId;
|
|
931
|
+
koerper.hidden = !offen;
|
|
932
|
+
koerper.appendChild(linkTeil(z));
|
|
933
|
+
karte.appendChild(koerper);
|
|
934
|
+
|
|
935
|
+
function schalte() {
|
|
936
|
+
offen = !offen;
|
|
937
|
+
ZIEL_OFFEN[schluessel] = offen;
|
|
938
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
939
|
+
koerper.hidden = !offen;
|
|
940
|
+
}
|
|
941
|
+
// Ein Bedienelement in der Kopfzeile (E47 „Skills hinzufügen") schaltet nicht zusätzlich um.
|
|
942
|
+
function ausBedienelement(ev) {
|
|
943
|
+
return ev.target.closest('button, a, input, select, textarea') !== null;
|
|
944
|
+
}
|
|
945
|
+
kopf.addEventListener('click', function (ev) {
|
|
946
|
+
if (!ausBedienelement(ev)) schalte();
|
|
947
|
+
});
|
|
948
|
+
kopf.addEventListener('keydown', function (ev) {
|
|
949
|
+
if (ausBedienelement(ev)) return;
|
|
950
|
+
if (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar') {
|
|
951
|
+
ev.preventDefault();
|
|
952
|
+
schalte();
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
return karte;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/** Körper des Chips „Alle" einer Verzeichnis-Karte (E53 Pkt. 2, 4, 5): eine gemeinsame Liste
|
|
960
|
+
statt gestapelter Harness-Abschnitte. Eigene Links gebündelt (Schlüssel Name+Quelle), fremde
|
|
961
|
+
Einträge und die Inaktiv-Zeile je Harness mit dessen Zeichen am Fundort. */
|
|
962
|
+
function alleTeil(pfad, teile) {
|
|
963
|
+
var teil = document.createDocumentFragment();
|
|
964
|
+
|
|
965
|
+
teile.forEach(function (z) {
|
|
966
|
+
var eigeneZ = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; });
|
|
967
|
+
if (!harnessAktiv(z.ziel.harness) && (eigeneZ.length > 0 || z.istRegistriert)) {
|
|
968
|
+
teil.appendChild(ruheZeile(z, eigeneZ.length > 0));
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
var beachteteAlle = [], ignorierteAlle = [];
|
|
973
|
+
teile.forEach(function (z) {
|
|
974
|
+
var ignoriertNamen = z.ignoriert || [];
|
|
975
|
+
z.eintraege.filter(function (ei) { return ei.art === 'fremd'; }).forEach(function (x) {
|
|
976
|
+
var paar = { x: x, z: z };
|
|
977
|
+
if (ignoriertNamen.indexOf(x.name) === -1) beachteteAlle.push(paar);
|
|
978
|
+
else ignorierteAlle.push(paar);
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
if (beachteteAlle.length > 0) teil.appendChild(fremdKasten(beachteteAlle, true));
|
|
982
|
+
if (ignorierteAlle.length > 0) teil.appendChild(ignoriertZeile(ignorierteAlle, true));
|
|
983
|
+
|
|
984
|
+
// Eigene Links über alle Harnesses gebündelt: Schlüssel Name+Quelle, hängt ein Name je Harness
|
|
985
|
+
// aus verschiedener Quelle, gibt es je Quelle eine eigene Zeile (E53 Pkt. 2).
|
|
986
|
+
var zeilen = {};
|
|
987
|
+
var reihenfolge = [];
|
|
988
|
+
teile.forEach(function (z) {
|
|
989
|
+
z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).forEach(function (ei) {
|
|
990
|
+
var defekt = ei.art === 'link-ohne-quelle';
|
|
991
|
+
var name = defekt ? ei.name : ei.link.name;
|
|
992
|
+
var quelleSchluessel = defekt ? ('defekt\u0000' + ei.checkoutPfad)
|
|
993
|
+
: (quelleKurz(ei.link.checkout.quelle) + '@' + ei.link.checkout.ref);
|
|
994
|
+
// NUL als Trenner, kein Leerzeichen: Skill-Namen dürfen Leerzeichen enthalten, Quellen-Namen
|
|
995
|
+
// seit E59 auch, ein lokaler Pfad ohnehin — „a b“ + „x“ und „a“ + „b x“ ergäben sonst
|
|
996
|
+
// denselben Schlüssel. Immer als Escape geschrieben, nie als rohes Byte im Quelltext.
|
|
997
|
+
var key = name + '\u0000' + quelleSchluessel;
|
|
998
|
+
if (!zeilen[key]) {
|
|
999
|
+
zeilen[key] = {
|
|
1000
|
+
name: name,
|
|
1001
|
+
defekt: defekt,
|
|
1002
|
+
icon: defekt ? { art: 'lokal', pfad: ei.checkoutPfad } : ei.link.checkout.quelle,
|
|
1003
|
+
checkoutPfad: defekt ? ei.checkoutPfad : null,
|
|
1004
|
+
quelle: defekt ? null : ei.link.checkout.quelle,
|
|
1005
|
+
ref: defekt ? null : ei.link.checkout.ref,
|
|
1006
|
+
einschraenkung: defekt ? null : z.einschraenkungen[name],
|
|
1007
|
+
skill: defekt ? null : ei.link.skill,
|
|
1008
|
+
teile: [],
|
|
1009
|
+
};
|
|
1010
|
+
reihenfolge.push(key);
|
|
1011
|
+
}
|
|
1012
|
+
zeilen[key].teile.push(z);
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// MCPs hängen am Verzeichnis, nicht am Harness-Chip (E61-GUI-Nachtrag): der Abschnitt unter
|
|
1017
|
+
// „Alle" zeigt jedes Ziel, das an genau diesem Pfad MCP kann — dieselbe Fähigkeitsfrage wie unter
|
|
1018
|
+
// dem Harness-Chip, allein über den Ort aus MCP_PLAETZE gestellt. Kein Harness-Name im Filter
|
|
1019
|
+
// (E16): heute liefert das nur Claude Code, weil Hermes keine Projektebene für MCP kennt; käme
|
|
1020
|
+
// ein Harness mit Projektebene dazu, stünde er hier von selbst und fiele nicht stumm weg.
|
|
1021
|
+
var verzMcpZiele = MCP_PLAETZE.filter(function (p) {
|
|
1022
|
+
return p.ort.art === 'arbeitsverzeichnis' && p.ort.pfad === pfad;
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
if (reihenfolge.length === 0) {
|
|
1026
|
+
var leer = e('div', 'leerzeile');
|
|
1027
|
+
leer.appendChild(e('p', 'ohne', 'Hier liegt noch nichts.'));
|
|
1028
|
+
leer.appendChild(alleSkillsKnopf(pfad, teile));
|
|
1029
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1030
|
+
var mcpKnopfLeer = mcpHinzufuegenKnopf(mz);
|
|
1031
|
+
if (mcpKnopfLeer) leer.appendChild(mcpKnopfLeer);
|
|
1032
|
+
});
|
|
1033
|
+
teil.appendChild(leer);
|
|
1034
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1035
|
+
var mcpAbschnLeer = mcpAbschnitt(mz);
|
|
1036
|
+
if (mcpAbschnLeer) teil.appendChild(mcpAbschnLeer);
|
|
1037
|
+
});
|
|
1038
|
+
return teil;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
var kopf = e('div', 'linkkopf');
|
|
1042
|
+
kopf.appendChild(e('span'));
|
|
1043
|
+
kopf.appendChild(e('span', 'etikett', 'Skill'));
|
|
1044
|
+
kopf.appendChild(e('span', 'etikett', 'Beschreibung'));
|
|
1045
|
+
kopf.appendChild(e('span', 'etikett', 'Quelle'));
|
|
1046
|
+
kopf.appendChild(e('span'));
|
|
1047
|
+
teil.appendChild(kopf);
|
|
1048
|
+
|
|
1049
|
+
var ul = e('ul', 'linkliste');
|
|
1050
|
+
reihenfolge
|
|
1051
|
+
.slice()
|
|
1052
|
+
.sort(function (ka, kb) {
|
|
1053
|
+
var a = zeilen[ka], b = zeilen[kb];
|
|
1054
|
+
if (a.defekt !== b.defekt) return a.defekt ? -1 : 1;
|
|
1055
|
+
return a.name.localeCompare(b.name, 'de');
|
|
1056
|
+
})
|
|
1057
|
+
.forEach(function (key) {
|
|
1058
|
+
var row = zeilen[key];
|
|
1059
|
+
var harnesse = [];
|
|
1060
|
+
row.teile.forEach(function (z) { if (harnesse.indexOf(z.ziel.harness) === -1) harnesse.push(z.ziel.harness); });
|
|
1061
|
+
harnesse.sort(function (a, b) { return harnessName(a).localeCompare(harnessName(b), 'de'); });
|
|
1062
|
+
|
|
1063
|
+
var li = e('li');
|
|
1064
|
+
li.appendChild(repoIcon(row.icon));
|
|
1065
|
+
|
|
1066
|
+
var nameSpan = e('span', 'name');
|
|
1067
|
+
nameSpan.appendChild(harnesseZeichenSammel(harnesse));
|
|
1068
|
+
nameSpan.appendChild(document.createTextNode(row.name));
|
|
1069
|
+
li.appendChild(nameSpan);
|
|
1070
|
+
|
|
1071
|
+
li.appendChild(wasZelle(row.skill));
|
|
1072
|
+
|
|
1073
|
+
var qz = e('span', 'quelle');
|
|
1074
|
+
if (row.defekt) {
|
|
1075
|
+
qz.appendChild(e('span', 'mono', pfadKurz(row.checkoutPfad)));
|
|
1076
|
+
} else {
|
|
1077
|
+
if (row.einschraenkung) qz.appendChild(document.createTextNode(row.einschraenkung + ' → '));
|
|
1078
|
+
qz.appendChild(quelleLinkElement(row.quelle));
|
|
1079
|
+
var standardRef = STANDARD_REF[quelleKurz(row.quelle)];
|
|
1080
|
+
if (row.ref && standardRef && row.ref !== standardRef) qz.appendChild(e('span', 'ref', '@' + row.ref));
|
|
1081
|
+
// Unterscheiden sich die Gründe je Harness, zählt „über Zeiger", sobald einer davon so gilt.
|
|
1082
|
+
var zeiger = row.teile.some(function (z) { var g = z.gruende[row.name]; return g && g.art === 'zeiger'; });
|
|
1083
|
+
if (zeiger) qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
1084
|
+
}
|
|
1085
|
+
li.appendChild(qz);
|
|
1086
|
+
|
|
1087
|
+
var entfernbar = row.teile.filter(function (z) {
|
|
1088
|
+
var g = z.gruende[row.name];
|
|
1089
|
+
return !(g && g.art === 'plugin');
|
|
1090
|
+
});
|
|
1091
|
+
var pluginNamen = [];
|
|
1092
|
+
row.teile.forEach(function (z) {
|
|
1093
|
+
var g = z.gruende[row.name];
|
|
1094
|
+
if (g && g.art === 'plugin' && pluginNamen.indexOf(g.plugin.split('@')[0]) === -1) {
|
|
1095
|
+
pluginNamen.push(g.plugin.split('@')[0]);
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
var nichtEntfernbar = row.teile
|
|
1099
|
+
.filter(function (z) { var g = z.gruende[row.name]; return !!(g && g.art === 'plugin'); })
|
|
1100
|
+
.map(function (z) { return z.ziel.harness; });
|
|
1101
|
+
|
|
1102
|
+
if (entfernbar.length > 0) {
|
|
1103
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
1104
|
+
weg.type = 'button';
|
|
1105
|
+
// Die Reichweite gehört in die Beschriftung: unter „Alle" stehen zwei Zeilen gleichen
|
|
1106
|
+
// Namens nebeneinander, sobald ein Skill je Harness aus verschiedener Quelle hängt.
|
|
1107
|
+
weg.setAttribute('aria-label', 'Link auf ' + row.name + ' in '
|
|
1108
|
+
+ entfernbar.map(function (z) { return harnessName(z.ziel.harness); }).join(', ') + ' entfernen');
|
|
1109
|
+
weg.addEventListener('click', function () { frageEntfernenAlle(row.name, pfad, entfernbar, nichtEntfernbar); });
|
|
1110
|
+
li.appendChild(weg);
|
|
1111
|
+
} else {
|
|
1112
|
+
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + pluginNamen.join('“, „') + '“'));
|
|
1113
|
+
}
|
|
1114
|
+
ul.appendChild(li);
|
|
1115
|
+
});
|
|
1116
|
+
teil.appendChild(ul);
|
|
1117
|
+
|
|
1118
|
+
// MCP-Abschnitt (E61-GUI-Nachtrag): auf Kartenebene unter „Alle", wie unter dem Harness-Chip,
|
|
1119
|
+
// der an diesem Pfad MCP kann.
|
|
1120
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1121
|
+
var mcpAbschnAlle = mcpAbschnitt(mz);
|
|
1122
|
+
if (mcpAbschnAlle) teil.appendChild(mcpAbschnAlle);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
var fuss = e('div', 'zielfuss');
|
|
1126
|
+
fuss.appendChild(alleSkillsKnopf(pfad, teile));
|
|
1127
|
+
verzMcpZiele.forEach(function (mz) {
|
|
1128
|
+
var mcpKnopfAlle = mcpHinzufuegenKnopf(mz);
|
|
1129
|
+
if (mcpKnopfAlle) fuss.appendChild(mcpKnopfAlle);
|
|
1130
|
+
});
|
|
1131
|
+
teil.appendChild(fuss);
|
|
1132
|
+
|
|
1133
|
+
return teil;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** Chip-Wahl einer Verzeichnis-Karte: merken, Liste neu bauen, Fokus zurück auf den Chip. */
|
|
1137
|
+
function waehleChip(schluessel, wahl) {
|
|
1138
|
+
VERZ_CHIP[schluessel] = wahl;
|
|
1139
|
+
CHIP_FOKUS = { karte: schluessel, wahl: wahl };
|
|
1140
|
+
baueZiele();
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/** Eine Akkordeon-Karte je Arbeitsverzeichnis (E49), im Körper eine Chip-Leiste „Alle" + je
|
|
1144
|
+
beteiligtem Harness ein Chip (E53): „Alle" zeigt die gebündelte Liste (alleTeil), ein
|
|
1145
|
+
Harness-Chip exakt dessen Einzel-Logik (linkTeil). Der Offen-Zustand läuft über verzKey(pfad). */
|
|
1146
|
+
function verzeichnisKarte(pfad, teile) {
|
|
1147
|
+
var karte = e('div', 'ziel verzeichnis');
|
|
1148
|
+
var kopf = e('div', 'zielkopf');
|
|
1149
|
+
kopf.setAttribute('role', 'button');
|
|
1150
|
+
kopf.setAttribute('tabindex', '0');
|
|
1151
|
+
var schluessel = verzKey(pfad);
|
|
1152
|
+
var bodyId = 'zielkoerper-' + zielKoerperNr++;
|
|
1153
|
+
var offen = zielFilter === 'achtung' || !!ZIEL_OFFEN[schluessel];
|
|
1154
|
+
kopf.setAttribute('aria-controls', bodyId);
|
|
1155
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
1156
|
+
kopf.appendChild(e('h3', null, pfadKurz(pfad)));
|
|
1157
|
+
|
|
1158
|
+
// Harness-Zeichen aller beteiligten Harnesses statt eines einzelnen (kein Ziel hat hier nur
|
|
1159
|
+
// einen Harness): klein, im Kopf, mit Sammel-aria-label statt einzelner Beschriftung je Icon.
|
|
1160
|
+
var symbole = e('span', 'harnesssymbole');
|
|
1161
|
+
var namen = teile.map(function (z) { return harnessName(z.ziel.harness); }).sort(function (a, b) { return a.localeCompare(b, 'de'); });
|
|
1162
|
+
symbole.setAttribute('role', 'img');
|
|
1163
|
+
symbole.setAttribute('aria-label', namen.join(', '));
|
|
1164
|
+
teile
|
|
1165
|
+
.slice()
|
|
1166
|
+
.sort(function (a, b) { return harnessName(a.ziel.harness).localeCompare(harnessName(b.ziel.harness), 'de'); })
|
|
1167
|
+
.forEach(function (z) { symbole.appendChild(harnessZeichen(z.ziel.harness)); });
|
|
1168
|
+
kopf.appendChild(symbole);
|
|
1169
|
+
|
|
1170
|
+
if (teile.some(zielBraucht)) {
|
|
1171
|
+
var warnspan = e('span', 'warnung', '⚠️');
|
|
1172
|
+
warnspan.setAttribute('role', 'img');
|
|
1173
|
+
warnspan.setAttribute('aria-label', 'braucht Aufmerksamkeit');
|
|
1174
|
+
kopf.appendChild(warnspan);
|
|
1175
|
+
}
|
|
1176
|
+
var gesamtZahl = teile.reduce(function (n, z) {
|
|
1177
|
+
return n + z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
1178
|
+
}, 0);
|
|
1179
|
+
kopf.appendChild(e('span', 'zahl', gesamtZahl + (gesamtZahl === 1 ? ' Link' : ' Links')));
|
|
1180
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1181
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
1182
|
+
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
1183
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
1184
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
1185
|
+
kopf.appendChild(pfeil);
|
|
1186
|
+
karte.appendChild(kopf);
|
|
1187
|
+
|
|
1188
|
+
var koerper = e('div', 'zielkoerper');
|
|
1189
|
+
koerper.id = bodyId;
|
|
1190
|
+
koerper.hidden = !offen;
|
|
1191
|
+
|
|
1192
|
+
// Chip-Leiste „Alle" + je beteiligtem Harness einer, Wahl je Karte über VERZ_CHIP (E53 Pkt. 1).
|
|
1193
|
+
var harnesseBeteiligt = [];
|
|
1194
|
+
teile.forEach(function (z) { if (harnesseBeteiligt.indexOf(z.ziel.harness) === -1) harnesseBeteiligt.push(z.ziel.harness); });
|
|
1195
|
+
harnesseBeteiligt.sort(function (a, b) { return harnessName(a).localeCompare(harnessName(b), 'de'); });
|
|
1196
|
+
var aktivChip = VERZ_CHIP[schluessel] || 'alle';
|
|
1197
|
+
if (aktivChip !== 'alle' && harnesseBeteiligt.indexOf(aktivChip) === -1) aktivChip = 'alle';
|
|
1198
|
+
|
|
1199
|
+
var chipleiste = e('div', 'filter kartenchips');
|
|
1200
|
+
chipleiste.setAttribute('role', 'group');
|
|
1201
|
+
chipleiste.setAttribute('aria-label', 'Harness wählen');
|
|
1202
|
+
var chipAlle = e('button', 'chip', 'Alle');
|
|
1203
|
+
chipAlle.type = 'button';
|
|
1204
|
+
chipAlle.dataset.chipkarte = schluessel;
|
|
1205
|
+
chipAlle.dataset.chipwahl = 'alle';
|
|
1206
|
+
chipAlle.setAttribute('aria-pressed', aktivChip === 'alle' ? 'true' : 'false');
|
|
1207
|
+
chipAlle.addEventListener('click', function () { waehleChip(schluessel, 'alle'); });
|
|
1208
|
+
chipleiste.appendChild(chipAlle);
|
|
1209
|
+
harnesseBeteiligt.forEach(function (h) {
|
|
1210
|
+
var chip = e('button', 'chip', harnessName(h));
|
|
1211
|
+
chip.type = 'button';
|
|
1212
|
+
chip.dataset.chipkarte = schluessel;
|
|
1213
|
+
chip.dataset.chipwahl = h;
|
|
1214
|
+
chip.insertBefore(harnessZeichen(h), chip.firstChild);
|
|
1215
|
+
chip.setAttribute('aria-pressed', aktivChip === h ? 'true' : 'false');
|
|
1216
|
+
chip.addEventListener('click', function () { waehleChip(schluessel, h); });
|
|
1217
|
+
chipleiste.appendChild(chip);
|
|
1218
|
+
});
|
|
1219
|
+
if (aktivChip === 'alle') {
|
|
1220
|
+
koerper.appendChild(chipleiste);
|
|
1221
|
+
koerper.appendChild(alleTeil(pfad, teile));
|
|
1222
|
+
} else {
|
|
1223
|
+
var gewaehlt = teile.filter(function (z) { return z.ziel.harness === aktivChip; })[0];
|
|
1224
|
+
// Welcher Ordner im Verzeichnis gelesen wird, stand vorher im Abschnittskopf (25cddf6) und
|
|
1225
|
+
// gehört zum gewählten Harness, nicht zur Karte: er steht rechts in der Chip-Leiste, relativ
|
|
1226
|
+
// zum Verzeichnis, mit der Inaktiv-Marke daneben.
|
|
1227
|
+
if (gewaehlt) {
|
|
1228
|
+
// Nur die Anzeige: liegt der gelesene Ordner unterhalb des Verzeichnisses, steht er
|
|
1229
|
+
// relativ, sonst gekürzt. Beide Trenner, sonst greift das unter Windows nie (E68).
|
|
1230
|
+
var unterhalb = gewaehlt.verzeichnis.indexOf(pfad + '/') === 0
|
|
1231
|
+
|| gewaehlt.verzeichnis.indexOf(pfad + '\\') === 0;
|
|
1232
|
+
chipleiste.appendChild(e('span', 'kartenordner mono',
|
|
1233
|
+
unterhalb ? gewaehlt.verzeichnis.slice(pfad.length + 1) : pfadKurz(gewaehlt.verzeichnis)));
|
|
1234
|
+
if (!harnessAktiv(aktivChip)) chipleiste.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
1235
|
+
}
|
|
1236
|
+
koerper.appendChild(chipleiste);
|
|
1237
|
+
if (gewaehlt) koerper.appendChild(linkTeil(gewaehlt));
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Entfernen heißt nur vergessen (E52, E26): der Dialog versichert das, gelöscht wird nichts.
|
|
1241
|
+
var fuss = e('div', 'zielfuss');
|
|
1242
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen …');
|
|
1243
|
+
weg.type = 'button';
|
|
1244
|
+
weg.setAttribute('aria-label', pfadKurz(pfad) + ' aus hmgr entfernen');
|
|
1245
|
+
weg.addEventListener('click', function () { frageVergessen(pfad); });
|
|
1246
|
+
fuss.appendChild(weg);
|
|
1247
|
+
koerper.appendChild(fuss);
|
|
1248
|
+
karte.appendChild(koerper);
|
|
1249
|
+
|
|
1250
|
+
function schalte() {
|
|
1251
|
+
offen = !offen;
|
|
1252
|
+
ZIEL_OFFEN[schluessel] = offen;
|
|
1253
|
+
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
1254
|
+
koerper.hidden = !offen;
|
|
1255
|
+
}
|
|
1256
|
+
function ausBedienelement(ev) {
|
|
1257
|
+
return ev.target.closest('button, a, input, select, textarea') !== null;
|
|
1258
|
+
}
|
|
1259
|
+
kopf.addEventListener('click', function (ev) {
|
|
1260
|
+
if (!ausBedienelement(ev)) schalte();
|
|
1261
|
+
});
|
|
1262
|
+
kopf.addEventListener('keydown', function (ev) {
|
|
1263
|
+
if (ausBedienelement(ev)) return;
|
|
1264
|
+
if (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar') {
|
|
1265
|
+
ev.preventDefault();
|
|
1266
|
+
schalte();
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
return karte;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function baueZiele() {
|
|
1274
|
+
var liste = $('zielliste');
|
|
1275
|
+
liste.textContent = '';
|
|
1276
|
+
var karten = zielKarten();
|
|
1277
|
+
|
|
1278
|
+
var agentsSichtbar = karten.agents
|
|
1279
|
+
.filter(function (z) { return zielFilter === 'alle' || zielBraucht(z); })
|
|
1280
|
+
.sort(function (a, b) { return zielTitel(a).localeCompare(zielTitel(b), 'de'); });
|
|
1281
|
+
var verzeichnisseSichtbar = karten.verzeichnisse
|
|
1282
|
+
.filter(function (g) { return zielFilter === 'alle' || g.teile.some(zielBraucht); })
|
|
1283
|
+
.sort(function (a, b) { return pfadKurz(a.pfad).localeCompare(pfadKurz(b.pfad), 'de'); });
|
|
1284
|
+
|
|
1285
|
+
if (agentsSichtbar.length > 0) {
|
|
1286
|
+
liste.appendChild(e('h2', 'gruppenkopf', 'Agents'));
|
|
1287
|
+
agentsSichtbar.forEach(function (z) { liste.appendChild(zielKarte(z)); });
|
|
1288
|
+
}
|
|
1289
|
+
if (verzeichnisseSichtbar.length > 0) {
|
|
1290
|
+
liste.appendChild(e('h2', 'gruppenkopf', 'Arbeitsverzeichnisse'));
|
|
1291
|
+
verzeichnisseSichtbar.forEach(function (g) { liste.appendChild(verzeichnisKarte(g.pfad, g.teile)); });
|
|
1292
|
+
}
|
|
1293
|
+
var gezeigt = agentsSichtbar.length + verzeichnisseSichtbar.length;
|
|
1294
|
+
var gesamt = karten.agents.length + karten.verzeichnisse.length;
|
|
1295
|
+
if (gezeigt === 0)
|
|
1296
|
+
liste.appendChild(e('p', 'leerhinweis', zielFilter === 'achtung'
|
|
1297
|
+
? 'Kein Ziel braucht gerade Aufmerksamkeit.'
|
|
1298
|
+
: 'Noch keine Ziele. Füge unten ein Arbeitsverzeichnis hinzu oder verlinke einen Skill.'));
|
|
1299
|
+
|
|
1300
|
+
$('zielzahl').textContent = gezeigt + ' von ' + gesamt + ' Karten gezeigt';
|
|
1301
|
+
|
|
1302
|
+
if (CHIP_FOKUS !== null) {
|
|
1303
|
+
var merker = CHIP_FOKUS;
|
|
1304
|
+
CHIP_FOKUS = null;
|
|
1305
|
+
Array.prototype.forEach.call(liste.querySelectorAll('.kartenchips .chip'), function (c) {
|
|
1306
|
+
if (c.dataset.chipkarte === merker.karte && c.dataset.chipwahl === merker.wahl) c.focus();
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
document.querySelectorAll('#ansicht-ziele .chip').forEach(function (c) {
|
|
1312
|
+
c.addEventListener('click', function () {
|
|
1313
|
+
zielFilter = c.dataset.zielfilter;
|
|
1314
|
+
// Filterwechsel leert den gemerkten Offen-Zustand (E45): „Alle" beginnt wieder eingeklappt.
|
|
1315
|
+
// Ebenso die Chip-Wahl (E53): sonst zeigte eine Karte unter „Benötigt Aufmerksamkeit" einen
|
|
1316
|
+
// Harness, in dem gar nichts zu sehen ist, während der Grund im Chip daneben steckt.
|
|
1317
|
+
ZIEL_OFFEN = {};
|
|
1318
|
+
VERZ_CHIP = {};
|
|
1319
|
+
document.querySelectorAll('#ansicht-ziele .chip').forEach(function (a) {
|
|
1320
|
+
a.setAttribute('aria-pressed', a === c ? 'true' : 'false');
|
|
1321
|
+
});
|
|
1322
|
+
baueZiele();
|
|
1323
|
+
});
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// entfernenKontext.eintraege: [{harness, ort}], immer eines bei frageEntfernen (Einzel-Ziel),
|
|
1327
|
+
// mehrere bei frageEntfernenAlle (E53 Pkt. 2, „Alle"-Zeile über mehrere Harnesses).
|
|
1328
|
+
var entfernenKontext = null;
|
|
1329
|
+
function frageEntfernen(name, ziel) {
|
|
1330
|
+
entfernenKontext = { name: name, eintraege: [{ harness: ziel.harness, ort: ziel.ort }] };
|
|
1331
|
+
$('dx-titel').textContent = 'Link auf ' + name + ' entfernen?';
|
|
1332
|
+
$('dx-text').textContent = 'Der Link in ' + zielTitel({ ziel: ziel }) + ' verschwindet. Der Checkout bleibt liegen, bis kein Link mehr darauf zeigt.';
|
|
1333
|
+
zeige($('d-entfernen'));
|
|
1334
|
+
}
|
|
1335
|
+
/** Entfernen einer „Alle"-Zeile (E53 Pkt. 2): entfernbareZiele sind die Ziele (voller z.ziel), in
|
|
1336
|
+
denen der Link weg soll, nichtEntfernbareHarnesse die, in denen er nur über ein Plugin hängt
|
|
1337
|
+
und liegen bleibt. Der Dialog nennt beides. */
|
|
1338
|
+
function frageEntfernenAlle(name, pfad, entfernbareZiele, nichtEntfernbareHarnesse) {
|
|
1339
|
+
entfernenKontext = {
|
|
1340
|
+
name: name,
|
|
1341
|
+
eintraege: entfernbareZiele.map(function (z) { return { harness: z.ziel.harness, ort: z.ziel.ort }; }),
|
|
1342
|
+
};
|
|
1343
|
+
var namen = entfernbareZiele.map(function (z) { return harnessName(z.ziel.harness); });
|
|
1344
|
+
$('dx-titel').textContent = 'Link auf ' + name + ' entfernen?';
|
|
1345
|
+
var text = 'Der Link in ' + namen.join(', ') + ' (' + pfadKurz(pfad) + ') verschwindet.';
|
|
1346
|
+
if (nichtEntfernbareHarnesse.length > 0) {
|
|
1347
|
+
text += ' In ' + nichtEntfernbareHarnesse.map(harnessName).join(', ') + ' bleibt er bestehen (nur über das Plugin).';
|
|
1348
|
+
}
|
|
1349
|
+
text += ' Der Checkout bleibt liegen, bis kein Link mehr darauf zeigt.';
|
|
1350
|
+
$('dx-text').textContent = text;
|
|
1351
|
+
zeige($('d-entfernen'));
|
|
1352
|
+
}
|
|
1353
|
+
nachDialog($('d-entfernen'), async function (wert) {
|
|
1354
|
+
if (wert !== 'ja' || entfernenKontext === null) return;
|
|
1355
|
+
var kontext = entfernenKontext;
|
|
1356
|
+
// Ein Fehlschlag in einem Harness hält die übrigen nicht auf: rufe hat ihn schon gemeldet, die
|
|
1357
|
+
// Schleife läuft weiter, und die Liste wird danach in jedem Fall neu geladen. Sonst stünde nach
|
|
1358
|
+
// einem Fehler die halb erledigte Arbeit unsichtbar in einer veralteten Liste.
|
|
1359
|
+
var gescheitert = [];
|
|
1360
|
+
var hinweiseWeg = [];
|
|
1361
|
+
for (var i = 0; i < kontext.eintraege.length; i++) {
|
|
1362
|
+
var eintrag = kontext.eintraege[i];
|
|
1363
|
+
try {
|
|
1364
|
+
var weg = await rufe(function () {
|
|
1365
|
+
return window.hmgr.linkEntfernen({ skill: kontext.name, harness: eintrag.harness, ort: eintrag.ort });
|
|
1366
|
+
});
|
|
1367
|
+
if (weg && weg.hinweis && hinweiseWeg.indexOf(weg.hinweis) < 0) hinweiseWeg.push(weg.hinweis);
|
|
1368
|
+
} catch (fehler) {
|
|
1369
|
+
gescheitert.push(harnessName(eintrag.harness));
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
await ladeAlles();
|
|
1373
|
+
if (gescheitert.length > 1) melde('Der Link auf ' + kontext.name + ' blieb in ' + gescheitert.join(', ') + ' liegen.');
|
|
1374
|
+
// Zeiger-Hinweis (F32) nur, wenn nichts gescheitert ist: sonst stünde er über der Fehlermeldung,
|
|
1375
|
+
// die rufe schon gesetzt hat.
|
|
1376
|
+
else if (gescheitert.length === 0 && hinweiseWeg.length > 0) melde(hinweiseWeg.join('\n'));
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
var loeschenKontext = null;
|
|
1380
|
+
var vergessenPfad = null;
|
|
1381
|
+
function frageVergessen(pfad) {
|
|
1382
|
+
vergessenPfad = pfad;
|
|
1383
|
+
$('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.';
|
|
1384
|
+
zeige($('d-vergessen'));
|
|
1385
|
+
}
|
|
1386
|
+
nachDialog($('d-vergessen'), async function (wert) {
|
|
1387
|
+
if (wert !== 'ja' || vergessenPfad === null) return;
|
|
1388
|
+
var pfad = vergessenPfad;
|
|
1389
|
+
vergessenPfad = null;
|
|
1390
|
+
await rufe(function () { return window.hmgr.arbeitsverzeichnisVergessen(pfad); });
|
|
1391
|
+
await ladeAlles();
|
|
1392
|
+
melde(pfadKurz(pfad) + ' aus hmgr entfernt, das Verzeichnis bleibt erhalten.');
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
function frageLoeschen(name, ziel, verzeichnis) {
|
|
1396
|
+
loeschenKontext = { name: name, ziel: ziel };
|
|
1397
|
+
$('dl-titel').textContent = '„' + name + '“ löschen?';
|
|
1398
|
+
$('dl-text').textContent = 'hmgr löscht ' + pfadKurz(verzeichnis) + TRENNER + name
|
|
1399
|
+
+ ' unwiderruflich von der Platte, ein Ordner mit allem Inhalt oder ein Link. Das lässt sich nicht rückgängig machen.';
|
|
1400
|
+
zeige($('d-loeschen'));
|
|
1401
|
+
}
|
|
1402
|
+
nachDialog($('d-loeschen'), async function (wert) {
|
|
1403
|
+
if (wert !== 'ja' || loeschenKontext === null) return;
|
|
1404
|
+
var kontext = loeschenKontext;
|
|
1405
|
+
await rufe(function () {
|
|
1406
|
+
return window.hmgr.eintragLoeschen({ harness: kontext.ziel.harness, ort: kontext.ziel.ort, name: kontext.name });
|
|
1407
|
+
});
|
|
1408
|
+
await ladeAlles();
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
var uebernahmeKontext = null;
|
|
1412
|
+
// Lesbare Fassung der Kern-Gründe (UebernahmeGrund), warum ein Eintrag liegen bleibt: eine kurze
|
|
1413
|
+
// Ursache, dazu Handreichungen (E43), die der Nutzer außerhalb von hmgr selbst erledigt.
|
|
1414
|
+
var UEBERNAHME_GRUENDE = {
|
|
1415
|
+
'kein-link': {
|
|
1416
|
+
ursache: 'Von Hand installiert, hmgr kennt die Herkunft nicht.',
|
|
1417
|
+
tipps: [
|
|
1418
|
+
'In dein Skills-Repo übernehmen und von dort verlinken',
|
|
1419
|
+
'Oder als Quelle aufnehmen, falls es ein Git-Repo dazu gibt',
|
|
1420
|
+
],
|
|
1421
|
+
},
|
|
1422
|
+
'keine-arbeitskopie': {
|
|
1423
|
+
ursache: 'Der Link führt in keine Git-Arbeitskopie.',
|
|
1424
|
+
tipps: ['Das Linkziel in ein Git-Repo bringen, dann erneut übernehmen'],
|
|
1425
|
+
},
|
|
1426
|
+
'keine-url': {
|
|
1427
|
+
ursache: 'Die Arbeitskopie hat keine Remote-URL.',
|
|
1428
|
+
tipps: ['Remote hinzufügen und pushen, dann erneut übernehmen'],
|
|
1429
|
+
},
|
|
1430
|
+
'kein-branch': {
|
|
1431
|
+
ursache: 'Die Arbeitskopie steht auf keinem Branch.',
|
|
1432
|
+
tipps: ['Branch auschecken, dann erneut übernehmen'],
|
|
1433
|
+
},
|
|
1434
|
+
'skill-unbekannt': {
|
|
1435
|
+
ursache: 'Die Quelle führt unter diesem Pfad keinen Skill.',
|
|
1436
|
+
tipps: ['Prüfen, ob der Skill im Repo verschoben oder entfernt wurde'],
|
|
1437
|
+
},
|
|
1438
|
+
'lokale-aenderungen': {
|
|
1439
|
+
ursache: 'Der Ordner weicht von der Quelle ab.',
|
|
1440
|
+
tipps: ['Änderungen committen und pushen, dann erneut übernehmen'],
|
|
1441
|
+
},
|
|
1442
|
+
'nicht-pruefbar': {
|
|
1443
|
+
ursache: 'hmgr kann nicht prüfen, ob der Ordner der Quelle entspricht.',
|
|
1444
|
+
tipps: ['Skill über die skills-CLI neu installieren, dann erneut prüfen'],
|
|
1445
|
+
},
|
|
1446
|
+
};
|
|
1447
|
+
async function oeffneUebernahme(ziel, name) {
|
|
1448
|
+
uebernahmeKontext = { ziel: ziel, name: name };
|
|
1449
|
+
$('du-etikett').textContent = 'Probelauf, nichts wird verändert';
|
|
1450
|
+
$('du-titel').textContent = '„' + name + '“ in ' + zielTitel({ ziel: ziel }) + ' übernehmen?';
|
|
1451
|
+
var ergebnis = await rufe(function () {
|
|
1452
|
+
return window.hmgr.uebernehmen({ harness: ziel.harness, ort: ziel.ort, name: name, probelauf: true });
|
|
1453
|
+
});
|
|
1454
|
+
var ul = $('du-liste');
|
|
1455
|
+
ul.textContent = '';
|
|
1456
|
+
ergebnis.eintraege.forEach(function (ei) {
|
|
1457
|
+
var li = e('li');
|
|
1458
|
+
if (ei.ergebnis === 'uebernommen') {
|
|
1459
|
+
li.appendChild(e('span', 'ok', 'wird ersetzt'));
|
|
1460
|
+
var s = e('span');
|
|
1461
|
+
s.appendChild(e('b', null, ei.name));
|
|
1462
|
+
s.appendChild(quelleSpan('mono', ei.quelle, ' @ ' + ei.ref + ' (' + ei.skill.pfad + ')'));
|
|
1463
|
+
if (ei.ordnerErsetzt) s.appendChild(e('p', 'fremderklaerung', 'Der Ordner wird gelöscht und durch den Link ersetzt.'));
|
|
1464
|
+
if (ei.gitignoriert && ei.gitignoriert.length > 0) {
|
|
1465
|
+
var maxAnzeige = 5;
|
|
1466
|
+
var angezeigt = ei.gitignoriert.slice(0, maxAnzeige).join(', ');
|
|
1467
|
+
var weitere = ei.gitignoriert.length > maxAnzeige ? ', …' : '';
|
|
1468
|
+
s.appendChild(e('p', 'fremderklaerung', zahlwort(ei.gitignoriert.length, 'gitignorierte Datei geht', 'gitignorierte Dateien gehen')
|
|
1469
|
+
+ ' beim Ersetzen verloren: ' + angezeigt + weitere));
|
|
1470
|
+
}
|
|
1471
|
+
li.appendChild(s);
|
|
1472
|
+
} else {
|
|
1473
|
+
li.appendChild(e('span', 'aus', 'bleibt liegen'));
|
|
1474
|
+
var s2 = e('span');
|
|
1475
|
+
s2.appendChild(e('b', null, ei.name));
|
|
1476
|
+
var grundInfo = UEBERNAHME_GRUENDE[ei.grund];
|
|
1477
|
+
s2.appendChild(e('span', 'mono', grundInfo ? grundInfo.ursache : ei.grund));
|
|
1478
|
+
if (grundInfo && grundInfo.tipps && grundInfo.tipps.length > 0) {
|
|
1479
|
+
var tippbox = e('div', 'tipps');
|
|
1480
|
+
tippbox.appendChild(e('p', 'tippkopf', 'Außerhalb von hmgr:'));
|
|
1481
|
+
var tul = e('ul', 'tippliste');
|
|
1482
|
+
grundInfo.tipps.forEach(function (t) { tul.appendChild(e('li', null, '→ ' + t)); });
|
|
1483
|
+
tippbox.appendChild(tul);
|
|
1484
|
+
s2.appendChild(tippbox);
|
|
1485
|
+
}
|
|
1486
|
+
li.appendChild(s2);
|
|
1487
|
+
}
|
|
1488
|
+
ul.appendChild(li);
|
|
1489
|
+
});
|
|
1490
|
+
if (ergebnis.eintraege.length === 0) ul.appendChild(e('li', null, 'Nichts mehr zu übernehmen.'));
|
|
1491
|
+
var ersetzt = ergebnis.eintraege.filter(function (ei) { return ei.ergebnis === 'uebernommen'; }).length;
|
|
1492
|
+
var liegen = ergebnis.eintraege.length - ersetzt;
|
|
1493
|
+
$('du-bilanz').textContent = ersetzt === 0
|
|
1494
|
+
? 'Nichts wird ersetzt.'
|
|
1495
|
+
: (ersetzt === 1 ? '1 wird ersetzt' : ersetzt + ' werden ersetzt') + ', '
|
|
1496
|
+
+ (liegen === 0 ? 'nichts bleibt liegen' : liegen === 1 ? '1 bleibt liegen' : liegen + ' bleiben liegen');
|
|
1497
|
+
// Ohne Übernehmbares gibt es nichts auszuführen: Knopf weg statt stumm deaktiviert.
|
|
1498
|
+
$('du-los').hidden = ersetzt === 0;
|
|
1499
|
+
zeige($('d-uebernahme'));
|
|
1500
|
+
}
|
|
1501
|
+
nachDialog($('d-uebernahme'), async function (wert) {
|
|
1502
|
+
if (wert !== 'los' || uebernahmeKontext === null) return;
|
|
1503
|
+
var ziel = uebernahmeKontext.ziel;
|
|
1504
|
+
var name = uebernahmeKontext.name;
|
|
1505
|
+
var ergebnis = await rufe(function () {
|
|
1506
|
+
return window.hmgr.uebernehmen({ harness: ziel.harness, ort: ziel.ort, name: name, probelauf: false });
|
|
1507
|
+
});
|
|
1508
|
+
var uebernommen = ergebnis.eintraege.filter(function (ei) { return ei.ergebnis === 'uebernommen'; }).length;
|
|
1509
|
+
var uebersprungen = ergebnis.eintraege.length - uebernommen;
|
|
1510
|
+
$('de-titel').textContent = 'Übernommen';
|
|
1511
|
+
$('de-text').textContent = uebernommen + ' übernommen, ' + uebersprungen + ' übersprungen.';
|
|
1512
|
+
setzeErfolgHinweise([ergebnis.hinweis]);
|
|
1513
|
+
var ul = $('de-liste');
|
|
1514
|
+
ul.textContent = '';
|
|
1515
|
+
ergebnis.eintraege.forEach(function (ei) {
|
|
1516
|
+
var li = e('li');
|
|
1517
|
+
li.appendChild(e('span', ei.ergebnis === 'uebernommen' ? 'ok' : 'aus', ei.ergebnis === 'uebernommen' ? 'Link' : 'Liegen gelassen'));
|
|
1518
|
+
var s = e('span');
|
|
1519
|
+
s.appendChild(e('b', null, ei.name));
|
|
1520
|
+
li.appendChild(s);
|
|
1521
|
+
ul.appendChild(li);
|
|
1522
|
+
});
|
|
1523
|
+
await ladeAlles();
|
|
1524
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
$('verzneu').addEventListener('click', function () {
|
|
1528
|
+
$('dv-pfad').value = '';
|
|
1529
|
+
zeige($('d-verzeichnis'));
|
|
1530
|
+
});
|
|
1531
|
+
enterBestaetigt($('dv-pfad'), $('dv-los'));
|
|
1532
|
+
$('dv-auswaehlen').addEventListener('click', async function () {
|
|
1533
|
+
var pfad = await rufe(function () { return window.hmgr.verzeichnisWaehlen(); });
|
|
1534
|
+
if (pfad) $('dv-pfad').value = pfad;
|
|
1535
|
+
});
|
|
1536
|
+
nachDialog($('d-verzeichnis'), async function (wert) {
|
|
1537
|
+
if (wert !== 'los') return;
|
|
1538
|
+
var pfad = $('dv-pfad').value.trim();
|
|
1539
|
+
if (!pfad) return;
|
|
1540
|
+
var ergebnis = await rufe(function () { return window.hmgr.arbeitsverzeichnisHinzufuegen(pfad); });
|
|
1541
|
+
await ladeAlles();
|
|
1542
|
+
$('de-titel').textContent = 'Arbeitsverzeichnis hinzugefügt';
|
|
1543
|
+
$('de-text').textContent = ergebnis.hinweis || ('Zeiger: ' + (ergebnis.zeigerPfad || 'keiner'));
|
|
1544
|
+
setzeErfolgHinweise([]);
|
|
1545
|
+
$('de-liste').textContent = '';
|
|
1546
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
/* ================= SKILLS ================= */
|
|
1550
|
+
|
|
1551
|
+
var QUELLFARBEN = ['', 'q2', 'q3'];
|
|
1552
|
+
function quellenIndex(quelleText) {
|
|
1553
|
+
var i = QUELLEN.findIndex(function (q) { return q.text === quelleText; });
|
|
1554
|
+
return i < 0 ? 0 : i % 3;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function baueQuellenfilter() {
|
|
1558
|
+
var box = $('quellenfilter');
|
|
1559
|
+
box.textContent = '';
|
|
1560
|
+
var alle = e('button', 'chip', 'Alle');
|
|
1561
|
+
alle.type = 'button';
|
|
1562
|
+
alle.dataset.filter = 'alle';
|
|
1563
|
+
alle.setAttribute('aria-pressed', quellenFilter === 'alle' ? 'true' : 'false');
|
|
1564
|
+
alle.addEventListener('click', function () { setzeQuellenfilter('alle'); });
|
|
1565
|
+
box.appendChild(alle);
|
|
1566
|
+
|
|
1567
|
+
var gesehen = {};
|
|
1568
|
+
SKILLS.forEach(function (s) {
|
|
1569
|
+
if (gesehen[s.quelleSchluessel]) return;
|
|
1570
|
+
gesehen[s.quelleSchluessel] = true;
|
|
1571
|
+
var idx = quellenIndex(s.quelleText);
|
|
1572
|
+
var chip = e('button', 'chip', s.quelleName);
|
|
1573
|
+
chip.title = s.quelleText;
|
|
1574
|
+
chip.type = 'button';
|
|
1575
|
+
chip.dataset.filter = s.quelleSchluessel;
|
|
1576
|
+
chip.setAttribute('aria-pressed', quellenFilter === s.quelleSchluessel ? 'true' : 'false');
|
|
1577
|
+
var punkt = e('i');
|
|
1578
|
+
punkt.style.background = 'var(--q' + (idx + 1) + ')';
|
|
1579
|
+
chip.insertBefore(punkt, chip.firstChild);
|
|
1580
|
+
chip.addEventListener('click', function () { setzeQuellenfilter(s.quelleSchluessel); });
|
|
1581
|
+
|
|
1582
|
+
// Quellen-Chip-Paar (E60): links filtert der Name, rechts öffnet das Pfeilsymbol die Quelle
|
|
1583
|
+
// (Repo-Seite im Browser, lokale Arbeitskopie im Dateimanager). Kein <a> im <button>, deshalb
|
|
1584
|
+
// stehen sie nebeneinander in einer gemeinsamen Hülle.
|
|
1585
|
+
var quelle = QUELLEN.filter(function (q) { return q.text === s.quelleText; })[0];
|
|
1586
|
+
if (quelle) {
|
|
1587
|
+
var gruppe = e('span', 'chipgruppe');
|
|
1588
|
+
gruppe.appendChild(chip);
|
|
1589
|
+
var lokal = quelle.art === 'lokal';
|
|
1590
|
+
var oeffnen = document.createElement(lokal ? 'button' : 'a');
|
|
1591
|
+
if (lokal) oeffnen.type = 'button'; else oeffnen.href = '#';
|
|
1592
|
+
oeffnen.className = 'chipoeffnen';
|
|
1593
|
+
oeffnen.title = quelle.text;
|
|
1594
|
+
oeffnen.setAttribute('aria-label', s.quelleName + ' ' + (lokal ? 'im Dateimanager öffnen' : 'im Browser öffnen'));
|
|
1595
|
+
oeffnen.appendChild(externSvg());
|
|
1596
|
+
oeffnen.addEventListener('click', function (ev) {
|
|
1597
|
+
ev.preventDefault();
|
|
1598
|
+
ev.stopPropagation();
|
|
1599
|
+
window.hmgr.quelleOeffnen(quelle.text).catch(function () {});
|
|
1600
|
+
});
|
|
1601
|
+
gruppe.appendChild(oeffnen);
|
|
1602
|
+
box.appendChild(gruppe);
|
|
1603
|
+
} else {
|
|
1604
|
+
box.appendChild(chip);
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function setzeQuellenfilter(wert) {
|
|
1610
|
+
quellenFilter = wert;
|
|
1611
|
+
document.querySelectorAll('#quellenfilter .chip').forEach(function (c) {
|
|
1612
|
+
c.setAttribute('aria-pressed', c.dataset.filter === wert ? 'true' : 'false');
|
|
1613
|
+
});
|
|
1614
|
+
filtereSkills();
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/** Immer sichtbare Marken einer Skill-Zeile aus dem Frontmatter (E55), nur wenn zutreffend.
|
|
1618
|
+
Hooks und vorab erlaubte Werkzeuge sind sicherheitsrelevant und deshalb warnend gefärbt (F11). */
|
|
1619
|
+
function skillMarken(skill) {
|
|
1620
|
+
var marken = [];
|
|
1621
|
+
if (skill.nurPerBefehl) marken.push({ text: 'nur per /' + skill.name });
|
|
1622
|
+
if (skill.nichtPerBefehl) marken.push({ text: 'nicht per /' + skill.name });
|
|
1623
|
+
if (skill.registriertHooks) marken.push({ text: 'registriert Hooks', warnend: true });
|
|
1624
|
+
if (skill.werkzeugeErlaubt) marken.push({ text: 'Werkzeuge vorab erlaubt', warnend: true });
|
|
1625
|
+
return marken;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/** Die Marken eines Skills als fertige Box, für Katalog und Skills-hinzufügen-Dialog (F13).
|
|
1629
|
+
Ohne Besonderheiten null, dann bleibt die Zeile unverändert. */
|
|
1630
|
+
function skillMarkenBox(skill) {
|
|
1631
|
+
var marken = skillMarken(skill);
|
|
1632
|
+
if (marken.length === 0) return null;
|
|
1633
|
+
var box = e('span', 'marken');
|
|
1634
|
+
marken.forEach(function (m) { box.appendChild(e('span', 'merker leise' + (m.warnend ? ' warnend' : ''), m.text)); });
|
|
1635
|
+
return box;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/** Beschreibungs-Zelle der Ziel-Tabellen: die Beschreibung (E39) und darunter die immer
|
|
1639
|
+
sichtbaren Frontmatter-Marken (E55), derselbe Helfer wie im Katalog. Ohne Skill (Link ohne
|
|
1640
|
+
Quelle) bleibt nur die Fehlt-Zeile. */
|
|
1641
|
+
function wasZelle(skill) {
|
|
1642
|
+
var feld = e('span', 'wasfeld');
|
|
1643
|
+
var beschreibung = skill ? skill.beschreibung : undefined;
|
|
1644
|
+
if (beschreibung) {
|
|
1645
|
+
var wasEl = e('span', 'was', beschreibung);
|
|
1646
|
+
if (beschreibung.length > 88) wasEl.title = beschreibung;
|
|
1647
|
+
feld.appendChild(wasEl);
|
|
1648
|
+
} else {
|
|
1649
|
+
feld.appendChild(e('span', 'was fehlt', 'keine Beschreibung in der SKILL.md'));
|
|
1650
|
+
}
|
|
1651
|
+
var marken = skill ? skillMarkenBox(skill) : null;
|
|
1652
|
+
if (marken) feld.appendChild(marken);
|
|
1653
|
+
return feld;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/** Zeilen des Aufklappbereichs einer Skill-Zeile aus dem Frontmatter (E55), nur wenn vorhanden. */
|
|
1657
|
+
function skillDetails(skill) {
|
|
1658
|
+
var zeilen = [];
|
|
1659
|
+
if (skill.kompatibilitaet) zeilen.push({ label: 'Kompatibilität', text: skill.kompatibilitaet });
|
|
1660
|
+
if (skill.modell) zeilen.push({ label: 'Modell', text: skill.modell });
|
|
1661
|
+
if (skill.eigenerKontext) zeilen.push({ label: 'Kontext', text: 'läuft in eigenem Kontext' });
|
|
1662
|
+
if (skill.argumentHinweis) zeilen.push({ label: 'Argumente', text: skill.argumentHinweis });
|
|
1663
|
+
return zeilen;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function baueSkills() {
|
|
1667
|
+
var liste = $('katalog');
|
|
1668
|
+
liste.textContent = '';
|
|
1669
|
+
SKILLS.forEach(function (skill, i) {
|
|
1670
|
+
var li = e('li', 'zeile');
|
|
1671
|
+
li.dataset.index = i;
|
|
1672
|
+
var idx = quellenIndex(skill.quelleText);
|
|
1673
|
+
li.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1674
|
+
|
|
1675
|
+
var links = e('div');
|
|
1676
|
+
var titel = e('h3');
|
|
1677
|
+
var herkunftQuelle = QUELLEN.filter(function (q) { return q.text === skill.quelleText; })[0];
|
|
1678
|
+
// Quellen-Icon vor dem Namen, wie in den Ziel-Tabellen (E60). Ohne bekannte Quelle generisch.
|
|
1679
|
+
titel.appendChild(repoIcon(herkunftQuelle || { art: 'lokal', pfad: skill.quelleText }));
|
|
1680
|
+
titel.appendChild(document.createTextNode(skill.name));
|
|
1681
|
+
var markenBox = skillMarkenBox(skill);
|
|
1682
|
+
if (markenBox) titel.appendChild(markenBox);
|
|
1683
|
+
links.appendChild(titel);
|
|
1684
|
+
links.appendChild(e('p', 'beschreibung', skill.beschreibung || ''));
|
|
1685
|
+
// Herkunft: der Name der Quelle, anklickbar wie überall sonst (E60), die Adresse steht im
|
|
1686
|
+
// title. Fehlt die Quelle in der Liste (Katalog und Quellen kämen auseinander), bleibt es
|
|
1687
|
+
// beim reinen Text — angeschrieben ist die Zeile so oder so.
|
|
1688
|
+
var herkunft = e('p', 'herkunft mono');
|
|
1689
|
+
if (herkunftQuelle) {
|
|
1690
|
+
herkunft.appendChild(quelleLinkElement(herkunftQuelle));
|
|
1691
|
+
herkunft.appendChild(e('b', null, ' · Ref ' + skill.ref));
|
|
1692
|
+
} else {
|
|
1693
|
+
herkunft.title = skill.quelleText;
|
|
1694
|
+
herkunft.appendChild(e('b', null, skill.quelleName + ' · Ref ' + skill.ref));
|
|
1695
|
+
}
|
|
1696
|
+
links.appendChild(herkunft);
|
|
1697
|
+
|
|
1698
|
+
var rechts = e('div', 'rechts');
|
|
1699
|
+
var details = skillDetails(skill);
|
|
1700
|
+
var umschalter = null;
|
|
1701
|
+
var koerperId = 'skillkoerper-' + i;
|
|
1702
|
+
// Der Aufklappzustand überlebt den Neuaufbau durch ladeAlles (F10), wie ZIEL_OFFEN.
|
|
1703
|
+
var offen = !!SKILL_OFFEN[skillKey(skill)];
|
|
1704
|
+
if (details.length > 0) {
|
|
1705
|
+
umschalter = e('button', 'aufklappknopf');
|
|
1706
|
+
umschalter.type = 'button';
|
|
1707
|
+
umschalter.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
1708
|
+
umschalter.setAttribute('aria-controls', koerperId);
|
|
1709
|
+
umschalter.setAttribute('aria-label', 'Details zu ' + skill.name + ' ein- oder ausklappen');
|
|
1710
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1711
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
1712
|
+
pfeil.setAttribute('class', 'akkordeonpfeil dunkel');
|
|
1713
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
1714
|
+
pfeil.innerHTML = '<path d="M4 6.5 8 10.5 12 6.5"/>';
|
|
1715
|
+
umschalter.appendChild(pfeil);
|
|
1716
|
+
rechts.appendChild(umschalter);
|
|
1717
|
+
}
|
|
1718
|
+
var knopf = e('button', 'knopf leise', 'Verlinken');
|
|
1719
|
+
knopf.type = 'button';
|
|
1720
|
+
knopf.addEventListener('click', function () { starteVerlinken(i); });
|
|
1721
|
+
rechts.appendChild(knopf);
|
|
1722
|
+
|
|
1723
|
+
li.appendChild(links);
|
|
1724
|
+
li.appendChild(rechts);
|
|
1725
|
+
|
|
1726
|
+
if (details.length > 0 && umschalter) {
|
|
1727
|
+
var koerper = e('div', 'skillkoerper');
|
|
1728
|
+
koerper.id = koerperId;
|
|
1729
|
+
koerper.hidden = !offen;
|
|
1730
|
+
details.forEach(function (d) {
|
|
1731
|
+
var zeile = e('p', 'skilldetail');
|
|
1732
|
+
zeile.appendChild(e('b', null, d.label + ': '));
|
|
1733
|
+
zeile.appendChild(document.createTextNode(d.text));
|
|
1734
|
+
koerper.appendChild(zeile);
|
|
1735
|
+
});
|
|
1736
|
+
li.appendChild(koerper);
|
|
1737
|
+
umschalter.addEventListener('click', function () {
|
|
1738
|
+
var war = umschalter.getAttribute('aria-expanded') === 'true';
|
|
1739
|
+
umschalter.setAttribute('aria-expanded', war ? 'false' : 'true');
|
|
1740
|
+
koerper.hidden = war;
|
|
1741
|
+
SKILL_OFFEN[skillKey(skill)] = !war;
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
liste.appendChild(li);
|
|
1746
|
+
});
|
|
1747
|
+
filtereSkills();
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function filtereSkills() {
|
|
1751
|
+
var suche = $('suchfeld').value.trim().toLowerCase();
|
|
1752
|
+
var sichtbar = 0;
|
|
1753
|
+
document.querySelectorAll('#katalog .zeile').forEach(function (li) {
|
|
1754
|
+
var skill = SKILLS[+li.dataset.index];
|
|
1755
|
+
var passtSuche = !suche || skill.name.toLowerCase().indexOf(suche) > -1 || skill.pfad.toLowerCase().indexOf(suche) > -1;
|
|
1756
|
+
var passtFilter = quellenFilter === 'alle' || skill.quelleSchluessel === quellenFilter;
|
|
1757
|
+
var an = passtSuche && passtFilter;
|
|
1758
|
+
li.hidden = !an;
|
|
1759
|
+
if (an) sichtbar++;
|
|
1760
|
+
});
|
|
1761
|
+
$('katalogzahl').textContent = sichtbar + ' von ' + SKILLS.length + ' Skills gezeigt';
|
|
1762
|
+
}
|
|
1763
|
+
$('suchfeld').addEventListener('input', filtereSkills);
|
|
1764
|
+
|
|
1765
|
+
var laufendeSkill = null;
|
|
1766
|
+
function starteVerlinken(index) {
|
|
1767
|
+
laufendeSkill = index;
|
|
1768
|
+
var skill = SKILLS[index];
|
|
1769
|
+
$('dz-skill').textContent = skill.name;
|
|
1770
|
+
var box = $('dz-ziele');
|
|
1771
|
+
box.textContent = '';
|
|
1772
|
+
ZIELE.forEach(function (z) {
|
|
1773
|
+
var schon = z.eintraege.some(function (ei) {
|
|
1774
|
+
var n = ei.art === 'link' ? ei.link.name : ei.name;
|
|
1775
|
+
return n === skill.name;
|
|
1776
|
+
});
|
|
1777
|
+
var label = e('label', 'wahlzeile');
|
|
1778
|
+
var input = document.createElement('input');
|
|
1779
|
+
input.type = 'checkbox';
|
|
1780
|
+
input.value = String(ZIELE.indexOf(z));
|
|
1781
|
+
input.disabled = schon;
|
|
1782
|
+
label.appendChild(input);
|
|
1783
|
+
var txt = e('span');
|
|
1784
|
+
txt.appendChild(beschriftet(e('b', null, zielTitel(z)), z.ziel.harness));
|
|
1785
|
+
txt.appendChild(e('span', 'mono', pfadKurz(z.verzeichnis)));
|
|
1786
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
1787
|
+
label.appendChild(txt);
|
|
1788
|
+
box.appendChild(label);
|
|
1789
|
+
});
|
|
1790
|
+
zeige($('d-ziel'));
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
$('d-ziel').addEventListener('submit', function (ev) {
|
|
1794
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
1795
|
+
var gewaehlt = Array.from($('dz-ziele').querySelectorAll('input:checked'));
|
|
1796
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
nachDialog($('d-ziel'), async function (wert) {
|
|
1801
|
+
if (wert !== 'los' || laufendeSkill === null) return;
|
|
1802
|
+
var skill = SKILLS[laufendeSkill];
|
|
1803
|
+
var indizes = Array.from($('dz-ziele').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
1804
|
+
var ergebnisZeilen = [];
|
|
1805
|
+
var hinweise = [];
|
|
1806
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
1807
|
+
var z = ZIELE[indizes[n]];
|
|
1808
|
+
try {
|
|
1809
|
+
var gelegt = await rufe(function () {
|
|
1810
|
+
return window.hmgr.linkLegen({ skill: skill.name, harness: z.ziel.harness, ort: z.ziel.ort, quelle: skill.quelleText });
|
|
1811
|
+
});
|
|
1812
|
+
hinweise.push(gelegt.hinweis);
|
|
1813
|
+
ergebnisZeilen.push({ ok: true, ziel: z });
|
|
1814
|
+
} catch (fehler) {
|
|
1815
|
+
ergebnisZeilen.push({ ok: false, ziel: z, fehler: fehler.message });
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
await ladeAlles();
|
|
1819
|
+
$('de-titel').textContent = 'Verlinkt';
|
|
1820
|
+
var erfolge = ergebnisZeilen.filter(function (r) { return r.ok; }).length;
|
|
1821
|
+
$('de-text').textContent = skill.name + ' hängt jetzt in ' + erfolge + (erfolge === 1 ? ' Ziel.' : ' Zielen.');
|
|
1822
|
+
setzeErfolgHinweise(hinweise);
|
|
1823
|
+
var ul = $('de-liste');
|
|
1824
|
+
ul.textContent = '';
|
|
1825
|
+
ergebnisZeilen.forEach(function (r) {
|
|
1826
|
+
var li = e('li');
|
|
1827
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1828
|
+
var s = e('span');
|
|
1829
|
+
s.appendChild(beschriftet(e('b', null, zielTitel(r.ziel)), r.ziel.ziel.harness));
|
|
1830
|
+
s.appendChild(e('span', 'mono', r.ok ? pfadKurz(r.ziel.verzeichnis) + TRENNER + skill.name : r.fehler));
|
|
1831
|
+
li.appendChild(s);
|
|
1832
|
+
ul.appendChild(li);
|
|
1833
|
+
});
|
|
1834
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
/* E47: Gegenrichtung zu starteVerlinken/d-ziel. Mehrfachauswahl über den ganzen Katalog, schon
|
|
1838
|
+
hier hängende Skills (gleicher Name, egal welche Art) sind ausgegraut. E53 erweitert das um
|
|
1839
|
+
einen zweiten Modus für den Chip „Alle" einer Verzeichnis-Karte: modus „alle" grauet einen
|
|
1840
|
+
Skill nur aus, wenn er in ALLEN beteiligten Harnesses hängt, und verlinkt je Harness, wo er fehlt. */
|
|
1841
|
+
var skillsKontext = null;
|
|
1842
|
+
function dskBilanzAktualisieren() {
|
|
1843
|
+
var n = $('dsk-liste').querySelectorAll('input:checked').length;
|
|
1844
|
+
$('dsk-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
1845
|
+
}
|
|
1846
|
+
function filtereDskListe() {
|
|
1847
|
+
var suche = $('dsk-suche').value.trim().toLowerCase();
|
|
1848
|
+
document.querySelectorAll('#dsk-liste .wahlzeile').forEach(function (label) {
|
|
1849
|
+
var skill = SKILLS[+label.dataset.index];
|
|
1850
|
+
label.hidden = !!suche && skill.name.toLowerCase().indexOf(suche) === -1;
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
$('dsk-suche').addEventListener('input', filtereDskListe);
|
|
1854
|
+
|
|
1855
|
+
function oeffneSkillsHinzufuegen(z) {
|
|
1856
|
+
skillsKontext = { modus: 'einzeln', z: z };
|
|
1857
|
+
$('dsk-ziel').textContent = zielTitel(z);
|
|
1858
|
+
$('dsk-suche').value = '';
|
|
1859
|
+
var box = $('dsk-liste');
|
|
1860
|
+
box.textContent = '';
|
|
1861
|
+
var vorhandeneNamen = z.eintraege.map(function (ei) { return ei.art === 'link' ? ei.link.name : ei.name; });
|
|
1862
|
+
SKILLS.forEach(function (skill, i) {
|
|
1863
|
+
var schon = vorhandeneNamen.indexOf(skill.name) > -1;
|
|
1864
|
+
var label = e('label', 'wahlzeile quellfarbe');
|
|
1865
|
+
label.dataset.index = i;
|
|
1866
|
+
var idx = quellenIndex(skill.quelleText);
|
|
1867
|
+
label.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1868
|
+
var input = document.createElement('input');
|
|
1869
|
+
input.type = 'checkbox';
|
|
1870
|
+
input.value = String(i);
|
|
1871
|
+
input.disabled = schon;
|
|
1872
|
+
input.addEventListener('change', dskBilanzAktualisieren);
|
|
1873
|
+
label.appendChild(input);
|
|
1874
|
+
var txt = e('span');
|
|
1875
|
+
txt.appendChild(e('b', null, skill.name));
|
|
1876
|
+
// Dieselben Marken wie im Katalog (F13): hier fällt die Entscheidung, was installiert wird.
|
|
1877
|
+
var marken = skillMarkenBox(skill);
|
|
1878
|
+
if (marken) txt.appendChild(marken);
|
|
1879
|
+
var quelleSpanEl = e('span', 'mono', skill.quelleName + ' · Ref ' + skill.ref);
|
|
1880
|
+
quelleSpanEl.title = skill.quelleText;
|
|
1881
|
+
txt.appendChild(quelleSpanEl);
|
|
1882
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
1883
|
+
label.appendChild(txt);
|
|
1884
|
+
box.appendChild(label);
|
|
1885
|
+
});
|
|
1886
|
+
dskBilanzAktualisieren();
|
|
1887
|
+
zeige($('d-skills'));
|
|
1888
|
+
$('dsk-suche').focus();
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/** Skills-hinzufügen-Dialog für den Chip „Alle" (E53 Pkt. 4): ausgegraut nur, wenn ein Skill in
|
|
1892
|
+
allen beteiligten Harnesses schon hängt. */
|
|
1893
|
+
function oeffneSkillsHinzufuegenAlle(pfad, teile) {
|
|
1894
|
+
skillsKontext = { modus: 'alle', pfad: pfad, teile: teile };
|
|
1895
|
+
$('dsk-ziel').textContent = pfadKurz(pfad) + ' (alle Harnesses)';
|
|
1896
|
+
$('dsk-suche').value = '';
|
|
1897
|
+
var box = $('dsk-liste');
|
|
1898
|
+
box.textContent = '';
|
|
1899
|
+
var vorhandeneJeHarness = teile.map(function (z) {
|
|
1900
|
+
return z.eintraege.map(function (ei) { return ei.art === 'link' ? ei.link.name : ei.name; });
|
|
1901
|
+
});
|
|
1902
|
+
SKILLS.forEach(function (skill, i) {
|
|
1903
|
+
var fehltIn = teile.filter(function (z, n) { return vorhandeneJeHarness[n].indexOf(skill.name) === -1; });
|
|
1904
|
+
var ueberall = fehltIn.length === 0;
|
|
1905
|
+
var label = e('label', 'wahlzeile quellfarbe');
|
|
1906
|
+
label.dataset.index = i;
|
|
1907
|
+
var idx = quellenIndex(skill.quelleText);
|
|
1908
|
+
label.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1909
|
+
var input = document.createElement('input');
|
|
1910
|
+
input.type = 'checkbox';
|
|
1911
|
+
input.value = String(i);
|
|
1912
|
+
input.disabled = ueberall;
|
|
1913
|
+
input.addEventListener('change', dskBilanzAktualisieren);
|
|
1914
|
+
label.appendChild(input);
|
|
1915
|
+
var txt = e('span');
|
|
1916
|
+
txt.appendChild(e('b', null, skill.name));
|
|
1917
|
+
// Dieselben Marken wie im Katalog (F13): hier fällt die Entscheidung, was installiert wird.
|
|
1918
|
+
var marken = skillMarkenBox(skill);
|
|
1919
|
+
if (marken) txt.appendChild(marken);
|
|
1920
|
+
var quelleSpanEl = e('span', 'mono', skill.quelleName + ' · Ref ' + skill.ref);
|
|
1921
|
+
quelleSpanEl.title = skill.quelleText;
|
|
1922
|
+
txt.appendChild(quelleSpanEl);
|
|
1923
|
+
if (ueberall) txt.appendChild(e('span', 'notiz', 'Hängt schon überall.'));
|
|
1924
|
+
else if (fehltIn.length < teile.length) {
|
|
1925
|
+
txt.appendChild(e('span', 'notiz', 'Hängt schon in ' + (teile.length - fehltIn.length) + ' von ' + teile.length + ' Harnesses.'));
|
|
1926
|
+
}
|
|
1927
|
+
label.appendChild(txt);
|
|
1928
|
+
box.appendChild(label);
|
|
1929
|
+
});
|
|
1930
|
+
dskBilanzAktualisieren();
|
|
1931
|
+
zeige($('d-skills'));
|
|
1932
|
+
$('dsk-suche').focus();
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
$('d-skills').addEventListener('submit', function (ev) {
|
|
1936
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
1937
|
+
var gewaehlt = Array.from($('dsk-liste').querySelectorAll('input:checked'));
|
|
1938
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
nachDialog($('d-skills'), async function (wert) {
|
|
1943
|
+
if (wert !== 'los' || skillsKontext === null) return;
|
|
1944
|
+
var kontext = skillsKontext;
|
|
1945
|
+
var indizes = Array.from($('dsk-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
1946
|
+
|
|
1947
|
+
if (kontext.modus === 'einzeln') {
|
|
1948
|
+
var z = kontext.z;
|
|
1949
|
+
var ergebnisZeilen = [];
|
|
1950
|
+
// Zwei Skills gleichen Namens aus verschiedenen Quellen stehen beide im Katalog (E32), am Ziel
|
|
1951
|
+
// gilt der Name aber nur einmal (E12), und der Kern ersetzte den eben gelegten Link stumm.
|
|
1952
|
+
// Der zweite gehört daher als Fehler in die Bilanz, sonst meldet der Dialog Links, die es nicht gibt.
|
|
1953
|
+
var vergeben = [];
|
|
1954
|
+
var hinweiseZiel = [];
|
|
1955
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
1956
|
+
var skill = SKILLS[indizes[n]];
|
|
1957
|
+
if (vergeben.indexOf(skill.name) > -1) {
|
|
1958
|
+
ergebnisZeilen.push({
|
|
1959
|
+
ok: false,
|
|
1960
|
+
skill: skill,
|
|
1961
|
+
fehler: 'Ein Skill dieses Namens ist gerade verlinkt worden, hier gibt es den Namen nur einmal.',
|
|
1962
|
+
});
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
try {
|
|
1966
|
+
var gelegtZiel = await rufe(function () {
|
|
1967
|
+
return window.hmgr.linkLegen({ skill: skill.name, harness: z.ziel.harness, ort: z.ziel.ort, quelle: skill.quelleText });
|
|
1968
|
+
});
|
|
1969
|
+
hinweiseZiel.push(gelegtZiel.hinweis);
|
|
1970
|
+
vergeben.push(skill.name);
|
|
1971
|
+
ergebnisZeilen.push({ ok: true, skill: skill });
|
|
1972
|
+
} catch (fehler) {
|
|
1973
|
+
ergebnisZeilen.push({ ok: false, skill: skill, fehler: fehler.message });
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
// Nach dem Verlinken bleibt die Karte offen, in der das Ziel steckt: bei einem
|
|
1977
|
+
// Arbeitsverzeichnis ist das die gebündelte Verzeichnis-Karte, nicht das Ziel allein (E49).
|
|
1978
|
+
ZIEL_OFFEN[kartenKey(z.ziel)] = true;
|
|
1979
|
+
await ladeAlles();
|
|
1980
|
+
var erfolge = ergebnisZeilen.filter(function (r) { return r.ok; }).length;
|
|
1981
|
+
$('de-titel').textContent = 'Verlinkt';
|
|
1982
|
+
$('de-text').textContent = zahlwort(erfolge, 'Skill hängt', 'Skills hängen') + ' jetzt in ' + zielTitel(z) + '.';
|
|
1983
|
+
setzeErfolgHinweise(hinweiseZiel);
|
|
1984
|
+
var ul = $('de-liste');
|
|
1985
|
+
ul.textContent = '';
|
|
1986
|
+
ergebnisZeilen.forEach(function (r) {
|
|
1987
|
+
var li = e('li');
|
|
1988
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
1989
|
+
var s = e('span');
|
|
1990
|
+
s.appendChild(e('b', null, r.skill.name));
|
|
1991
|
+
if (!r.ok) s.appendChild(e('span', 'mono', r.fehler));
|
|
1992
|
+
li.appendChild(s);
|
|
1993
|
+
ul.appendChild(li);
|
|
1994
|
+
});
|
|
1995
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// modus „alle" (E53 Pkt. 4): je gewähltem Skill in jedem Harness verlinken, wo er noch fehlt.
|
|
2000
|
+
// Erfolgsliste je Harness eine Zeile, schon vorhandene Harnesses bleiben ohne Zeile.
|
|
2001
|
+
var pfad = kontext.pfad;
|
|
2002
|
+
var teile = kontext.teile;
|
|
2003
|
+
var zeilenErgebnis = [];
|
|
2004
|
+
// Der Duplikat-Schutz aus dem Einzel-Modus gilt hier je Harness, nicht über die Auswahl hinweg:
|
|
2005
|
+
// derselbe Skill gehört absichtlich in mehrere Harnesses, zwei gleichnamige Skills aus
|
|
2006
|
+
// verschiedenen Quellen (E32) gehören aber in keinem Harness zweimal (E12), der Kern ersetzte
|
|
2007
|
+
// den eben gelegten Link sonst stumm.
|
|
2008
|
+
var vergebenJeHarness = {};
|
|
2009
|
+
var hinweiseAlle = [];
|
|
2010
|
+
for (var m = 0; m < indizes.length; m++) {
|
|
2011
|
+
var sk = SKILLS[indizes[m]];
|
|
2012
|
+
for (var t = 0; t < teile.length; t++) {
|
|
2013
|
+
var zt = teile[t];
|
|
2014
|
+
var vorhandene = zt.eintraege.map(function (ei) { return ei.art === 'link' ? ei.link.name : ei.name; });
|
|
2015
|
+
if (vorhandene.indexOf(sk.name) > -1) continue;
|
|
2016
|
+
var vergebenKey = zt.ziel.harness + '\u0000' + sk.name;
|
|
2017
|
+
if (vergebenJeHarness[vergebenKey]) {
|
|
2018
|
+
zeilenErgebnis.push({
|
|
2019
|
+
ok: false,
|
|
2020
|
+
skill: sk,
|
|
2021
|
+
harness: zt.ziel.harness,
|
|
2022
|
+
fehler: 'Ein Skill dieses Namens ist gerade verlinkt worden, hier gibt es den Namen nur einmal.',
|
|
2023
|
+
});
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
try {
|
|
2027
|
+
var gelegtAlle = await rufe(function () {
|
|
2028
|
+
return window.hmgr.linkLegen({ skill: sk.name, harness: zt.ziel.harness, ort: zt.ziel.ort, quelle: sk.quelleText });
|
|
2029
|
+
});
|
|
2030
|
+
hinweiseAlle.push(gelegtAlle.hinweis);
|
|
2031
|
+
vergebenJeHarness[vergebenKey] = true;
|
|
2032
|
+
zeilenErgebnis.push({ ok: true, skill: sk, harness: zt.ziel.harness });
|
|
2033
|
+
} catch (fehler) {
|
|
2034
|
+
zeilenErgebnis.push({ ok: false, skill: sk, harness: zt.ziel.harness, fehler: fehler.message });
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
VERZ_CHIP[verzKey(pfad)] = 'alle';
|
|
2039
|
+
ZIEL_OFFEN[verzKey(pfad)] = true;
|
|
2040
|
+
await ladeAlles();
|
|
2041
|
+
var erfolgeAlle = zeilenErgebnis.filter(function (r) { return r.ok; }).length;
|
|
2042
|
+
$('de-titel').textContent = 'Verlinkt';
|
|
2043
|
+
$('de-text').textContent = zahlwort(erfolgeAlle, 'Link angelegt', 'Links angelegt') + ' in ' + pfadKurz(pfad) + '.';
|
|
2044
|
+
setzeErfolgHinweise(hinweiseAlle);
|
|
2045
|
+
var ul2 = $('de-liste');
|
|
2046
|
+
ul2.textContent = '';
|
|
2047
|
+
zeilenErgebnis.forEach(function (r) {
|
|
2048
|
+
var li = e('li');
|
|
2049
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
2050
|
+
var s = e('span');
|
|
2051
|
+
// Derselbe Skill steht hier je Harness einmal. Der Harness gehört als Text dazu, das Zeichen
|
|
2052
|
+
// allein trägt keine Beschriftung und die Zeilen wären sonst nicht zu unterscheiden.
|
|
2053
|
+
s.appendChild(beschriftet(e('b', null, r.skill.name), r.harness));
|
|
2054
|
+
s.appendChild(e('span', 'mono', harnessName(r.harness) + (r.ok ? '' : ' · ' + r.fehler)));
|
|
2055
|
+
li.appendChild(s);
|
|
2056
|
+
ul2.appendChild(li);
|
|
2057
|
+
});
|
|
2058
|
+
setTimeout(function () { zeige($('d-erfolg')); }, 90);
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
/* ================= AKTUALISIEREN ================= */
|
|
2062
|
+
|
|
2063
|
+
/**
|
|
2064
|
+
* Ein Aktualisieren-Lauf, von Hand über den Knopf oder selbsttätig angestoßen (E44). Eine Sperre
|
|
2065
|
+
* über den disabled-Zustand des Knopfs verhindert, dass ein Anstoß während eines laufenden Laufs
|
|
2066
|
+
* etwas tut. Automatische Läufe rufen den Kern ohne Rückfrage, sie räumen also nichts weg.
|
|
2067
|
+
*/
|
|
2068
|
+
/* Stand-Zeile der Seitenleiste (Entwurf): hervorgehoben „N Quellen mit Neuem" nach einem
|
|
2069
|
+
automatischen Lauf mit Änderungen, bis die Update-Ansicht den Bericht zeigt; sonst ruhig
|
|
2070
|
+
„Alles aktuell, HH:MM geprüft", oder bei Fehlschlägen (E51) „Prüfung fehlgeschlagen, HH:MM"
|
|
2071
|
+
(alle Quellen-Abrufe gescheitert) bzw. „Geprüft mit Fehlern, HH:MM" (Mischung). Das „mit
|
|
2072
|
+
Neuem"-Signal hat Vorrang: es kündigt sich sofort nach einem automatischen Lauf an, die
|
|
2073
|
+
Fehlerkennzeichnung gilt erst wieder im ruhigen Zustand danach. */
|
|
2074
|
+
var standZeit = null;
|
|
2075
|
+
var standFehlerZustand = 'keine';
|
|
2076
|
+
function standRuhig() {
|
|
2077
|
+
var text = 'Alles aktuell, ' + standZeit + ' geprüft';
|
|
2078
|
+
if (standZeit === null) text = 'Noch nicht aktualisiert';
|
|
2079
|
+
else if (standFehlerZustand === 'alle') text = 'Prüfung fehlgeschlagen, ' + standZeit;
|
|
2080
|
+
else if (standFehlerZustand === 'gemischt') text = 'Geprüft mit Fehlern, ' + standZeit;
|
|
2081
|
+
$('standtext').textContent = text;
|
|
2082
|
+
$('standknopf').classList.remove('neu');
|
|
2083
|
+
$('standknopf').textContent = 'Prüfen';
|
|
2084
|
+
}
|
|
2085
|
+
function standNachLauf(ergebnis, automatisch, uhrzeit) {
|
|
2086
|
+
standZeit = uhrzeit;
|
|
2087
|
+
var quellen = {};
|
|
2088
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
2089
|
+
if (c.aktualisierung.ergebnis === 'aktualisiert') quellen[quelleKurz(c.checkout.quelle)] = true;
|
|
2090
|
+
});
|
|
2091
|
+
var neue = Object.keys(quellen).length;
|
|
2092
|
+
var fehlgeschlagen = ergebnis.checkouts.filter(function (c) { return c.aktualisierung.ergebnis === 'fehlgeschlagen'; }).length;
|
|
2093
|
+
var gesamt = ergebnis.checkouts.length;
|
|
2094
|
+
standFehlerZustand = fehlgeschlagen === 0 ? 'keine' : fehlgeschlagen === gesamt ? 'alle' : 'gemischt';
|
|
2095
|
+
// Von Hand läuft es in der Update-Ansicht, der Bericht ist schon sichtbar: nichts anzukündigen.
|
|
2096
|
+
if (neue === 0 || !automatisch || !$('ansicht-update').hidden) return standRuhig();
|
|
2097
|
+
var p = $('standtext');
|
|
2098
|
+
p.textContent = '';
|
|
2099
|
+
p.appendChild(e('b', null, zahlwort(neue, 'Quelle', 'Quellen')));
|
|
2100
|
+
p.appendChild(document.createTextNode(' mit Neuem'));
|
|
2101
|
+
$('standknopf').classList.add('neu');
|
|
2102
|
+
$('standknopf').textContent = 'Ansehen';
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
async function aktualisierenLauf(automatisch) {
|
|
2106
|
+
var knopf = $('updatestart');
|
|
2107
|
+
if (knopf.disabled) return;
|
|
2108
|
+
knopf.disabled = true;
|
|
2109
|
+
knopf.textContent = 'Läuft …';
|
|
2110
|
+
$('standtext').textContent = 'Prüft …';
|
|
2111
|
+
try {
|
|
2112
|
+
var ergebnis = await rufe(function () {
|
|
2113
|
+
return automatisch ? window.hmgr.aktualisieren({ rueckfragen: false }) : window.hmgr.aktualisieren();
|
|
2114
|
+
});
|
|
2115
|
+
baueBericht(ergebnis);
|
|
2116
|
+
var uhrzeit = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
|
2117
|
+
var ausloeser = automatisch ? 'automatisch' : 'von Hand';
|
|
2118
|
+
$('updatestand').textContent = 'Zuletzt ' + uhrzeit + ', ' + ausloeser + ', ' + ergebnis.checkouts.length + ' Checkouts.';
|
|
2119
|
+
standNachLauf(ergebnis, automatisch, uhrzeit);
|
|
2120
|
+
await ladeAlles();
|
|
2121
|
+
} finally {
|
|
2122
|
+
knopf.disabled = false;
|
|
2123
|
+
knopf.textContent = 'Jetzt aktualisieren';
|
|
2124
|
+
// Scheiterte der Lauf, bliebe sonst „Prüft …" stehen.
|
|
2125
|
+
if ($('standtext').textContent === 'Prüft …') standRuhig();
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
$('updatestart').addEventListener('click', function () { aktualisierenLauf(false); });
|
|
2130
|
+
// Der Standknopf sagt, was ein Klick tut: ruhig „Prüfen" (Ansicht öffnen und Lauf starten),
|
|
2131
|
+
// mit Neuem „Ansehen" (nur den Bericht zeigen, der Lauf ist schon gelaufen). Die Klasse muss
|
|
2132
|
+
// vor zeigeAnsicht gelesen werden, denn die räumt den neu-Zustand ab.
|
|
2133
|
+
$('standknopf').addEventListener('click', function () {
|
|
2134
|
+
var neu = this.classList.contains('neu');
|
|
2135
|
+
zeigeAnsicht('update');
|
|
2136
|
+
if (!neu) aktualisierenLauf(false);
|
|
2137
|
+
});
|
|
2138
|
+
window.hmgr.onAktualisierenAnstossen(function () { aktualisierenLauf(true); });
|
|
2139
|
+
|
|
2140
|
+
// Stilles Neuladen bei Änderungen außerhalb der GUI (E62, Fokus-Wechsel und Datei-Watcher): nur
|
|
2141
|
+
// ladeAlles, keine Meldung, kein Spinner-Zwang. Nachrutsch-sicher: höchstens ein Durchlauf läuft,
|
|
2142
|
+
// höchstens ein weiterer ist vorgemerkt, ein laufender Durchlauf wird nie von einem zweiten
|
|
2143
|
+
// überholt. Fehler landen wie sonst über rufe() in der Meldungsleiste, kein stiller Absturz.
|
|
2144
|
+
var stillerReloadLaeuft = false;
|
|
2145
|
+
var stillerReloadVorgemerkt = false;
|
|
2146
|
+
async function stillerReload() {
|
|
2147
|
+
// Läuft gerade ein Aktualisieren-Lauf, wird nicht parallel geladen: er endet ohnehin mit
|
|
2148
|
+
// ladeAlles und nimmt die Änderung mit. Sonst bauten zwei Läufe gleichzeitig die Ansicht auf.
|
|
2149
|
+
if ($('updatestart').disabled) return;
|
|
2150
|
+
if (stillerReloadLaeuft) {
|
|
2151
|
+
stillerReloadVorgemerkt = true;
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
stillerReloadLaeuft = true;
|
|
2155
|
+
try {
|
|
2156
|
+
await ladeAlles();
|
|
2157
|
+
} catch (fehler) {
|
|
2158
|
+
// rufe() hat den Fehler schon gemeldet, hier bleibt nur: den Durchlauf nicht wiederholen.
|
|
2159
|
+
} finally {
|
|
2160
|
+
stillerReloadLaeuft = false;
|
|
2161
|
+
if (stillerReloadVorgemerkt) {
|
|
2162
|
+
stillerReloadVorgemerkt = false;
|
|
2163
|
+
void stillerReload();
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
window.hmgr.onNeuLadenAnstossen(function () { void stillerReload(); });
|
|
2168
|
+
|
|
2169
|
+
function baueBericht(ergebnis) {
|
|
2170
|
+
var box = $('bericht');
|
|
2171
|
+
box.textContent = '';
|
|
2172
|
+
box.hidden = false;
|
|
2173
|
+
|
|
2174
|
+
var checkoutBlock = e('div', 'block');
|
|
2175
|
+
checkoutBlock.appendChild(e('span', 'etikett', 'Checkouts'));
|
|
2176
|
+
var cul = e('ul');
|
|
2177
|
+
if (ergebnis.checkouts.length === 0) cul.appendChild(e('li', null, 'Keine Checkouts.'));
|
|
2178
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
2179
|
+
var li = e('li');
|
|
2180
|
+
var a = c.aktualisierung;
|
|
2181
|
+
var art = a.ergebnis === 'aktualisiert' || a.ergebnis === 'unveraendert' ? 'ok' : 'aus';
|
|
2182
|
+
var text = a.ergebnis === 'aktualisiert' ? 'aktualisiert'
|
|
2183
|
+
: a.ergebnis === 'unveraendert' ? 'unverändert'
|
|
2184
|
+
: a.ergebnis === 'fehlgeschlagen' ? 'fehlgeschlagen (' + a.fehler + ')'
|
|
2185
|
+
: 'übersprungen (lokale Änderung)';
|
|
2186
|
+
li.appendChild(e('span', 'marke-' + art, text));
|
|
2187
|
+
li.appendChild(quelleSpan('pfad', c.checkout.quelle, ' @ ' + c.checkout.ref));
|
|
2188
|
+
cul.appendChild(li);
|
|
2189
|
+
});
|
|
2190
|
+
checkoutBlock.appendChild(cul);
|
|
2191
|
+
box.appendChild(checkoutBlock);
|
|
2192
|
+
|
|
2193
|
+
if (ergebnis.pluginHarnesses.length > 0) {
|
|
2194
|
+
var pluginBlock = e('div', 'block');
|
|
2195
|
+
pluginBlock.appendChild(e('span', 'etikett', 'Plugins'));
|
|
2196
|
+
var pul = e('ul');
|
|
2197
|
+
ergebnis.pluginHarnesses.forEach(function (name) {
|
|
2198
|
+
var li = e('li');
|
|
2199
|
+
li.appendChild(e('span', 'marke-ok', 'aktualisiert'));
|
|
2200
|
+
li.appendChild(e('span', 'pfad', name));
|
|
2201
|
+
pul.appendChild(li);
|
|
2202
|
+
});
|
|
2203
|
+
pluginBlock.appendChild(pul);
|
|
2204
|
+
box.appendChild(pluginBlock);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
if (ergebnis.aufraeumen.verwaiste.length > 0) {
|
|
2208
|
+
var raeumBlock = e('div', 'block entfernt');
|
|
2209
|
+
raeumBlock.appendChild(e('span', 'etikett', 'Verwaiste Checkouts'));
|
|
2210
|
+
var rul = e('ul');
|
|
2211
|
+
ergebnis.aufraeumen.verwaiste.forEach(function (v) {
|
|
2212
|
+
var li = e('li');
|
|
2213
|
+
var text = v.geloescht ? 'gelöscht' : v.lokaleAenderung ? 'liegen gelassen (lokale Änderung)' : 'liegen gelassen';
|
|
2214
|
+
li.appendChild(e('span', v.geloescht ? 'marke-aus' : 'marke-aus', text));
|
|
2215
|
+
li.appendChild(quelleSpan('pfad', v.checkout.quelle, ' @ ' + v.checkout.ref));
|
|
2216
|
+
rul.appendChild(li);
|
|
2217
|
+
});
|
|
2218
|
+
raeumBlock.appendChild(rul);
|
|
2219
|
+
box.appendChild(raeumBlock);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/* ================= PLUGINS ================= */
|
|
2224
|
+
|
|
2225
|
+
function baueMarketplaces() {
|
|
2226
|
+
var box = $('pluginliste');
|
|
2227
|
+
box.textContent = '';
|
|
2228
|
+
if (MARKETPLACES.length === 0) {
|
|
2229
|
+
box.appendChild(e('p', 'leerhinweis', 'Keine Quelle mit marketplace.json bekannt.'));
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
MARKETPLACES.forEach(function (m) {
|
|
2233
|
+
var markt = e('div', 'markt');
|
|
2234
|
+
var kopf = e('div', 'marktkopf');
|
|
2235
|
+
kopf.appendChild(e('h3', null, m.name));
|
|
2236
|
+
kopf.appendChild(e('span', 'mono', m.quelleText));
|
|
2237
|
+
markt.appendChild(kopf);
|
|
2238
|
+
m.plugins.forEach(function (plugin) {
|
|
2239
|
+
var zeile = e('div', 'pluginzeile');
|
|
2240
|
+
var links = e('div');
|
|
2241
|
+
links.appendChild(e('h4', null, plugin.name));
|
|
2242
|
+
links.appendChild(e('p', null, plugin.skills.length + (plugin.skills.length === 1 ? ' Skill' : ' Skills') +
|
|
2243
|
+
': ' + plugin.skills.map(function (s) { return s.name; }).join(', ')));
|
|
2244
|
+
if (plugin.dependencies.length > 0) {
|
|
2245
|
+
var kette = e('div', 'kette');
|
|
2246
|
+
kette.appendChild(e('span', 'glied', plugin.name));
|
|
2247
|
+
plugin.dependencies.forEach(function (dep) {
|
|
2248
|
+
kette.appendChild(e('span', 'pfeil', '→'));
|
|
2249
|
+
kette.appendChild(e('span', 'glied', dep));
|
|
2250
|
+
});
|
|
2251
|
+
links.appendChild(kette);
|
|
2252
|
+
}
|
|
2253
|
+
zeile.appendChild(links);
|
|
2254
|
+
markt.appendChild(zeile);
|
|
2255
|
+
});
|
|
2256
|
+
box.appendChild(markt);
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
/* ================= MCP (E61-Folgerunde, F46: reiner Katalog) ================= */
|
|
2261
|
+
/* Der Katalog aus ~/.hmgr/mcp.yaml wird hier nicht bearbeitet, hmgr liest ihn nur: kein Anlegen,
|
|
2262
|
+
kein Ändern, nur Einhängen (entwurf/gui.html). Abweichung vom Entwurf (F43): kein Erfolgsdialog
|
|
2263
|
+
und kein Wirkungs-Vermerk an der Orts-Zeile, Erfolg läuft über die bestehende Meldungsleiste
|
|
2264
|
+
(melde()), bei Claude Code mit dem Hinweis des Kerns zur nächsten Sitzung angehängt, bei Hermes
|
|
2265
|
+
ohne (der Kern liefert den Hinweis nur für Claude Code).
|
|
2266
|
+
F46 („die Ziele gehören nicht in die MCP, sondern die MCP in die Ziele"): wo ein Server hängt und
|
|
2267
|
+
das Aushängen zeigen seither nur noch die Ziel-Karten (mcpAbschnitt weiter unten), diese Ansicht
|
|
2268
|
+
bleibt reiner Katalog — Name, Beschreibung, Homepage, Konfigblock, F39-Warnzeile, Notizen,
|
|
2269
|
+
Einhängen. mcpOrtDatei/-Scope/-Spur bleiben hier stehen, sie tragen weiterhin den Einhängen-
|
|
2270
|
+
Dialog (baueMcpWahl) und den Aushängen-Text (frageMcpAus), beide von den Ziel-Karten geteilt. */
|
|
2271
|
+
|
|
2272
|
+
/* Wohin der Eintrag geschrieben wird, rein zur Anzeige: Claude Code führt Projekt- wie globale
|
|
2273
|
+
Server in ~/.claude.json (die geteilte .mcp.json der Projektwurzel bleibt unangetastet, E57),
|
|
2274
|
+
Hermes je Ort in seiner config.yaml. */
|
|
2275
|
+
function mcpOrtDatei(ziel) {
|
|
2276
|
+
if (ziel.harness === 'hermes')
|
|
2277
|
+
return ziel.ort.art === 'profil'
|
|
2278
|
+
? pfadSchreibweise('~/.hermes/profiles/' + ziel.ort.name + '/config.yaml')
|
|
2279
|
+
: pfadSchreibweise('~/.hermes/config.yaml');
|
|
2280
|
+
return pfadSchreibweise('~/.claude.json');
|
|
2281
|
+
}
|
|
2282
|
+
function mcpOrtScope(ziel) {
|
|
2283
|
+
if (ziel.harness !== 'claude-code') return null;
|
|
2284
|
+
return ziel.ort.art === 'arbeitsverzeichnis' ? 'Scope local' : 'Scope user';
|
|
2285
|
+
}
|
|
2286
|
+
/** Was in der Ort-Zeile hinter dem Namen steht: die Datei mit dem Scope. Anders als im Entwurf
|
|
2287
|
+
auch beim Arbeitsverzeichnis, denn der Ortsname der App trägt den Pfad schon (`ortName`); im
|
|
2288
|
+
Entwurf hieß er nur „Arbeitsverzeichnis", weshalb der Pfad dort hier stand. Jede Angabe
|
|
2289
|
+
einmal: Pfad im Namen, Datei und Scope daneben. */
|
|
2290
|
+
function mcpOrtSpur(ziel) {
|
|
2291
|
+
var scope = mcpOrtScope(ziel);
|
|
2292
|
+
return mcpOrtDatei(ziel) + (scope ? ' · ' + scope : '');
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function mcpKonfigArt(konfig) { return 'command' in konfig ? 'stdio' : 'HTTP'; }
|
|
2296
|
+
/** Der Konfigblock im Wortlaut, so wie er in der mcp.yaml steht: die ${VAR}-Verweise darin bleiben
|
|
2297
|
+
sichtbar, nur sie sind der Grund für die Warnzeile darunter. Nie ein Klartext-Secret (F41). */
|
|
2298
|
+
function mcpKonfigWortlaut(konfig) {
|
|
2299
|
+
if ('command' in konfig) {
|
|
2300
|
+
var text = [konfig.command].concat(konfig.args || []).join(' ');
|
|
2301
|
+
Object.keys(konfig.env || {}).forEach(function (k) { text += ' · ' + k + '=' + konfig.env[k]; });
|
|
2302
|
+
return text;
|
|
2303
|
+
}
|
|
2304
|
+
var url = konfig.url;
|
|
2305
|
+
Object.keys(konfig.headers || {}).forEach(function (k) { url += ' · ' + k + ': ' + konfig.headers[k]; });
|
|
2306
|
+
return url;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/** Die Homepage eines Servers, dasselbe Muster wie eine Quelle (E60): der Name bleibt ruhig, das
|
|
2310
|
+
Signalorange trägt nur das Pfeilsymbol. Öffnet über denselben Kanal wie eine Quelle (E60): eine
|
|
2311
|
+
http(s)-Adresse geht in den Browser. */
|
|
2312
|
+
function mcpHeimLink(url) {
|
|
2313
|
+
var a = document.createElement('a');
|
|
2314
|
+
a.className = 'qlink';
|
|
2315
|
+
a.href = '#';
|
|
2316
|
+
a.title = url;
|
|
2317
|
+
a.setAttribute('aria-label', url + ' im Browser öffnen');
|
|
2318
|
+
a.appendChild(document.createTextNode(url));
|
|
2319
|
+
a.appendChild(externSvg());
|
|
2320
|
+
a.addEventListener('click', function (ev) {
|
|
2321
|
+
ev.preventDefault();
|
|
2322
|
+
window.hmgr.quelleOeffnen(url).catch(function () {});
|
|
2323
|
+
});
|
|
2324
|
+
return a;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/** F39: die Prüf-Funktion ist dieselbe wie beim tatsächlichen Einhängen, hier aber nur gegen die
|
|
2328
|
+
Prozess-Umgebung von hmgr (der Main-Prozess liefert `fehlendeVariablen` schon fertig mit der
|
|
2329
|
+
Übersicht, ohne den ortsbezogenen .env-Abgleich, der bleibt dem Einhängen selbst vorbehalten
|
|
2330
|
+
und kommt dann als `warnung` zurück). Wiederverwendet die bestehende .ruhezeile-Optik. */
|
|
2331
|
+
function mcpWarnZeile(fehlend) {
|
|
2332
|
+
var mehrzahl = fehlend.length > 1;
|
|
2333
|
+
var namen = fehlend.map(function (v) { return '${' + v + '}'; }).join(', ');
|
|
2334
|
+
var rz = e('div', 'ruhezeile');
|
|
2335
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2336
|
+
svg.setAttribute('viewBox', '0 0 16 16');
|
|
2337
|
+
svg.setAttribute('stroke-width', '1.6');
|
|
2338
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
2339
|
+
svg.innerHTML = '<circle cx="8" cy="8" r="6.5"/><path d="M8 4.5v4M8 10.5v.5"/>';
|
|
2340
|
+
rz.appendChild(svg);
|
|
2341
|
+
rz.appendChild(e('p', null, namen + (mehrzahl ? ' sind' : ' ist') + ' in der Umgebung von hmgr nicht gesetzt. Ob '
|
|
2342
|
+
+ (mehrzahl ? 'die Variablen' : 'die Variable') + ' beim Start des Harness gesetzt ' + (mehrzahl ? 'sind' : 'ist')
|
|
2343
|
+
+ ', kann hmgr nicht wissen.'));
|
|
2344
|
+
return rz;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/** Notizen sind das Langsame am Eintrag (entwurf/gui.html): zugeklappt, öffnen sich nur, wenn
|
|
2348
|
+
jemand sie sucht. Der Wortlaut bleibt, wie er in der mcp.yaml steht, Zeilenumbrüche
|
|
2349
|
+
eingeschlossen (weiss-raum: pre-line in styles.css). */
|
|
2350
|
+
function mcpNotizenBlock(text) {
|
|
2351
|
+
var d = document.createElement('details');
|
|
2352
|
+
d.className = 'notizen';
|
|
2353
|
+
var s = document.createElement('summary');
|
|
2354
|
+
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2355
|
+
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
2356
|
+
pfeil.setAttribute('class', 'keil');
|
|
2357
|
+
pfeil.setAttribute('fill', 'none');
|
|
2358
|
+
pfeil.setAttribute('stroke', 'currentColor');
|
|
2359
|
+
pfeil.setAttribute('stroke-width', '1.7');
|
|
2360
|
+
pfeil.setAttribute('stroke-linejoin', 'round');
|
|
2361
|
+
pfeil.setAttribute('aria-hidden', 'true');
|
|
2362
|
+
pfeil.innerHTML = '<path d="M4.3 2.5 7.8 6 4.3 9.5"/>';
|
|
2363
|
+
s.appendChild(pfeil);
|
|
2364
|
+
s.appendChild(document.createTextNode('Notizen'));
|
|
2365
|
+
d.appendChild(s);
|
|
2366
|
+
d.appendChild(e('p', null, text));
|
|
2367
|
+
return d;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
function mcpKarte(k) {
|
|
2371
|
+
var eintrag = k.eintrag;
|
|
2372
|
+
var kEl = e('div', 'mcp');
|
|
2373
|
+
|
|
2374
|
+
var kopf = e('div', 'mcpkopf');
|
|
2375
|
+
kopf.appendChild(e('b', null, eintrag.name));
|
|
2376
|
+
if (eintrag.homepage) kopf.appendChild(mcpHeimLink(eintrag.homepage));
|
|
2377
|
+
var ein = e('button', 'knopf leise klein', 'Einhängen …');
|
|
2378
|
+
ein.type = 'button';
|
|
2379
|
+
ein.setAttribute('aria-label', eintrag.name + ' einhängen');
|
|
2380
|
+
ein.addEventListener('click', function () { oeffneMcpEinhaengen(k); });
|
|
2381
|
+
kopf.appendChild(ein);
|
|
2382
|
+
kEl.appendChild(kopf);
|
|
2383
|
+
|
|
2384
|
+
kEl.appendChild(e('p', 'was', eintrag.beschreibung));
|
|
2385
|
+
|
|
2386
|
+
var konf = e('div', 'kette mcpkonfig');
|
|
2387
|
+
konf.appendChild(e('span', 'glied', mcpKonfigArt(eintrag.konfig)));
|
|
2388
|
+
konf.appendChild(e('span', 'mono', mcpKonfigWortlaut(eintrag.konfig)));
|
|
2389
|
+
kEl.appendChild(konf);
|
|
2390
|
+
|
|
2391
|
+
if (k.fehlendeVariablen.length > 0) kEl.appendChild(mcpWarnZeile(k.fehlendeVariablen));
|
|
2392
|
+
if (eintrag.notizen) kEl.appendChild(mcpNotizenBlock(eintrag.notizen));
|
|
2393
|
+
|
|
2394
|
+
return kEl;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/** Ohne Katalog steht hier kein leerer Kasten, sondern der Weg zur Datei: anlegen lässt sich ein
|
|
2398
|
+
Eintrag nur dort, hmgr schreibt sie nie (E61). */
|
|
2399
|
+
function mcpEinladung() {
|
|
2400
|
+
var kasten = e('div', 'startfeld mcpstart');
|
|
2401
|
+
var p1 = e('p');
|
|
2402
|
+
p1.appendChild(document.createTextNode('Noch kein Server im Katalog. hmgr liest ihn aus '));
|
|
2403
|
+
p1.appendChild(e('span', 'mono', pfadSchreibweise('~/.hmgr/mcp.yaml')));
|
|
2404
|
+
p1.appendChild(document.createTextNode(' und schreibt die Datei nie.'));
|
|
2405
|
+
kasten.appendChild(p1);
|
|
2406
|
+
var p2 = e('p');
|
|
2407
|
+
p2.appendChild(document.createTextNode('Trag einen Server dort von Hand ein, oder lass ihn von dem LLM eintragen, das ihn ohnehin gerade einrichtet. Das Format steht in '));
|
|
2408
|
+
p2.appendChild(e('span', 'mono', 'hmgr help mcp'));
|
|
2409
|
+
p2.appendChild(document.createTextNode('.'));
|
|
2410
|
+
kasten.appendChild(p2);
|
|
2411
|
+
return kasten;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
function baueMcp() {
|
|
2415
|
+
var w = $('mcpliste');
|
|
2416
|
+
w.textContent = '';
|
|
2417
|
+
if (MCP_KATALOG.length === 0) w.appendChild(mcpEinladung());
|
|
2418
|
+
else MCP_KATALOG.forEach(function (k) { w.appendChild(mcpKarte(k)); });
|
|
2419
|
+
$('navzahlmcp').textContent = MCP_KATALOG.length;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/* ---------------- Einhängen (F42) ---------------- */
|
|
2423
|
+
|
|
2424
|
+
var mcpLaufend = null;
|
|
2425
|
+
var mcpPlaetzeAuswahl = [];
|
|
2426
|
+
// Sperre gegen Doppel-Anstoß (wie beim Hinzufügen einer Quelle, E54): zwischen Klick und Dialog
|
|
2427
|
+
// liegt ein IPC-Aufruf. Ein zweiter Klick in dieser Lücke riefe showModal() an einem schon
|
|
2428
|
+
// offenen Dialog auf, und das ist ein Fehler, kein zweiter Dialog.
|
|
2429
|
+
var mcpWahlOeffnet = false;
|
|
2430
|
+
|
|
2431
|
+
async function oeffneMcpEinhaengen(k) {
|
|
2432
|
+
if (mcpWahlOeffnet || $('d-mcp').open) return;
|
|
2433
|
+
mcpWahlOeffnet = true;
|
|
2434
|
+
try {
|
|
2435
|
+
await baueMcpWahl(k);
|
|
2436
|
+
} finally {
|
|
2437
|
+
mcpWahlOeffnet = false;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
async function baueMcpWahl(k) {
|
|
2442
|
+
mcpLaufend = k;
|
|
2443
|
+
$('dm-name').textContent = k.eintrag.name;
|
|
2444
|
+
$('dm-text').textContent = 'Ein Ort je Einhängen. agents kennt keine MCP-Server und steht deshalb nicht zur Wahl.';
|
|
2445
|
+
var box = $('dm-orte');
|
|
2446
|
+
box.textContent = '';
|
|
2447
|
+
// Die Plätze kommen aus dem Kern (F42, E16): die Fähigkeit hängt am Adapter, nicht am
|
|
2448
|
+
// Harness-Namen, hier wird nicht selbst entschieden, wer welche Ebene kennt.
|
|
2449
|
+
var plaetze = await rufe(function () { return window.hmgr.mcpPlaetze(); });
|
|
2450
|
+
mcpPlaetzeAuswahl = plaetze;
|
|
2451
|
+
plaetze.forEach(function (ziel, i) {
|
|
2452
|
+
var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
|
|
2453
|
+
var aktiv = harnessAktiv(ziel.harness);
|
|
2454
|
+
var label = e('label', 'wahlzeile');
|
|
2455
|
+
var input = document.createElement('input');
|
|
2456
|
+
input.type = 'radio';
|
|
2457
|
+
input.name = 'mcport';
|
|
2458
|
+
input.value = String(i);
|
|
2459
|
+
input.disabled = schon || !aktiv;
|
|
2460
|
+
label.appendChild(input);
|
|
2461
|
+
|
|
2462
|
+
var txt = e('span');
|
|
2463
|
+
txt.appendChild(beschriftet(e('b', null, zielTitel({ ziel: ziel })), ziel.harness));
|
|
2464
|
+
txt.appendChild(e('span', 'mono', mcpOrtSpur(ziel)));
|
|
2465
|
+
|
|
2466
|
+
if (schon) {
|
|
2467
|
+
// F46: die Orte-Liste am Katalog-Eintrag gibt es nicht mehr, der Weg zum Aushängen führt
|
|
2468
|
+
// über den Entfernen-Knopf im MCP-Abschnitt der Ziel-Karte.
|
|
2469
|
+
txt.appendChild(e('span', 'notiz', 'Hängt hier schon. Zum Aushängen den Entfernen-Knopf am MCP-Abschnitt der Ziel-Karte benutzen.'));
|
|
2470
|
+
} else if (!aktiv) {
|
|
2471
|
+
txt.appendChild(e('span', 'notiz', harnessName(ziel.harness) + ' ist inaktiv. Schalt den Harness in den Einstellungen wieder ein, um hier einzuhängen.'));
|
|
2472
|
+
} else if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
2473
|
+
// E64: die Datei bleibt beim Einhängen unangetastet, aber nicht mehr grundsätzlich — der
|
|
2474
|
+
// Übernehmen-Knopf an der Ziel-Karte fasst sie auf ausdrücklichen Auftrag an.
|
|
2475
|
+
txt.appendChild(e('span', 'notiz', 'Gilt nur für dich. Eingehängt wird neben der geteilten .mcp.json des Projekts, die bleibt dabei unverändert.'));
|
|
2476
|
+
} else if (ziel.harness === 'hermes' && ziel.ort.art === 'home') {
|
|
2477
|
+
txt.appendChild(e('span', 'notiz', 'Hermes kennt keine Projektebene für MCP-Server, hier geht nur global.'));
|
|
2478
|
+
}
|
|
2479
|
+
label.appendChild(txt);
|
|
2480
|
+
box.appendChild(label);
|
|
2481
|
+
});
|
|
2482
|
+
zeige($('d-mcp'));
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
$('d-mcp').addEventListener('submit', function (ev) {
|
|
2486
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
2487
|
+
var gewaehlt = $('dm-orte').querySelector('input:checked');
|
|
2488
|
+
if (!gewaehlt) ev.preventDefault();
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
nachDialog($('d-mcp'), async function (wert) {
|
|
2493
|
+
var k = mcpLaufend;
|
|
2494
|
+
mcpLaufend = null;
|
|
2495
|
+
if (wert !== 'los' || !k) return;
|
|
2496
|
+
var gewaehlt = $('dm-orte').querySelector('input:checked');
|
|
2497
|
+
if (!gewaehlt) return;
|
|
2498
|
+
var ziel = mcpPlaetzeAuswahl[+gewaehlt.value];
|
|
2499
|
+
var ergebnis = await rufe(function () {
|
|
2500
|
+
return window.hmgr.mcpEinhaengen({ name: k.eintrag.name, harness: ziel.harness, ort: ziel.ort });
|
|
2501
|
+
});
|
|
2502
|
+
await ladeAlles();
|
|
2503
|
+
// Erfolg über die Meldungsleiste (F43): kein Erfolgsdialog, kein Vermerk an der Orts-Zeile. Der
|
|
2504
|
+
// Kern liefert den Hinweis zur Wirkung nur bei Claude Code, Hermes lädt selbst nach.
|
|
2505
|
+
var teile = [k.eintrag.name + ' hängt jetzt in ' + zielTitel({ ziel: ziel }) + '.'];
|
|
2506
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2507
|
+
if (ergebnis.warnung) teile.push(ergebnis.warnung);
|
|
2508
|
+
melde(teile.join('\n'));
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2511
|
+
/* ---------------- Aushängen ---------------- */
|
|
2512
|
+
|
|
2513
|
+
var mcpAusKontext = null;
|
|
2514
|
+
function frageMcpAus(eintrag, ziel) {
|
|
2515
|
+
mcpAusKontext = { eintrag: eintrag, ziel: ziel };
|
|
2516
|
+
$('dma-titel').textContent = eintrag.name + ' aus ' + zielTitel({ ziel: ziel }) + ' aushängen?';
|
|
2517
|
+
$('dma-text').textContent = 'Der Eintrag verschwindet aus ' + mcpOrtDatei(ziel)
|
|
2518
|
+
+ '. Der Katalog-Eintrag bleibt, du kannst ihn jederzeit wieder einhängen.';
|
|
2519
|
+
zeige($('d-mcpaus'));
|
|
2520
|
+
}
|
|
2521
|
+
nachDialog($('d-mcpaus'), async function (wert) {
|
|
2522
|
+
var kontext = mcpAusKontext;
|
|
2523
|
+
mcpAusKontext = null;
|
|
2524
|
+
if (wert !== 'ja' || !kontext) return;
|
|
2525
|
+
var ergebnis = await rufe(function () {
|
|
2526
|
+
return window.hmgr.mcpAushaengen({ name: kontext.eintrag.name, harness: kontext.ziel.harness, ort: kontext.ziel.ort });
|
|
2527
|
+
});
|
|
2528
|
+
await ladeAlles();
|
|
2529
|
+
var teile = [kontext.eintrag.name + ' aus ' + zielTitel({ ziel: kontext.ziel }) + ' ausgehängt.'];
|
|
2530
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2531
|
+
melde(teile.join('\n'));
|
|
2532
|
+
});
|
|
2533
|
+
|
|
2534
|
+
/* ---------------- Übernehmen aus der geteilten .mcp.json (E64) ---------------- */
|
|
2535
|
+
/* Der einzige Weg, auf dem hmgr die geteilte Datei anfasst, deshalb eine ausdrückliche Rückfrage:
|
|
2536
|
+
der Eintrag wandert in die private Konfiguration und verschwindet aus der Datei — für alle, die
|
|
2537
|
+
sie teilen. Der Name muss nicht im Katalog stehen, übernommen wird der Block aus der Datei. */
|
|
2538
|
+
var mcpUebernahmeKontext = null;
|
|
2539
|
+
function frageMcpUebernahme(name, ziel, datei) {
|
|
2540
|
+
mcpUebernahmeKontext = { name: name, ziel: ziel };
|
|
2541
|
+
$('dmu-titel').textContent = name + ' übernehmen?';
|
|
2542
|
+
$('dmu-text').textContent = name + ' wird in deine private Konfiguration für '
|
|
2543
|
+
+ zielTitel({ ziel: ziel }) + ' übernommen und aus ' + (datei || '.mcp.json')
|
|
2544
|
+
+ ' entfernt. Ist die Datei mit dem Team geteilt, verlieren die anderen den Eintrag, sobald die '
|
|
2545
|
+
+ 'Änderung committet ist.';
|
|
2546
|
+
zeige($('d-mcpueb'));
|
|
2547
|
+
}
|
|
2548
|
+
nachDialog($('d-mcpueb'), async function (wert) {
|
|
2549
|
+
var kontext = mcpUebernahmeKontext;
|
|
2550
|
+
mcpUebernahmeKontext = null;
|
|
2551
|
+
if (wert !== 'ja' || !kontext) return;
|
|
2552
|
+
var ergebnis = await rufe(function () {
|
|
2553
|
+
return window.hmgr.mcpUebernehmen({ name: kontext.name, harness: kontext.ziel.harness, ort: kontext.ziel.ort });
|
|
2554
|
+
});
|
|
2555
|
+
await ladeAlles();
|
|
2556
|
+
var teile = [kontext.name + ' gehört jetzt zu deiner privaten Konfiguration für '
|
|
2557
|
+
+ zielTitel({ ziel: kontext.ziel }) + ' und steht nicht mehr in ' + ergebnis.datei + '.'];
|
|
2558
|
+
if (ergebnis.hinweis) teile.push(ergebnis.hinweis);
|
|
2559
|
+
melde(teile.join('\n'));
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
/* ---------------- MCP hinzufügen (E61-GUI-Nachtrag) ---------------- */
|
|
2563
|
+
/* Gegenrichtung zu oeffneMcpEinhaengen: von der Ziel-Karte aus, Mehrfachauswahl über den ganzen
|
|
2564
|
+
Katalog, dasselbe Dialogmuster wie „Skills hinzufügen" (E47) statt der Ortswahl von d-mcp. */
|
|
2565
|
+
var mcpHinzufuegenKontext = null;
|
|
2566
|
+
function dmhBilanzAktualisieren() {
|
|
2567
|
+
var n = $('dmh-liste').querySelectorAll('input:checked').length;
|
|
2568
|
+
$('dmh-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
2569
|
+
}
|
|
2570
|
+
function oeffneMcpHinzufuegen(ziel) {
|
|
2571
|
+
mcpHinzufuegenKontext = { ziel: ziel };
|
|
2572
|
+
$('dmh-ziel').textContent = zielTitel({ ziel: ziel });
|
|
2573
|
+
var box = $('dmh-liste');
|
|
2574
|
+
box.textContent = '';
|
|
2575
|
+
// Ohne Katalog stünde hier ein leerer Kasten mit einem Knopf, der nichts tut: hmgr legt
|
|
2576
|
+
// MCP-Einträge nie selbst an (E61), also nennt der Dialog denselben Weg wie die MCP-Ansicht.
|
|
2577
|
+
if (MCP_KATALOG.length === 0) {
|
|
2578
|
+
box.appendChild(e('p', 'ohne', 'Noch kein Server im Katalog. hmgr liest ihn aus '
|
|
2579
|
+
+ pfadSchreibweise('~/.hmgr/mcp.yaml') + ' und schreibt die Datei nie.'));
|
|
2580
|
+
}
|
|
2581
|
+
MCP_KATALOG.forEach(function (k, i) {
|
|
2582
|
+
var schon = k.eingehaengt.some(function (z) { return zielKey(z) === zielKey(ziel); });
|
|
2583
|
+
var label = e('label', 'wahlzeile');
|
|
2584
|
+
var input = document.createElement('input');
|
|
2585
|
+
input.type = 'checkbox';
|
|
2586
|
+
input.value = String(i);
|
|
2587
|
+
input.disabled = schon;
|
|
2588
|
+
input.addEventListener('change', dmhBilanzAktualisieren);
|
|
2589
|
+
label.appendChild(input);
|
|
2590
|
+
var txt = e('span');
|
|
2591
|
+
txt.appendChild(e('b', null, k.eintrag.name));
|
|
2592
|
+
if (k.eintrag.beschreibung) txt.appendChild(e('span', 'mono', k.eintrag.beschreibung));
|
|
2593
|
+
if (schon) txt.appendChild(e('span', 'notiz', 'Hängt hier schon.'));
|
|
2594
|
+
label.appendChild(txt);
|
|
2595
|
+
box.appendChild(label);
|
|
2596
|
+
});
|
|
2597
|
+
dmhBilanzAktualisieren();
|
|
2598
|
+
zeige($('d-mcp-hinzufuegen'));
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
$('d-mcp-hinzufuegen').addEventListener('submit', function (ev) {
|
|
2602
|
+
if (ev.submitter && ev.submitter.value === 'los') {
|
|
2603
|
+
var gewaehlt = Array.from($('dmh-liste').querySelectorAll('input:checked'));
|
|
2604
|
+
if (gewaehlt.length === 0) ev.preventDefault();
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
nachDialog($('d-mcp-hinzufuegen'), async function (wert) {
|
|
2609
|
+
if (wert !== 'los' || mcpHinzufuegenKontext === null) return;
|
|
2610
|
+
var ziel = mcpHinzufuegenKontext.ziel;
|
|
2611
|
+
var indizes = Array.from($('dmh-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
2612
|
+
var erfolge = 0;
|
|
2613
|
+
var hinweise = [];
|
|
2614
|
+
var fehlschlaege = [];
|
|
2615
|
+
for (var n = 0; n < indizes.length; n++) {
|
|
2616
|
+
var k = MCP_KATALOG[indizes[n]];
|
|
2617
|
+
try {
|
|
2618
|
+
var ergebnis = await rufe(function () {
|
|
2619
|
+
return window.hmgr.mcpEinhaengen({ name: k.eintrag.name, harness: ziel.harness, ort: ziel.ort });
|
|
2620
|
+
});
|
|
2621
|
+
erfolge++;
|
|
2622
|
+
if (ergebnis.hinweis && hinweise.indexOf(ergebnis.hinweis) < 0) hinweise.push(ergebnis.hinweis);
|
|
2623
|
+
// F39-Warnungen gesammelt im Erfolgs-Toast statt je Einhängen einzeln zu melden.
|
|
2624
|
+
if (ergebnis.warnung) hinweise.push(k.eintrag.name + ': ' + ergebnis.warnung);
|
|
2625
|
+
} catch (fehler) {
|
|
2626
|
+
// rufe() hat den Fehler zwar schon in die Meldungsleiste gesetzt, die Schluss-Meldung unten
|
|
2627
|
+
// überschreibt sie aber gleich wieder. Ein Teilerfolg muss beide Seiten nennen, sonst behauptet
|
|
2628
|
+
// der Toast einen Lauf ohne Fehler. Die übrigen Einträge laufen weiter.
|
|
2629
|
+
fehlschlaege.push(k.eintrag.name + ': ' + fehler.message);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
ZIEL_OFFEN[kartenKey(ziel)] = true;
|
|
2633
|
+
await ladeAlles();
|
|
2634
|
+
var zeilen = [erfolge > 0
|
|
2635
|
+
? zahlwort(erfolge, 'MCP-Server hängt', 'MCP-Server hängen') + ' jetzt in ' + zielTitel({ ziel: ziel }) + '.'
|
|
2636
|
+
: 'Nichts eingehängt in ' + zielTitel({ ziel: ziel }) + '.'];
|
|
2637
|
+
hinweise.forEach(function (h) { zeilen.push(h); });
|
|
2638
|
+
if (fehlschlaege.length > 0) {
|
|
2639
|
+
zeilen.push(zahlwort(fehlschlaege.length, 'Eintrag ließ', 'Einträge ließen') + ' sich nicht einhängen:');
|
|
2640
|
+
fehlschlaege.forEach(function (f) { zeilen.push(f); });
|
|
2641
|
+
}
|
|
2642
|
+
melde(zeilen.join('\n'));
|
|
2643
|
+
});
|
|
2644
|
+
|
|
2645
|
+
/* ================= EINSTELLUNGEN ================= */
|
|
2646
|
+
|
|
2647
|
+
/** Ziele eines Harness-Typs mit mindestens einem eigenen Link oder liegen gebliebener
|
|
2648
|
+
harness-eigener Registrierung ohne Links (E38 R7): die Konfiguration, die „Säubern" wegräumt.
|
|
2649
|
+
Aus istZustand abgelesen, wie GRILL.md Entwurfskritik 4 es vorgibt. */
|
|
2650
|
+
function harnessBilanz(h) {
|
|
2651
|
+
var ziele = ZIELE.filter(function (z) {
|
|
2652
|
+
if (z.ziel.harness !== h) return false;
|
|
2653
|
+
return z.eintraege.some(function (ei) { return ei.art !== 'fremd'; }) || z.istRegistriert;
|
|
2654
|
+
});
|
|
2655
|
+
var links = 0, eintraege = 0;
|
|
2656
|
+
ziele.forEach(function (z) {
|
|
2657
|
+
links += z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
2658
|
+
if (z.istRegistriert) eintraege += 1;
|
|
2659
|
+
});
|
|
2660
|
+
return { ziele: ziele, links: links, eintraege: eintraege };
|
|
2661
|
+
}
|
|
2662
|
+
function zahlwort(n, ein, viele) { return n + ' ' + (n === 1 ? ein : viele); }
|
|
2663
|
+
function bilanzText(b) {
|
|
2664
|
+
// Wer den harness-eigenen Eintrag angelegt hat, weiß niemand (E38 R7), also wird er benannt,
|
|
2665
|
+
// nicht hmgr zugeschrieben.
|
|
2666
|
+
var teile = [b.links
|
|
2667
|
+
? zahlwort(b.links, 'Link', 'Links') + ' in ' + zahlwort(b.ziele.length, 'Ziel', 'Zielen')
|
|
2668
|
+
: zahlwort(b.ziele.length, 'leeres Ziel', 'leere Ziele')];
|
|
2669
|
+
if (b.eintraege) teile.push(zahlwort(b.eintraege, 'harness-eigener Eintrag', 'harness-eigene Einträge'));
|
|
2670
|
+
return teile.join(', ');
|
|
2671
|
+
}
|
|
2672
|
+
function fuelleBilanzListe(ul, bilanz) {
|
|
2673
|
+
ul.textContent = '';
|
|
2674
|
+
bilanz.ziele.forEach(function (z) {
|
|
2675
|
+
var linkZahl = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).length;
|
|
2676
|
+
ul.appendChild(e('li', null, pfadKurz(z.verzeichnis) + ' · ' + zahlwort(linkZahl, 'Link', 'Links')));
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/** Was „Säubern" weggeräumt hat, aus dem Ergebnis des Anwendungsfalls statt aus einer Schätzung. */
|
|
2681
|
+
function saeuberMeldung(kopf, ergebnis) {
|
|
2682
|
+
var ziele = ergebnis.ziele.filter(function (z) { return z.entfernteLinks.length > 0; });
|
|
2683
|
+
var links = 0;
|
|
2684
|
+
ziele.forEach(function (z) { links += z.entfernteLinks.length; });
|
|
2685
|
+
var eintraege = ergebnis.ziele.filter(function (z) { return z.konfigurationZurueckgebaut; }).length;
|
|
2686
|
+
var teile = [];
|
|
2687
|
+
if (links) teile.push(zahlwort(links, 'Link', 'Links') + ' in ' + zahlwort(ziele.length, 'Ziel', 'Zielen'));
|
|
2688
|
+
if (eintraege) teile.push(zahlwort(eintraege, 'harness-eigener Eintrag', 'harness-eigene Einträge'));
|
|
2689
|
+
return kopf + ' '
|
|
2690
|
+
+ (teile.length ? teile.join(' und ') + ' entfernt' : 'Es war nichts wegzuräumen')
|
|
2691
|
+
+ '. Nicht von hmgr verwaltete Einträge liegen unverändert in den Verzeichnissen.';
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
function baueHarnessliste() {
|
|
2695
|
+
var ul = $('harnessliste');
|
|
2696
|
+
// Der Neuaufbau wirft den Schalter weg, auf dem der Fokus steht. Nach einer Rückfrage ist genau
|
|
2697
|
+
// das der Fall, also merken und wieder hinsetzen.
|
|
2698
|
+
var vorher = document.activeElement;
|
|
2699
|
+
var merk = vorher && vorher.dataset && ul.contains(vorher) ? vorher.dataset.harness : null;
|
|
2700
|
+
ul.textContent = '';
|
|
2701
|
+
HARNESSE_ALLE.forEach(function (h) {
|
|
2702
|
+
var li = document.createElement('li');
|
|
2703
|
+
var label = e('label', 'harnessschalter');
|
|
2704
|
+
var input = document.createElement('input');
|
|
2705
|
+
input.type = 'checkbox';
|
|
2706
|
+
input.checked = harnessAktiv(h);
|
|
2707
|
+
input.dataset.harness = h;
|
|
2708
|
+
input.addEventListener('change', function () { schalteHarness(h, input.checked); });
|
|
2709
|
+
label.appendChild(input);
|
|
2710
|
+
|
|
2711
|
+
var txt = e('span');
|
|
2712
|
+
txt.appendChild(beschriftet(e('b', null, harnessName(h)), h));
|
|
2713
|
+
var bilanz = harnessBilanz(h);
|
|
2714
|
+
var stand = harnessAktiv(h)
|
|
2715
|
+
? (bilanz.ziele.length
|
|
2716
|
+
? zahlwort(bilanz.ziele.length, 'Ziel', 'Ziele') + ' · ' + zahlwort(bilanz.links, 'Link', 'Links')
|
|
2717
|
+
: 'aktiv, noch kein Ziel')
|
|
2718
|
+
: (bilanz.ziele.length ? 'inaktiv, Konfiguration liegt noch' : 'inaktiv');
|
|
2719
|
+
// Der Schalter bleibt auch ohne Installation stehen, damit Säubern erreichbar ist (E42).
|
|
2720
|
+
if (HARNESSE_NICHT_INSTALLIERT.indexOf(h) !== -1) stand += ' · nicht installiert';
|
|
2721
|
+
txt.appendChild(e('small', null, stand));
|
|
2722
|
+
label.appendChild(txt);
|
|
2723
|
+
li.appendChild(label);
|
|
2724
|
+
ul.appendChild(li);
|
|
2725
|
+
});
|
|
2726
|
+
if (merk) {
|
|
2727
|
+
var zurueck = ul.querySelector('input[data-harness="' + merk + '"]');
|
|
2728
|
+
if (zurueck) zurueck.focus();
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
var harnessKandidat = null;
|
|
2733
|
+
function schalteHarness(h, an) {
|
|
2734
|
+
var bilanz = harnessBilanz(h);
|
|
2735
|
+
if (an) {
|
|
2736
|
+
rufe(function () { return window.hmgr.harnessAktivieren(h); }).then(async function () {
|
|
2737
|
+
await ladeAlles();
|
|
2738
|
+
melde(harnessName(h) + ' ist wieder aktiv. ' + (bilanz.ziele.length
|
|
2739
|
+
? 'hmgr pflegt ' + zahlwort(bilanz.ziele.length, 'vorhandenes Ziel', 'vorhandene Ziele') + ' wieder.'
|
|
2740
|
+
: 'Die Plätze zum Einrichten stehen wieder offen.'));
|
|
2741
|
+
});
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
if (bilanz.ziele.length === 0) {
|
|
2745
|
+
rufe(function () { return window.hmgr.harnessDeaktivieren(h); }).then(async function () {
|
|
2746
|
+
await ladeAlles();
|
|
2747
|
+
melde(harnessName(h) + ' ist inaktiv. hmgr hat hier keine Links liegen.');
|
|
2748
|
+
});
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
harnessKandidat = h;
|
|
2752
|
+
$('dh-titel').textContent = harnessName(h) + ' deaktivieren?';
|
|
2753
|
+
beschriftet($('dh-titel'), h);
|
|
2754
|
+
$('dh-text').textContent = 'Es liegt noch Konfiguration (' + bilanzText(bilanz)
|
|
2755
|
+
+ '). Säubern entfernt die hmgr-Links und harness-eigene Einträge, nicht von hmgr verwaltete Einträge bleiben.';
|
|
2756
|
+
fuelleBilanzListe($('dh-liste'), bilanz);
|
|
2757
|
+
zeige($('d-harness'));
|
|
2758
|
+
}
|
|
2759
|
+
nachDialog($('d-harness'), async function (wert) {
|
|
2760
|
+
var h = harnessKandidat;
|
|
2761
|
+
harnessKandidat = null;
|
|
2762
|
+
if (!h) return;
|
|
2763
|
+
if (wert === 'saeubern') {
|
|
2764
|
+
await rufe(function () { return window.hmgr.harnessDeaktivieren(h); });
|
|
2765
|
+
var ergebnis = await rufe(function () { return window.hmgr.saeubern(h); });
|
|
2766
|
+
await ladeAlles();
|
|
2767
|
+
melde(saeuberMeldung(harnessName(h) + ' deaktiviert und gesäubert.', ergebnis));
|
|
2768
|
+
} else if (wert === 'nur') {
|
|
2769
|
+
var liegen = bilanzText(harnessBilanz(h));
|
|
2770
|
+
await rufe(function () { return window.hmgr.harnessDeaktivieren(h); });
|
|
2771
|
+
await ladeAlles();
|
|
2772
|
+
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.');
|
|
2773
|
+
} else {
|
|
2774
|
+
// Abbruch: der Schalter im DOM steht noch auf aus, die Einstellung nicht. Neu aufbauen setzt ihn zurück.
|
|
2775
|
+
baueHarnessliste();
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
|
|
2779
|
+
/* Derselbe Weg für Konfiguration, die schon ohne aktiven Harness dalag (Säubern-Knopf im Ziel). */
|
|
2780
|
+
var saeuberKandidat = null;
|
|
2781
|
+
function frageSaeubern(h) {
|
|
2782
|
+
saeuberKandidat = h;
|
|
2783
|
+
var bilanz = harnessBilanz(h);
|
|
2784
|
+
$('ds-titel').textContent = harnessName(h) + ' säubern?';
|
|
2785
|
+
beschriftet($('ds-titel'), h);
|
|
2786
|
+
$('ds-text').textContent = harnessName(h) + ' ist inaktiv, Konfiguration liegt noch (' + bilanzText(bilanz)
|
|
2787
|
+
+ '). Säubern entfernt die hmgr-Links und harness-eigene Einträge, nicht von hmgr verwaltete Einträge bleiben.';
|
|
2788
|
+
fuelleBilanzListe($('ds-liste'), bilanz);
|
|
2789
|
+
zeige($('d-saeubern'));
|
|
2790
|
+
}
|
|
2791
|
+
nachDialog($('d-saeubern'), async function (wert) {
|
|
2792
|
+
var h = saeuberKandidat;
|
|
2793
|
+
saeuberKandidat = null;
|
|
2794
|
+
if (wert !== 'los' || !h) return;
|
|
2795
|
+
var ergebnis = await rufe(function () { return window.hmgr.saeubern(h); });
|
|
2796
|
+
await ladeAlles();
|
|
2797
|
+
melde(saeuberMeldung(harnessName(h) + ' gesäubert.', ergebnis));
|
|
2798
|
+
});
|
|
2799
|
+
|
|
2800
|
+
function baueEinstellungen() {
|
|
2801
|
+
baueHarnessliste();
|
|
2802
|
+
var liste = $('quellenliste');
|
|
2803
|
+
liste.textContent = '';
|
|
2804
|
+
if (QUELLEN.length === 0) {
|
|
2805
|
+
liste.appendChild(e('li', null, 'Noch keine Quelle. „Quelle hinzufügen …" legt die erste an.'));
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
QUELLEN.forEach(function (q) {
|
|
2809
|
+
var li = document.createElement('li');
|
|
2810
|
+
li.appendChild(repoIcon(q));
|
|
2811
|
+
var beschriftung = e('b', null, q.name);
|
|
2812
|
+
beschriftung.title = q.text;
|
|
2813
|
+
li.appendChild(beschriftung);
|
|
2814
|
+
|
|
2815
|
+
// Zweite Zeile, zurückgenommen: volle Adresse, Ref, Skill-Zahl (E60). Ref und Zahl kommen aus
|
|
2816
|
+
// dem schon geladenen Katalog (SKILLS), eine lokale Quelle hat keinen Ref.
|
|
2817
|
+
var ref = STANDARD_REF[q.text];
|
|
2818
|
+
var zahl = SKILLS.filter(function (s) { return s.quelleText === q.text; }).length;
|
|
2819
|
+
var teile = [q.text, q.art === 'lokal' ? 'Arbeitskopie' : (ref || '–'), zahlwort(zahl, 'Skill', 'Skills')];
|
|
2820
|
+
li.appendChild(e('small', null, teile.join(' · ')));
|
|
2821
|
+
|
|
2822
|
+
// Beide Knöpfe in einem Kasten: die Zeile ist ein Grid mit drei Spalten, zwei einzelne
|
|
2823
|
+
// Knöpfe lägen sonst beide in derselben Zelle (Spalte 3, Zeile 1) übereinander.
|
|
2824
|
+
var knoepfe = e('span', 'quellenknoepfe');
|
|
2825
|
+
var stift = e('button', 'umbenennen');
|
|
2826
|
+
stift.type = 'button';
|
|
2827
|
+
stift.title = 'Namen ändern';
|
|
2828
|
+
stift.setAttribute('aria-label', q.name + ' umbenennen');
|
|
2829
|
+
var stiftSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2830
|
+
stiftSvg.setAttribute('viewBox', '0 0 16 16');
|
|
2831
|
+
stiftSvg.setAttribute('fill', 'none');
|
|
2832
|
+
stiftSvg.setAttribute('stroke', 'currentColor');
|
|
2833
|
+
stiftSvg.setAttribute('stroke-width', '1.5');
|
|
2834
|
+
stiftSvg.setAttribute('stroke-linejoin', 'round');
|
|
2835
|
+
stiftSvg.setAttribute('aria-hidden', 'true');
|
|
2836
|
+
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"/>';
|
|
2837
|
+
stift.appendChild(stiftSvg);
|
|
2838
|
+
stift.addEventListener('click', function () { oeffneQuelleUmbenennen(q); });
|
|
2839
|
+
knoepfe.appendChild(stift);
|
|
2840
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen …');
|
|
2841
|
+
weg.type = 'button';
|
|
2842
|
+
weg.setAttribute('aria-label', q.name + ' entfernen');
|
|
2843
|
+
weg.addEventListener('click', function () { frageQuelleVergessen(q); });
|
|
2844
|
+
knoepfe.appendChild(weg);
|
|
2845
|
+
li.appendChild(knoepfe);
|
|
2846
|
+
liste.appendChild(li);
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
$('einstellungen').addEventListener('click', function () { zeige($('d-einstellungen')); });
|
|
2850
|
+
|
|
2851
|
+
/* ---------------- Quelle hinzufügen (E54, E59) ---------------- */
|
|
2852
|
+
|
|
2853
|
+
// Vorschlag für den Anzeigenamen, aus der getippten URL/dem Pfad abgeleitet (E59), so lange der
|
|
2854
|
+
// Nutzer das Namensfeld nicht selbst angefasst hat (dqNameBeruehrt). Kleine Kopie derselben Regel
|
|
2855
|
+
// wie quelleAnzeigename/quelleNameAbleiten im Kern: letztes Segment ohne .git-Suffix, dazu die
|
|
2856
|
+
// SSH-Kurzschreibweise git@host:pfad, deren Trenner ein Doppelpunkt statt eines Schrägstrichs ist.
|
|
2857
|
+
function vorschlagAusText(text) {
|
|
2858
|
+
var t = text.trim();
|
|
2859
|
+
if (!t) return '';
|
|
2860
|
+
var scp = /^[^@/]+@[^@/:]+:(.+)$/.exec(t);
|
|
2861
|
+
var pfadteil = scp ? scp[1] : t;
|
|
2862
|
+
var ohneSchraeg = pfadteil.replace(/\/+$/, '');
|
|
2863
|
+
var ohneGit = ohneSchraeg.endsWith('.git') ? ohneSchraeg.slice(0, -4) : ohneSchraeg;
|
|
2864
|
+
return letztesSegment(ohneGit);
|
|
2865
|
+
}
|
|
2866
|
+
var dqNameBeruehrt = false;
|
|
2867
|
+
$('dq-name').addEventListener('input', function () { dqNameBeruehrt = true; });
|
|
2868
|
+
$('dq-text').addEventListener('input', function () {
|
|
2869
|
+
if (dqNameBeruehrt) return;
|
|
2870
|
+
$('dq-name').value = vorschlagAusText($('dq-text').value);
|
|
2871
|
+
});
|
|
2872
|
+
|
|
2873
|
+
$('quelleneu').addEventListener('click', function () {
|
|
2874
|
+
$('dq-text').value = '';
|
|
2875
|
+
$('dq-name').value = '';
|
|
2876
|
+
dqNameBeruehrt = false;
|
|
2877
|
+
$('dq-fehler').hidden = true;
|
|
2878
|
+
zeige($('d-quelle'));
|
|
2879
|
+
});
|
|
2880
|
+
enterBestaetigt($('dq-text'), $('dq-los'));
|
|
2881
|
+
enterBestaetigt($('dq-name'), $('dq-los'));
|
|
2882
|
+
|
|
2883
|
+
// Kein nachDialog: bei einem Fehler (Quelle nicht erreichbar) muss der Dialog offen bleiben und
|
|
2884
|
+
// den Fehler zeigen, statt sich wie bei Erfolg zu schließen. Deshalb der IPC-Aufruf direkt hier,
|
|
2885
|
+
// ohne rufe() (das würde immer die Meldungsleiste befüllen und werfen).
|
|
2886
|
+
$('d-quelle').addEventListener('submit', function (ev) {
|
|
2887
|
+
if (!ev.submitter || ev.submitter.value !== 'los') return;
|
|
2888
|
+
ev.preventDefault();
|
|
2889
|
+
quelleHinzufuegenLos();
|
|
2890
|
+
});
|
|
2891
|
+
// Das Hinzufügen holt die Quelle aus dem Netz, das dauert. Ohne Sperre legte ein zweiter Anstoß
|
|
2892
|
+
// in der Zwischenzeit einen zweiten Lauf an, der denselben Zustand liest und schreibt: die Quelle
|
|
2893
|
+
// stünde zweimal im Zustand. Sperre über den disabled-Zustand wie beim Aktualisieren-Knopf.
|
|
2894
|
+
async function quelleHinzufuegenLos() {
|
|
2895
|
+
var knopf = $('dq-los');
|
|
2896
|
+
if (knopf.disabled) return;
|
|
2897
|
+
var text = $('dq-text').value.trim();
|
|
2898
|
+
var name = $('dq-name').value.trim();
|
|
2899
|
+
$('dq-fehler').hidden = true;
|
|
2900
|
+
if (!text) return;
|
|
2901
|
+
knopf.disabled = true;
|
|
2902
|
+
knopf.textContent = 'Läuft …';
|
|
2903
|
+
var ergebnis;
|
|
2904
|
+
try {
|
|
2905
|
+
ergebnis = await window.hmgr.quelleHinzufuegen(text, name || undefined);
|
|
2906
|
+
} finally {
|
|
2907
|
+
knopf.disabled = false;
|
|
2908
|
+
knopf.textContent = 'Hinzufügen';
|
|
2909
|
+
}
|
|
2910
|
+
if (!ergebnis.ok) {
|
|
2911
|
+
$('dq-fehler').textContent = ergebnis.fehler;
|
|
2912
|
+
$('dq-fehler').hidden = false;
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
$('d-quelle').close('los');
|
|
2916
|
+
var daten = ergebnis.daten;
|
|
2917
|
+
await ladeAlles();
|
|
2918
|
+
if (daten.bekannt) {
|
|
2919
|
+
// Die Adresse gehört dazu (E54): gemeldet wird, unter welcher Schreibweise die Quelle geführt
|
|
2920
|
+
// wird, der Anzeigename allein sagt das nicht.
|
|
2921
|
+
melde('Quelle ist schon bekannt, geführt als „' + daten.quelle.name + '“ (' + daten.quelle.text + ').');
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
var teile = [zahlwort(daten.skills.length, 'Skill', 'Skills')];
|
|
2925
|
+
if (daten.marketplace) teile.push('Marketplace „' + daten.marketplace.name + '“');
|
|
2926
|
+
melde('Quelle „' + daten.quelle.name + '“ hinzugefügt, ' + teile.join(', ') + '.');
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
/* ---------------- Quelle umbenennen (E59) ---------------- */
|
|
2930
|
+
|
|
2931
|
+
var quelleUmbenennenKontext = null;
|
|
2932
|
+
function oeffneQuelleUmbenennen(quelle) {
|
|
2933
|
+
quelleUmbenennenKontext = quelle;
|
|
2934
|
+
$('dqu-text').textContent = '„' + quelle.name + '“ (' + quelle.text + ')';
|
|
2935
|
+
$('dqu-name').value = quelle.name;
|
|
2936
|
+
$('dqu-fehler').hidden = true;
|
|
2937
|
+
zeige($('d-quelle-umbenennen'));
|
|
2938
|
+
}
|
|
2939
|
+
enterBestaetigt($('dqu-name'), $('dqu-los'));
|
|
2940
|
+
|
|
2941
|
+
// Wie bei d-quelle: kein nachDialog, ein Fehler (Namenskollision) muss den Dialog offen halten
|
|
2942
|
+
// und den Fehler dort zeigen (E59), statt sich wie bei Erfolg zu schließen.
|
|
2943
|
+
$('d-quelle-umbenennen').addEventListener('submit', function (ev) {
|
|
2944
|
+
if (!ev.submitter || ev.submitter.value !== 'los') return;
|
|
2945
|
+
ev.preventDefault();
|
|
2946
|
+
quelleUmbenennenLos();
|
|
2947
|
+
});
|
|
2948
|
+
async function quelleUmbenennenLos() {
|
|
2949
|
+
var knopf = $('dqu-los');
|
|
2950
|
+
if (knopf.disabled || quelleUmbenennenKontext === null) return;
|
|
2951
|
+
var quelle = quelleUmbenennenKontext;
|
|
2952
|
+
var name = $('dqu-name').value.trim();
|
|
2953
|
+
$('dqu-fehler').hidden = true;
|
|
2954
|
+
if (!name) return;
|
|
2955
|
+
knopf.disabled = true;
|
|
2956
|
+
knopf.textContent = 'Läuft …';
|
|
2957
|
+
var ergebnis;
|
|
2958
|
+
try {
|
|
2959
|
+
ergebnis = await window.hmgr.quelleUmbenennen(quelle.text, name);
|
|
2960
|
+
} finally {
|
|
2961
|
+
knopf.disabled = false;
|
|
2962
|
+
knopf.textContent = 'Umbenennen';
|
|
2963
|
+
}
|
|
2964
|
+
if (!ergebnis.ok) {
|
|
2965
|
+
$('dqu-fehler').textContent = ergebnis.fehler;
|
|
2966
|
+
$('dqu-fehler').hidden = false;
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
quelleUmbenennenKontext = null;
|
|
2970
|
+
$('d-quelle-umbenennen').close('los');
|
|
2971
|
+
await ladeAlles();
|
|
2972
|
+
melde('Quelle heißt jetzt „' + ergebnis.daten.name + '“.');
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
/* ---------------- Quelle entfernen (E54) ---------------- */
|
|
2976
|
+
|
|
2977
|
+
var quelleVergessenKontext = null;
|
|
2978
|
+
/** Zahl der Links, die nach dem Entfernen als „Link ohne Quelle" liegen bleiben. Über alle Ziele,
|
|
2979
|
+
auch die von abgeschalteten Harnesses und aus Arbeitsverzeichnissen. Die Quelle eines Links
|
|
2980
|
+
ist derselbe Zustandseintrag, den die Quellenliste zeigt (der Adapter setzt sie von dort ein),
|
|
2981
|
+
also ist der Textvergleich hier die Identität, keine Anzeige-Näherung. */
|
|
2982
|
+
function betroffeneLinksZahl(quelle) {
|
|
2983
|
+
var n = 0;
|
|
2984
|
+
ZIELE.forEach(function (z) {
|
|
2985
|
+
z.eintraege.forEach(function (ei) {
|
|
2986
|
+
if (ei.art !== 'link') return;
|
|
2987
|
+
var q = ei.link.checkout.quelle;
|
|
2988
|
+
var textq = q.art === 'git' ? q.url : q.pfad;
|
|
2989
|
+
if (q.art === quelle.art && textq === quelle.text) n++;
|
|
2990
|
+
});
|
|
2991
|
+
});
|
|
2992
|
+
return n;
|
|
2993
|
+
}
|
|
2994
|
+
function frageQuelleVergessen(quelle) {
|
|
2995
|
+
quelleVergessenKontext = quelle;
|
|
2996
|
+
var n = betroffeneLinksZahl(quelle);
|
|
2997
|
+
$('dqv-titel').textContent = '„' + quelle.name + '“ entfernen?';
|
|
2998
|
+
$('dqv-text').textContent = 'Checkouts und Links bleiben liegen. '
|
|
2999
|
+
+ (n === 0
|
|
3000
|
+
? 'Es gibt aktuell keine Links dazu.'
|
|
3001
|
+
: zahlwort(n, 'Link gilt', 'Links gelten') + ' danach als „Link ohne Quelle“, hmgr aktualisiert '
|
|
3002
|
+
+ (n === 1 ? 'ihn' : 'sie') + ' nicht mehr. Wieder hinzufügen macht '
|
|
3003
|
+
+ (n === 1 ? 'ihn' : 'sie') + ' wieder zu ' + (n === 1 ? 'einem normalen Link' : 'normalen Links') + '.');
|
|
3004
|
+
zeige($('d-quelle-vergessen'));
|
|
3005
|
+
}
|
|
3006
|
+
/** Nach dem „entfernen?“ vergisst hmgr die Quelle, Checkouts und Links bleiben liegen (E54). */
|
|
3007
|
+
async function quelleVergessen(quelle) {
|
|
3008
|
+
await rufe(function () { return window.hmgr.quelleEntfernen(quelle.text); });
|
|
3009
|
+
await ladeAlles();
|
|
3010
|
+
// Der Neuaufbau der Liste wirft den Knopf weg, auf dem der Fokus nach dem Dialog steht. Der
|
|
3011
|
+
// nächstliegende Platz ist der Knopf, der eine neue Quelle anlegt; er steht fest im Markup.
|
|
3012
|
+
$('quelleneu').focus();
|
|
3013
|
+
melde('Quelle „' + quelle.name + '“ aus hmgr entfernt.');
|
|
3014
|
+
}
|
|
3015
|
+
nachDialog($('d-quelle-vergessen'), async function (wert) {
|
|
3016
|
+
if (wert !== 'ja' || quelleVergessenKontext === null) return;
|
|
3017
|
+
var quelle = quelleVergessenKontext;
|
|
3018
|
+
quelleVergessenKontext = null;
|
|
3019
|
+
// Inventar (E58) VOR jeder Wirkung erheben: quelleEntfernen macht ihre Links zu „Link ohne
|
|
3020
|
+
// Quelle“, danach ist die Zuordnung weg. Bei leerem Inventar keine Rückfrage (F31a): sofort
|
|
3021
|
+
// vergessen wie bisher.
|
|
3022
|
+
var inventar = await rufe(function () { return window.hmgr.quelleAufraeumInventar(quelle.text); });
|
|
3023
|
+
if (inventar.links.length === 0 && inventar.checkouts.length === 0) {
|
|
3024
|
+
await quelleVergessen(quelle);
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
frageQuelleAufraeumen(quelle, inventar);
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
/* ---------------- Skills der Quelle komplett löschen (E58, F31a) ---------------- */
|
|
3031
|
+
|
|
3032
|
+
var quelleAufraeumenKontext = null;
|
|
3033
|
+
function frageQuelleAufraeumen(quelle, inventar) {
|
|
3034
|
+
quelleAufraeumenKontext = { quelle: quelle, inventar: inventar };
|
|
3035
|
+
var teile = [
|
|
3036
|
+
zahlwort(inventar.links.length, 'Link', 'Links'),
|
|
3037
|
+
zahlwort(inventar.checkouts.length, 'Checkout', 'Checkouts'),
|
|
3038
|
+
];
|
|
3039
|
+
function bleibt(n) { return n === 1 ? ', bleibt liegen' : ', bleiben liegen'; }
|
|
3040
|
+
if (inventar.plugins.length > 0) {
|
|
3041
|
+
teile.push(zahlwort(inventar.plugins.length, 'installiertes Plugin', 'installierte Plugins') + bleibt(inventar.plugins.length));
|
|
3042
|
+
}
|
|
3043
|
+
if (inventar.zeigerPfade.length > 0) {
|
|
3044
|
+
teile.push(zahlwort(inventar.zeigerPfade.length, 'Zeiger-Eintrag', 'Zeiger-Einträge') + bleibt(inventar.zeigerPfade.length));
|
|
3045
|
+
}
|
|
3046
|
+
var text = 'Skills von „' + quelle.name + '“ komplett vom Rechner löschen (' + teile.join(', ') + ')?';
|
|
3047
|
+
// Warnung bei lokalen Änderungen (E15, F31a): ein Ja bricht dann ohne zu löschen ab, auch die
|
|
3048
|
+
// Quelle bleibt bekannt, sichtbar VOR der Entscheidung, damit „Behalten“ die naheliegende Wahl ist.
|
|
3049
|
+
var lok = inventar.checkoutsMitLokalenAenderungen || [];
|
|
3050
|
+
if (lok.length > 0) {
|
|
3051
|
+
text += ' Achtung: ' + zahlwort(lok.length, 'Checkout hat', 'Checkouts haben') + ' lokale Änderungen: '
|
|
3052
|
+
+ lok.map(function (c) { return pfadKurz(c.pfad); }).join(', ')
|
|
3053
|
+
+ '. Ein Ja bricht dann ab, ohne zu löschen, die Quelle bleibt bekannt.';
|
|
3054
|
+
}
|
|
3055
|
+
$('dqa-text').textContent = text;
|
|
3056
|
+
// Erst nach einer kurzen Pause, wie beim Skill-Ordner-Lösch-Angebot (E56): zwei <dialog>
|
|
3057
|
+
// lassen sich nicht im selben close-Ereignis nacheinander öffnen.
|
|
3058
|
+
setTimeout(function () { zeige($('d-quelle-aufraeumen')); }, 90);
|
|
3059
|
+
}
|
|
3060
|
+
nachDialog($('d-quelle-aufraeumen'), async function (wert) {
|
|
3061
|
+
var kontext = quelleAufraeumenKontext;
|
|
3062
|
+
quelleAufraeumenKontext = null;
|
|
3063
|
+
if (kontext === null) return;
|
|
3064
|
+
// Nein: wie bisher vergessen, alles bleibt liegen (E54).
|
|
3065
|
+
if (wert !== 'loeschen') {
|
|
3066
|
+
await quelleVergessen(kontext.quelle);
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
// Ja: erst aufräumen. Bei lokalen Änderungen bricht quelleAufraeumen ab (F31a), dann bleibt die
|
|
3070
|
+
// Quelle bekannt und nichts wird gelöscht, nicht einmal vergessen.
|
|
3071
|
+
var ergebnis;
|
|
3072
|
+
try {
|
|
3073
|
+
ergebnis = await rufe(function () { return window.hmgr.quelleAufraeumen(kontext.inventar); });
|
|
3074
|
+
} catch (fehler) {
|
|
3075
|
+
await ladeAlles();
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
await rufe(function () { return window.hmgr.quelleEntfernen(kontext.quelle.text); });
|
|
3079
|
+
await ladeAlles();
|
|
3080
|
+
$('quelleneu').focus();
|
|
3081
|
+
melde(
|
|
3082
|
+
zahlwort(ergebnis.entfernteLinks, 'Link', 'Links') + ' und '
|
|
3083
|
+
+ zahlwort(ergebnis.entfernteCheckouts, 'Checkout', 'Checkouts') + ' von „' + kontext.quelle.name + '“ gelöscht, die Quelle ist vergessen.',
|
|
3084
|
+
);
|
|
3085
|
+
});
|
|
3086
|
+
|
|
3087
|
+
/* ---------------- Skill-Ordner festlegen (E56) ---------------- */
|
|
3088
|
+
|
|
3089
|
+
var skillOrdnerZiel = null;
|
|
3090
|
+
function oeffneSkillOrdnerFestlegen(z) {
|
|
3091
|
+
skillOrdnerZiel = z;
|
|
3092
|
+
$('dso-pfad').value = '';
|
|
3093
|
+
$('dso-fehler').hidden = true;
|
|
3094
|
+
zeige($('d-skillordner'));
|
|
3095
|
+
}
|
|
3096
|
+
enterBestaetigt($('dso-pfad'), $('dso-los'));
|
|
3097
|
+
|
|
3098
|
+
// Wie bei d-quelle: bei einem Fehler (Pfad ungültig, schon verwaltet) muss der Dialog offen
|
|
3099
|
+
// bleiben und den Fehler zeigen, statt sich wie bei Erfolg zu schließen.
|
|
3100
|
+
$('d-skillordner').addEventListener('submit', function (ev) {
|
|
3101
|
+
if (!ev.submitter || ev.submitter.value !== 'los') return;
|
|
3102
|
+
ev.preventDefault();
|
|
3103
|
+
skillOrdnerFestlegenLos();
|
|
3104
|
+
});
|
|
3105
|
+
async function skillOrdnerFestlegenLos() {
|
|
3106
|
+
var knopf = $('dso-los');
|
|
3107
|
+
if (knopf.disabled || skillOrdnerZiel === null) return;
|
|
3108
|
+
var pfad = $('dso-pfad').value.trim();
|
|
3109
|
+
$('dso-fehler').hidden = true;
|
|
3110
|
+
if (!pfad) return;
|
|
3111
|
+
var z = skillOrdnerZiel;
|
|
3112
|
+
knopf.disabled = true;
|
|
3113
|
+
knopf.textContent = 'Läuft …';
|
|
3114
|
+
var ergebnis;
|
|
3115
|
+
try {
|
|
3116
|
+
ergebnis = await window.hmgr.skillOrdnerFestlegen({ harness: z.ziel.harness, ort: z.ziel.ort, pfad: pfad });
|
|
3117
|
+
} finally {
|
|
3118
|
+
knopf.disabled = false;
|
|
3119
|
+
knopf.textContent = 'Festlegen';
|
|
3120
|
+
}
|
|
3121
|
+
if (!ergebnis.ok) {
|
|
3122
|
+
$('dso-fehler').textContent = ergebnis.fehler;
|
|
3123
|
+
$('dso-fehler').hidden = false;
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
$('d-skillordner').close('los');
|
|
3127
|
+
skillOrdnerZiel = null;
|
|
3128
|
+
await ladeAlles();
|
|
3129
|
+
melde('Liest Skills jetzt aus ' + pfadKurz(ergebnis.daten) + '.');
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
/* ---------------- Skill-Ordner entfernen und löschen (E56) ---------------- */
|
|
3133
|
+
|
|
3134
|
+
var skillOrdnerEntfernenZiel = null;
|
|
3135
|
+
function frageSkillOrdnerEntfernen(z) {
|
|
3136
|
+
skillOrdnerEntfernenZiel = z;
|
|
3137
|
+
$('dsoe-titel').textContent = pfadKurz(z.verwalteterSkillOrdner) + ' nicht mehr verwalten?';
|
|
3138
|
+
$('dsoe-text').textContent = 'Der Eintrag in Hermes\' config.yaml verschwindet, der Ordner bleibt zunächst '
|
|
3139
|
+
+ 'erhalten. Danach fragt hmgr, ob er samt Inhalt gelöscht werden soll.';
|
|
3140
|
+
zeige($('d-skillordner-entfernen'));
|
|
3141
|
+
}
|
|
3142
|
+
nachDialog($('d-skillordner-entfernen'), async function (wert) {
|
|
3143
|
+
if (wert !== 'ja' || skillOrdnerEntfernenZiel === null) return;
|
|
3144
|
+
var z = skillOrdnerEntfernenZiel;
|
|
3145
|
+
skillOrdnerEntfernenZiel = null;
|
|
3146
|
+
var ergebnis = await rufe(function () {
|
|
3147
|
+
return window.hmgr.skillOrdnerEntfernen({ harness: z.ziel.harness, ort: z.ziel.ort });
|
|
3148
|
+
});
|
|
3149
|
+
await ladeAlles();
|
|
3150
|
+
melde('Skill-Ordner-Eintrag für ' + zielTitel(z) + ' entfernt.');
|
|
3151
|
+
skillOrdnerLoeschenKontext = { ziel: z.ziel, pfad: ergebnis.pfad };
|
|
3152
|
+
$('dsol-text').textContent = 'Ordner ' + pfadKurz(ergebnis.pfad) + ' enthält noch '
|
|
3153
|
+
+ zahlwort(ergebnis.hmgrLinks, 'hmgr-Link', 'hmgr-Links') + ' und '
|
|
3154
|
+
+ zahlwort(ergebnis.fremdeEintraege, 'fremden Eintrag', 'fremde Einträge') + '.';
|
|
3155
|
+
setTimeout(function () { zeige($('d-skillordner-loeschen')); }, 90);
|
|
3156
|
+
});
|
|
3157
|
+
|
|
3158
|
+
var skillOrdnerLoeschenKontext = null;
|
|
3159
|
+
nachDialog($('d-skillordner-loeschen'), async function (wert) {
|
|
3160
|
+
var kontext = skillOrdnerLoeschenKontext;
|
|
3161
|
+
skillOrdnerLoeschenKontext = null;
|
|
3162
|
+
if (wert !== 'loeschen' || kontext === null) return;
|
|
3163
|
+
await rufe(function () {
|
|
3164
|
+
return window.hmgr.skillOrdnerLoeschen({ harness: kontext.ziel.harness, ort: kontext.ziel.ort, pfad: kontext.pfad });
|
|
3165
|
+
});
|
|
3166
|
+
await ladeAlles();
|
|
3167
|
+
melde('Ordner ' + pfadKurz(kontext.pfad) + ' gelöscht.');
|
|
3168
|
+
});
|
|
3169
|
+
|
|
3170
|
+
/* ================= RÜCKFRAGE VOM MAIN-PROZESS (E28, E34) ================= */
|
|
3171
|
+
|
|
3172
|
+
// Die Fragen kommen fertig formuliert aus dem Kern; für die Anzeige werden Home-Pfade darin
|
|
3173
|
+
// wie überall sonst auf ~ gekürzt (nur Anzeige, die Antwort transportiert keinen Text zurück).
|
|
3174
|
+
/** Wie pfadKurz, aber mitten im Fließtext einer Rückfrage — und ebenso mit beiden Trennern (E68). */
|
|
3175
|
+
function frageKurz(text) {
|
|
3176
|
+
if (!HOME) return text;
|
|
3177
|
+
return text.split(HOME + '/').join('~/').split(HOME + '\\').join('~\\');
|
|
3178
|
+
}
|
|
3179
|
+
window.hmgr.onRueckfrage(function (nachricht) {
|
|
3180
|
+
if (nachricht.art === 'bestaetigen') {
|
|
3181
|
+
$('db-frage').textContent = frageKurz(nachricht.frage);
|
|
3182
|
+
zeige($('d-bestaetigen'));
|
|
3183
|
+
$('d-bestaetigen').addEventListener(
|
|
3184
|
+
'close',
|
|
3185
|
+
function () { window.hmgr.antwortenRueckfrage(nachricht.id, $('d-bestaetigen').returnValue === 'ja'); },
|
|
3186
|
+
{ once: true },
|
|
3187
|
+
);
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
$('da-frage').textContent = frageKurz(nachricht.frage);
|
|
3191
|
+
var box = $('da-optionen');
|
|
3192
|
+
box.textContent = '';
|
|
3193
|
+
(nachricht.optionen || []).forEach(function (option, i) {
|
|
3194
|
+
var label = e('label', 'wahlzeile');
|
|
3195
|
+
label.appendChild(e('span', 'nummer', String(i + 1)));
|
|
3196
|
+
var innen = e('span');
|
|
3197
|
+
var input = document.createElement('input');
|
|
3198
|
+
input.type = 'radio';
|
|
3199
|
+
input.name = 'auswahl-' + nachricht.id;
|
|
3200
|
+
input.value = String(i);
|
|
3201
|
+
input.className = 'versteckt';
|
|
3202
|
+
if (i === 0) input.checked = true;
|
|
3203
|
+
innen.appendChild(input);
|
|
3204
|
+
innen.appendChild(e('b', null, option));
|
|
3205
|
+
label.appendChild(innen);
|
|
3206
|
+
box.appendChild(label);
|
|
3207
|
+
});
|
|
3208
|
+
zeige($('d-auswahl'));
|
|
3209
|
+
$('d-auswahl').addEventListener(
|
|
3210
|
+
'close',
|
|
3211
|
+
function () {
|
|
3212
|
+
var wert = $('d-auswahl').returnValue;
|
|
3213
|
+
if (wert !== 'weiter') {
|
|
3214
|
+
window.hmgr.antwortenRueckfrage(nachricht.id, undefined);
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
var gewaehlt = box.querySelector('input:checked');
|
|
3218
|
+
window.hmgr.antwortenRueckfrage(nachricht.id, gewaehlt ? +gewaehlt.value : undefined);
|
|
3219
|
+
},
|
|
3220
|
+
{ once: true },
|
|
3221
|
+
);
|
|
3222
|
+
});
|
|
3223
|
+
|
|
3224
|
+
/* ---------------- Start ---------------- */
|
|
3225
|
+
|
|
3226
|
+
ladeUmgebung().then(ladeAlles).catch(function (fehler) { melde(fehler.message || String(fehler)); });
|
|
3227
|
+
})();
|