hmgr 0.0.6 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/agents.js +14 -5
- package/dist/adapter/bundle-yaml.js +87 -0
- package/dist/adapter/claude-code.js +53 -12
- package/dist/adapter/dateisystem.js +85 -43
- package/dist/adapter/git.js +148 -0
- package/dist/adapter/heime.js +76 -0
- package/dist/adapter/hermes.js +16 -7
- package/dist/adapter/npm-registry.js +61 -0
- package/dist/adapter/paket-version.js +25 -0
- package/dist/adapter/umgebung.js +6 -1
- package/dist/adapter/verlauf-json.js +39 -0
- package/dist/adapter/wsl.js +212 -0
- package/dist/adapter/zustand-json.js +40 -12
- package/dist/cli/hilfe.js +197 -11
- package/dist/cli/main.js +376 -21
- package/dist/gui/hmgr-cli.js +44 -0
- package/dist/gui/main.js +445 -4
- package/dist/gui/preload.cjs +30 -2
- package/dist/gui/renderer/index.html +79 -16
- package/dist/gui/renderer/renderer.js +807 -286
- package/dist/gui/renderer/styles.css +89 -16
- package/dist/kern/anwendungsfaelle.js +687 -18
- package/dist/kern/modell.js +192 -1
- package/dist/kern/version.js +13 -0
- package/dist/ports/dateizugriff.js +1 -0
- package/dist/ports/harness.js +4 -0
- package/dist/ports/heime.js +1 -0
- package/dist/ports/verlauf.js +4 -0
- package/dist/ports/zustand.js +16 -1
- package/package.json +26 -5
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
// Pfade, die hier angezeigt werden, baut der Kern mit `node:path`, also mit dem Trenner der
|
|
21
21
|
// Plattform — auch dann, wenn HOME selbst in POSIX-Schreibweise gesetzt ist (MSYS/Git-Bash).
|
|
22
22
|
var TRENNER = '/';
|
|
23
|
+
// Die Plattform selbst (E78): entscheidet, ob der Weg zu einem Homedir in WSL überhaupt im Bild
|
|
24
|
+
// steht. Die Grenze zieht der Kern; hier soll nur kein Knopf stehen, der sicher abbricht.
|
|
25
|
+
var PLATTFORM = '';
|
|
23
26
|
/** Trenner einer Plattform, wie `process.platform` sie nennt. Rein, nur für die Anzeige. */
|
|
24
27
|
function plattformTrenner(plattform) { return plattform === 'win32' ? '\\' : '/'; }
|
|
25
28
|
/**
|
|
@@ -163,15 +166,20 @@
|
|
|
163
166
|
|
|
164
167
|
/* ---------------- Ort- und Grund-Texte ---------------- */
|
|
165
168
|
|
|
169
|
+
/* Ein zusätzliches Homedir (E78) heißt nach seiner Adresse, damit die Karten „claude-code · Home"
|
|
170
|
+
und „claude-code · wsl:Ubuntu" im Bild auseinanderzuhalten sind. */
|
|
166
171
|
function ortName(ort) {
|
|
167
|
-
if (ort.art === 'home') return 'Home';
|
|
172
|
+
if (ort.art === 'home') return ort.heim ? ort.heim : 'Home';
|
|
168
173
|
if (ort.art === 'profil') return 'Profil „' + ort.name + '“';
|
|
169
174
|
return 'Arbeitsverzeichnis ' + pfadKurz(ort.pfad);
|
|
170
175
|
}
|
|
171
176
|
function zielTitel(z) { return harnessName(z.ziel.harness) + ' · ' + ortName(z.ziel.ort); }
|
|
177
|
+
/* Gleichlautend mit zielSchluessel im Kern: ein Ort ohne heim behält seinen bisherigen Schlüssel. */
|
|
172
178
|
function zielKey(ziel) {
|
|
173
179
|
var o = ziel.ort;
|
|
174
|
-
var ort = o.art === 'home'
|
|
180
|
+
var ort = o.art === 'home'
|
|
181
|
+
? (o.heim ? 'home:' + o.heim : 'home')
|
|
182
|
+
: o.art === 'profil' ? 'profil:' + o.name : 'dir:' + o.pfad;
|
|
175
183
|
return ziel.harness + '/' + ort;
|
|
176
184
|
}
|
|
177
185
|
function quelleKurz(quelle) { return quelle.art === 'git' ? quelle.url : quelle.pfad; }
|
|
@@ -303,10 +311,18 @@
|
|
|
303
311
|
var ZIELE = [];
|
|
304
312
|
var SKILLS = [];
|
|
305
313
|
var MARKETPLACES = [];
|
|
314
|
+
// Bundles aller bekannten Quellen (E69), entdeckt beim Quellen-Scan: kein eigener Katalog, keine
|
|
315
|
+
// eigene Ansicht. Grundlage der Bundle-Sektion im Dialog „Skills hinzufügen" (F68).
|
|
316
|
+
var BUNDLES = [];
|
|
317
|
+
// Definitionen, die sich nicht lesen ließen: genannt, damit eine kaputte Datei nicht stumm fehlt.
|
|
318
|
+
var BUNDLES_KAPUTT = [];
|
|
306
319
|
var QUELLEN = [];
|
|
307
320
|
var HARNESSE_ALLE = [];
|
|
308
321
|
var AKTIVE_HARNESSE = [];
|
|
309
322
|
var HARNESSE_NICHT_INSTALLIERT = [];
|
|
323
|
+
// Homedirs je Harness (E78): je Eintrag { harness, heime: [{ adresse?, pfad, eingestellt }],
|
|
324
|
+
// kannZusaetzliche }. Grundlage der Homedir-Zeilen in den Einstellungen.
|
|
325
|
+
var HARNESS_HEIME = [];
|
|
310
326
|
// MCP-Katalog (E61-Folgerunde): je Eintrag { eintrag, eingehaengt, fehlendeVariablen }, dazu
|
|
311
327
|
// Einträge in Harness-Konfigurationen, die nicht im Katalog stehen (nur genannt). Seit F46 nur
|
|
312
328
|
// noch Grundlage für den MCP-Abschnitt der Ziel-Karten (mcpZeilenFuerZiel), die MCP-Ansicht selbst
|
|
@@ -321,6 +337,14 @@
|
|
|
321
337
|
// Ziel-Karten — nie am Harness-Namen entschieden, sondern an dieser Liste.
|
|
322
338
|
var MCP_PLAETZE = [];
|
|
323
339
|
var quellenFilter = 'alle';
|
|
340
|
+
// Sortierung der Skills-Rubrik (E79, Richtung E81): hält für die Sitzung wie quellenFilter,
|
|
341
|
+
// übersteht also ladeAlles. SKILLS selbst bleibt unsortiert nach Datum, damit li.dataset.index
|
|
342
|
+
// stabil bleibt.
|
|
343
|
+
var skillSortierung = 'name';
|
|
344
|
+
// Sortierrichtung je Schlüssel (E81): 'auf' oder 'ab'. Ein Klick auf den bereits aktiven Chip
|
|
345
|
+
// kehrt sie um, ein Klick auf den anderen Chip setzt die Standardrichtung des neuen Schlüssels.
|
|
346
|
+
var skillRichtung = 'auf';
|
|
347
|
+
var SKILL_RICHTUNG_STANDARD = { name: 'auf', datum: 'ab' };
|
|
324
348
|
var zielFilter = 'alle';
|
|
325
349
|
// Standard-Ref je Quelle (E60), aus dem Katalog abgeleitet (SKILLS trägt ihn schon je Skill): ein
|
|
326
350
|
// Ref an einem Link erscheint nur, wenn er davon abweicht (`@v2.1`). Schlüssel wie quelleKurz.
|
|
@@ -330,19 +354,43 @@
|
|
|
330
354
|
|
|
331
355
|
// Ignorierte fremde Einträge zählen hier nicht mit (E43): Ignorieren blendet die Warnung aus,
|
|
332
356
|
// sonst bliebe das Ziel im Filter „braucht Aufmerksamkeit" stehen.
|
|
333
|
-
function
|
|
357
|
+
function zielAusloeser(z) {
|
|
334
358
|
var ignoriert = z.ignoriert || [];
|
|
335
|
-
return z.eintraege.
|
|
359
|
+
return z.eintraege.filter(function (ei) {
|
|
336
360
|
if (ei.art === 'fremd') return ignoriert.indexOf(ei.name) === -1;
|
|
337
361
|
return ei.art === 'link-ohne-quelle';
|
|
338
|
-
});
|
|
362
|
+
}).length;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Warnung und Warnzahl (E75) sind dieselbe Regel, einmal geschrieben: die Warnung steht genau
|
|
366
|
+
// dann, wenn die Zählung etwas findet — sonst könnten Dreieck und Zahl auseinanderlaufen.
|
|
367
|
+
function zielBraucht(z) { return zielAusloeser(z) > 0; }
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Warnzeichen für den Kartenkopf, mit der Zahl der Auslöser daneben (E75): eine Karte mit
|
|
371
|
+
* „0 Skills" und fünfzehn fremden Einträgen wirkte sonst leer, obwohl der Ordner voll ist. Die
|
|
372
|
+
* E71-Zahl (eigene Skills) bleibt daneben stehen, die Warnzahl ist eine eigene Information.
|
|
373
|
+
* Zeichen statt Text-Badge: trägt die Farbe selbst (Emoji-Darstellung), aria-label hält den
|
|
374
|
+
* Akkordeon-Knopf-Namen brauchbar (role="img", die Bedeutung steht nicht im Glyph selbst) und
|
|
375
|
+
* nennt die Zahl mit, sonst hörte sie im Screenreader gar nicht.
|
|
376
|
+
*/
|
|
377
|
+
function warnzeichen(anzahl) {
|
|
378
|
+
var span = e('span', 'warnung');
|
|
379
|
+
span.setAttribute('role', 'img');
|
|
380
|
+
span.setAttribute(
|
|
381
|
+
'aria-label',
|
|
382
|
+
anzahl === 1 ? '1 Eintrag braucht Aufmerksamkeit' : anzahl + ' Einträge brauchen Aufmerksamkeit',
|
|
383
|
+
);
|
|
384
|
+
span.appendChild(document.createTextNode('⚠️'));
|
|
385
|
+
span.appendChild(e('span', 'warnzahl', String(anzahl)));
|
|
386
|
+
return span;
|
|
339
387
|
}
|
|
340
388
|
|
|
341
|
-
// E45: ein Ziel eines inaktiven Harness
|
|
342
|
-
// fremde Einträge
|
|
389
|
+
// E74 (revidiert E45): ein Ziel eines inaktiven Harness erscheint gar nicht, auch wenn dort noch
|
|
390
|
+
// Links, fremde Einträge oder eine harness-eigene Registrierung liegen — was hmgr nicht managed,
|
|
391
|
+
// zeigt es nicht. Beim Aktivieren taucht das Ziel samt allem, was liegen blieb, wieder auf.
|
|
343
392
|
function zielSichtbar(z) {
|
|
344
|
-
|
|
345
|
-
return z.eintraege.length > 0 || z.istRegistriert;
|
|
393
|
+
return harnessAktiv(z.ziel.harness);
|
|
346
394
|
}
|
|
347
395
|
|
|
348
396
|
// Von Hand aufgeklappte Ziele bleiben über Neuaufbauten der Liste offen (E45), Schlüssel wie
|
|
@@ -386,6 +434,7 @@
|
|
|
386
434
|
rufe(function () { return window.hmgr.plattform(); }),
|
|
387
435
|
]);
|
|
388
436
|
HOME = werte[0];
|
|
437
|
+
PLATTFORM = werte[1];
|
|
389
438
|
TRENNER = plattformTrenner(werte[1]);
|
|
390
439
|
$('mcpkatalogpfad').textContent = pfadSchreibweise('~/.hmgr/mcp.yaml');
|
|
391
440
|
}
|
|
@@ -404,6 +453,8 @@
|
|
|
404
453
|
rufe(function () { return window.hmgr.nichtVorhandeneHarnesses(); }),
|
|
405
454
|
rufe(function () { return window.hmgr.mcpUebersicht(); }),
|
|
406
455
|
rufe(function () { return window.hmgr.mcpPlaetze(); }),
|
|
456
|
+
rufe(function () { return window.hmgr.bundles(); }),
|
|
457
|
+
rufe(function () { return window.hmgr.harnessHeime(); }),
|
|
407
458
|
]);
|
|
408
459
|
ZIELE = ergebnisse[0];
|
|
409
460
|
SKILLS = ergebnisse[1];
|
|
@@ -418,6 +469,10 @@
|
|
|
418
469
|
MCP_FREMD = ergebnisse[7].unbekannt;
|
|
419
470
|
MCP_FREMD_GETEILT = ergebnisse[7].unbekannteGeteilteFundorte;
|
|
420
471
|
MCP_PLAETZE = ergebnisse[8];
|
|
472
|
+
BUNDLES = ergebnisse[9].bundles;
|
|
473
|
+
BUNDLES.sort(function (a, b) { return a.name.localeCompare(b.name, 'de') || a.quelleText.localeCompare(b.quelleText, 'de'); });
|
|
474
|
+
BUNDLES_KAPUTT = ergebnisse[9].kaputte;
|
|
475
|
+
HARNESS_HEIME = ergebnisse[10] || [];
|
|
421
476
|
STANDARD_REF = {};
|
|
422
477
|
SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
|
|
423
478
|
|
|
@@ -484,8 +539,12 @@
|
|
|
484
539
|
else a.removeAttribute('aria-current');
|
|
485
540
|
});
|
|
486
541
|
ANSICHTEN.forEach(function (x) { $('ansicht-' + x).hidden = x !== name; });
|
|
487
|
-
|
|
488
|
-
|
|
542
|
+
if (name === 'update') {
|
|
543
|
+
// Die Ansicht ist jetzt offen, die Ankündigung „mit Neuem" hat ihren Dienst getan (E80).
|
|
544
|
+
if (standNeu !== null) { standNeu = null; standText(); }
|
|
545
|
+
// Der Verlauf zeigt immer den aktuellen Stand der Datei, nicht nur den des letzten Laufs hier.
|
|
546
|
+
void ladeVerlauf();
|
|
547
|
+
}
|
|
489
548
|
window.scrollTo(0, 0);
|
|
490
549
|
}
|
|
491
550
|
document.querySelectorAll('[data-ansicht]').forEach(function (k) {
|
|
@@ -494,26 +553,6 @@
|
|
|
494
553
|
|
|
495
554
|
/* ================= ZIELE ================= */
|
|
496
555
|
|
|
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
556
|
/** Eine Zeile im Fremdkasten für einen fremden Eintrag x an einem Ziel z. Mit mitZeichen=true
|
|
518
557
|
steht davor das kleine Harness-Zeichen seines Fundorts (E53 Pkt. 5, „Alle"-Ansicht). Gibt
|
|
519
558
|
neben dem li auch zurück, ob eine Quelle erkannt wurde (für den Erklärtext). */
|
|
@@ -652,18 +691,115 @@
|
|
|
652
691
|
return kz;
|
|
653
692
|
}
|
|
654
693
|
|
|
694
|
+
/** Defekte Links (ohne Quelle) zuerst, sie brauchen Aufmerksamkeit (E45), sonst alphabetisch. */
|
|
695
|
+
function sortiereEintraege(eintraege) {
|
|
696
|
+
return eintraege.slice().sort(function (a, b) {
|
|
697
|
+
var da = a.art === 'link-ohne-quelle', db = b.art === 'link-ohne-quelle';
|
|
698
|
+
if (da !== db) return da ? -1 : 1;
|
|
699
|
+
var na = a.art === 'link' ? a.link.name : a.name;
|
|
700
|
+
var nb = b.art === 'link' ? b.link.name : b.name;
|
|
701
|
+
return na.localeCompare(nb, 'de');
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Eine Zeile der Link-Tabelle. `bundle` gesetzt heißt: die Zeile steht in einer Bundle-Gruppe
|
|
706
|
+
(E69), das Entfernen zerbricht dort das Bundle und der Dialog sagt es. */
|
|
707
|
+
function linkZeile(ei, z, bundle) {
|
|
708
|
+
var name = ei.art === 'link' ? ei.link.name : ei.name;
|
|
709
|
+
var li = e('li');
|
|
710
|
+
|
|
711
|
+
if (ei.art === 'link') li.appendChild(repoIcon(ei.link.checkout.quelle));
|
|
712
|
+
else li.appendChild(repoIcon({ art: 'lokal', pfad: ei.checkoutPfad }));
|
|
713
|
+
|
|
714
|
+
li.appendChild(e('span', 'name', name));
|
|
715
|
+
|
|
716
|
+
// Die Beschreibung aus der SKILL.md (E39): die Frage, wegen der die Zeile gelesen wird,
|
|
717
|
+
// deshalb die breiteste Spalte. Ein Link ohne bekannte Quelle kennt seinen Skill nicht.
|
|
718
|
+
li.appendChild(wasZelle(ei.art === 'link' ? ei.link.skill : null));
|
|
719
|
+
|
|
720
|
+
// Quelle: der Name, anklickbar, dahinter nur, was vom Normalfall abweicht — ein Ref, das
|
|
721
|
+
// vom Standard-Ref der Quelle abweicht, und der Grund „über Zeiger" (E57). „Manuell" (der
|
|
722
|
+
// Normalfall) entfällt ersatzlos, ein Plugin-Grund steht stattdessen in der Aktionsspalte.
|
|
723
|
+
var qz = e('span', 'quelle');
|
|
724
|
+
if (ei.art === 'link') {
|
|
725
|
+
var einschraenkung = z.einschraenkungen[name];
|
|
726
|
+
if (einschraenkung) qz.appendChild(document.createTextNode(einschraenkung + ' → '));
|
|
727
|
+
qz.appendChild(quelleLinkElement(ei.link.checkout.quelle));
|
|
728
|
+
var standardRef = STANDARD_REF[quelleKurz(ei.link.checkout.quelle)];
|
|
729
|
+
if (ei.link.checkout.ref && standardRef && ei.link.checkout.ref !== standardRef) {
|
|
730
|
+
qz.appendChild(e('span', 'ref', '@' + ei.link.checkout.ref));
|
|
731
|
+
}
|
|
732
|
+
} else {
|
|
733
|
+
qz.appendChild(e('span', 'mono', pfadKurz(ei.checkoutPfad)));
|
|
734
|
+
}
|
|
735
|
+
var grund = z.gruende[name];
|
|
736
|
+
if (grund && grund.art === 'zeiger') qz.appendChild(e('span', 'grundmarke', 'über Zeiger'));
|
|
737
|
+
li.appendChild(qz);
|
|
738
|
+
|
|
739
|
+
// Aus einem Plugin verlinkt: kein Entfernen-Knopf, das geht nur über das Plugin — der
|
|
740
|
+
// Grund erklärt hier zugleich, warum der Knopf fehlt.
|
|
741
|
+
if (grund && grund.art === 'plugin') {
|
|
742
|
+
li.appendChild(e('span', 'kein-weg', 'nur über Plugin „' + grund.plugin.split('@')[0] + '“'));
|
|
743
|
+
} else {
|
|
744
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
745
|
+
weg.type = 'button';
|
|
746
|
+
weg.setAttribute('aria-label', 'Link auf ' + name + ' entfernen');
|
|
747
|
+
weg.addEventListener('click', function () { frageEntfernen(name, z.ziel, bundle ? bundle.name : null); });
|
|
748
|
+
li.appendChild(weg);
|
|
749
|
+
}
|
|
750
|
+
return li;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Ein installiertes Bundle als Gruppe über seinen Skill-Zeilen (E69, F68): Kopf mit Name, Quelle
|
|
754
|
+
und „Entfernen" für das Ganze, darunter die Zeilen seiner Mitglieder. */
|
|
755
|
+
function bundleGruppe(b, z, eintraege) {
|
|
756
|
+
var gruppe = e('div', 'bundlegruppe');
|
|
757
|
+
var kopf = e('div', 'bundlekopf');
|
|
758
|
+
var titel = e('span', 'bundletitel');
|
|
759
|
+
titel.appendChild(e('span', 'etikett', 'Bundle'));
|
|
760
|
+
titel.appendChild(e('b', 'bundlename', b.name));
|
|
761
|
+
kopf.appendChild(titel);
|
|
762
|
+
var quelleEl = e('span', 'mono bundlequelle', b.quelleName);
|
|
763
|
+
quelleEl.title = b.quelleText;
|
|
764
|
+
kopf.appendChild(quelleEl);
|
|
765
|
+
kopf.appendChild(e('span', 'bundlezahl', zahlwort(b.skills.length, 'Skill', 'Skills')));
|
|
766
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
767
|
+
weg.type = 'button';
|
|
768
|
+
weg.setAttribute('aria-label', 'Bundle ' + b.name + ' aus ' + zielTitel(z) + ' entfernen');
|
|
769
|
+
weg.addEventListener('click', function () { frageBundleEntfernen(b, z.ziel); });
|
|
770
|
+
kopf.appendChild(weg);
|
|
771
|
+
gruppe.appendChild(kopf);
|
|
772
|
+
var ul = e('ul', 'linkliste');
|
|
773
|
+
eintraege.forEach(function (ei) { ul.appendChild(linkZeile(ei, z, b)); });
|
|
774
|
+
gruppe.appendChild(ul);
|
|
775
|
+
return gruppe;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** Ein Bundle als einzelner Streifen (Chip „Alle" einer Verzeichnis-Karte, E69/E53): Harness-
|
|
779
|
+
Zeichen, Name, seine Mitglieder als Text, Entfernen. Ohne eigene Tabelle. */
|
|
780
|
+
function bundleStreifen(b, z) {
|
|
781
|
+
var streifen = e('div', 'bundlegruppe');
|
|
782
|
+
var kopf = e('div', 'bundlekopf');
|
|
783
|
+
var titel = e('span', 'bundletitel');
|
|
784
|
+
titel.appendChild(harnessZeichen(z.ziel.harness));
|
|
785
|
+
titel.appendChild(e('span', 'etikett', 'Bundle'));
|
|
786
|
+
titel.appendChild(e('b', 'bundlename', b.name));
|
|
787
|
+
kopf.appendChild(titel);
|
|
788
|
+
kopf.appendChild(e('span', 'bundlemitglieder', b.skills.join(', ')));
|
|
789
|
+
var weg = e('button', 'knopf leise klein', 'Entfernen');
|
|
790
|
+
weg.type = 'button';
|
|
791
|
+
weg.setAttribute('aria-label', 'Bundle ' + b.name + ' aus ' + zielTitel(z) + ' entfernen');
|
|
792
|
+
weg.addEventListener('click', function () { frageBundleEntfernen(b, z.ziel); });
|
|
793
|
+
kopf.appendChild(weg);
|
|
794
|
+
streifen.appendChild(kopf);
|
|
795
|
+
return streifen;
|
|
796
|
+
}
|
|
797
|
+
|
|
655
798
|
function linkTeil(z) {
|
|
656
799
|
var teil = document.createDocumentFragment();
|
|
657
800
|
var eigene = z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; });
|
|
658
801
|
var fremde = z.eintraege.filter(function (ei) { return ei.art === 'fremd'; });
|
|
659
802
|
|
|
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
803
|
var kz = konfigZeile(z);
|
|
668
804
|
if (kz) teil.appendChild(kz);
|
|
669
805
|
|
|
@@ -697,63 +833,26 @@
|
|
|
697
833
|
kopf.appendChild(e('span'));
|
|
698
834
|
teil.appendChild(kopf);
|
|
699
835
|
|
|
700
|
-
|
|
701
|
-
//
|
|
702
|
-
|
|
836
|
+
// Bundles zuerst (E69): sie stehen als Gruppe über den Einzel-Zeilen, ihre Mitglieder liegen
|
|
837
|
+
// in der Gruppe statt in der allgemeinen Liste. Was zum Bundle gehört, sagt der Kern.
|
|
838
|
+
var bundles = (z.bundles || []).filter(function (b) { return b.skills.length > 0; });
|
|
839
|
+
var bundleVon = {};
|
|
840
|
+
bundles.forEach(function (b) { b.skills.forEach(function (n) { bundleVon[n] = b; }); });
|
|
841
|
+
bundles
|
|
703
842
|
.slice()
|
|
704
|
-
.sort(function (a, b) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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);
|
|
843
|
+
.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); })
|
|
844
|
+
.forEach(function (b) {
|
|
845
|
+
var mitglieder = eigene.filter(function (ei) {
|
|
846
|
+
return b.skills.indexOf(ei.art === 'link' ? ei.link.name : ei.name) > -1;
|
|
847
|
+
});
|
|
848
|
+
teil.appendChild(bundleGruppe(b, z, sortiereEintraege(mitglieder)));
|
|
755
849
|
});
|
|
756
|
-
|
|
850
|
+
|
|
851
|
+
var ul = e('ul', 'linkliste');
|
|
852
|
+
sortiereEintraege(
|
|
853
|
+
eigene.filter(function (ei) { return !bundleVon[ei.art === 'link' ? ei.link.name : ei.name]; }),
|
|
854
|
+
).forEach(function (ei) { ul.appendChild(linkZeile(ei, z, null)); });
|
|
855
|
+
if (ul.childNodes.length > 0) teil.appendChild(ul);
|
|
757
856
|
|
|
758
857
|
// MCP-Abschnitt (E61-GUI-Nachtrag): unter der Skill-Link-Tabelle, vor dem Kartenfuß mit den
|
|
759
858
|
// Knöpfen, Gegenrichtung zur MCP-Ansicht.
|
|
@@ -894,6 +993,44 @@
|
|
|
894
993
|
return knopf;
|
|
895
994
|
}
|
|
896
995
|
|
|
996
|
+
/** Eigene Links eines Verzeichnisses über alle Harnesses gebündelt: Schlüssel Name+Quelle,
|
|
997
|
+
hängt ein Name je Harness aus verschiedener Quelle, gibt es je Quelle eine eigene Zeile
|
|
998
|
+
(E53 Pkt. 2). Einzige Zählregel für Skills: der Körper zeilt daraus, die Kartenköpfe zählen
|
|
999
|
+
dieselbe Struktur (E71). Ein einzelnes Ziel kommt als einelementige Liste.
|
|
1000
|
+
Fremde Einträge bleiben draußen, defekte Links (ohne Quelle) zählen mit. */
|
|
1001
|
+
function buendleEigene(teile) {
|
|
1002
|
+
var zeilen = {};
|
|
1003
|
+
var reihenfolge = [];
|
|
1004
|
+
teile.forEach(function (z) {
|
|
1005
|
+
z.eintraege.filter(function (ei) { return ei.art !== 'fremd'; }).forEach(function (ei) {
|
|
1006
|
+
var defekt = ei.art === 'link-ohne-quelle';
|
|
1007
|
+
var name = defekt ? ei.name : ei.link.name;
|
|
1008
|
+
var quelleSchluessel = defekt ? ('defekt\u0000' + ei.checkoutPfad)
|
|
1009
|
+
: (quelleKurz(ei.link.checkout.quelle) + '@' + ei.link.checkout.ref);
|
|
1010
|
+
// NUL als Trenner, kein Leerzeichen: Skill-Namen dürfen Leerzeichen enthalten, Quellen-Namen
|
|
1011
|
+
// seit E59 auch, ein lokaler Pfad ohnehin — „a b“ + „x“ und „a“ + „b x“ ergäben sonst
|
|
1012
|
+
// denselben Schlüssel. Immer als Escape geschrieben, nie als rohes Byte im Quelltext.
|
|
1013
|
+
var key = name + '\u0000' + quelleSchluessel;
|
|
1014
|
+
if (!zeilen[key]) {
|
|
1015
|
+
zeilen[key] = {
|
|
1016
|
+
name: name,
|
|
1017
|
+
defekt: defekt,
|
|
1018
|
+
icon: defekt ? { art: 'lokal', pfad: ei.checkoutPfad } : ei.link.checkout.quelle,
|
|
1019
|
+
checkoutPfad: defekt ? ei.checkoutPfad : null,
|
|
1020
|
+
quelle: defekt ? null : ei.link.checkout.quelle,
|
|
1021
|
+
ref: defekt ? null : ei.link.checkout.ref,
|
|
1022
|
+
einschraenkung: defekt ? null : z.einschraenkungen[name],
|
|
1023
|
+
skill: defekt ? null : ei.link.skill,
|
|
1024
|
+
teile: [],
|
|
1025
|
+
};
|
|
1026
|
+
reihenfolge.push(key);
|
|
1027
|
+
}
|
|
1028
|
+
zeilen[key].teile.push(z);
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
return { zeilen: zeilen, reihenfolge: reihenfolge };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
897
1034
|
function zielKarte(z) {
|
|
898
1035
|
var karte = e('div', 'ziel');
|
|
899
1036
|
var kopf = e('div', 'zielkopf');
|
|
@@ -908,17 +1045,12 @@
|
|
|
908
1045
|
kopf.setAttribute('aria-expanded', offen ? 'true' : 'false');
|
|
909
1046
|
kopf.appendChild(beschriftet(e('h3', null, zielTitel(z)), z.ziel.harness));
|
|
910
1047
|
kopf.appendChild(e('span', 'mono', pfadKurz(z.verzeichnis)));
|
|
911
|
-
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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')));
|
|
1048
|
+
var ausloeser = zielAusloeser(z);
|
|
1049
|
+
if (ausloeser > 0) kopf.appendChild(warnzeichen(ausloeser));
|
|
1050
|
+
// Der Kopf zählt Skills, nicht Einträge (E71), über dieselbe Bündelung, aus der der Körper zeilt.
|
|
1051
|
+
// Bei einem Harness fällt die Zahl mit der Zahl eigener Einträge zusammen, die Regel bleibt eine.
|
|
1052
|
+
var zahl = buendleEigene([z]).reihenfolge.length;
|
|
1053
|
+
kopf.appendChild(e('span', 'zahl', zahl + (zahl === 1 ? ' Skill' : ' Skills')));
|
|
922
1054
|
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
923
1055
|
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
924
1056
|
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
@@ -958,17 +1090,10 @@
|
|
|
958
1090
|
|
|
959
1091
|
/** Körper des Chips „Alle" einer Verzeichnis-Karte (E53 Pkt. 2, 4, 5): eine gemeinsame Liste
|
|
960
1092
|
statt gestapelter Harness-Abschnitte. Eigene Links gebündelt (Schlüssel Name+Quelle), fremde
|
|
961
|
-
Einträge
|
|
1093
|
+
Einträge je Harness mit dessen Zeichen am Fundort. */
|
|
962
1094
|
function alleTeil(pfad, teile) {
|
|
963
1095
|
var teil = document.createDocumentFragment();
|
|
964
1096
|
|
|
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
1097
|
var beachteteAlle = [], ignorierteAlle = [];
|
|
973
1098
|
teile.forEach(function (z) {
|
|
974
1099
|
var ignoriertNamen = z.ignoriert || [];
|
|
@@ -981,37 +1106,9 @@
|
|
|
981
1106
|
if (beachteteAlle.length > 0) teil.appendChild(fremdKasten(beachteteAlle, true));
|
|
982
1107
|
if (ignorierteAlle.length > 0) teil.appendChild(ignoriertZeile(ignorierteAlle, true));
|
|
983
1108
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
var
|
|
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
|
-
});
|
|
1109
|
+
var gebuendelt = buendleEigene(teile);
|
|
1110
|
+
var zeilen = gebuendelt.zeilen;
|
|
1111
|
+
var reihenfolge = gebuendelt.reihenfolge;
|
|
1015
1112
|
|
|
1016
1113
|
// MCPs hängen am Verzeichnis, nicht am Harness-Chip (E61-GUI-Nachtrag): der Abschnitt unter
|
|
1017
1114
|
// „Alle" zeigt jedes Ziel, das an genau diesem Pfad MCP kann — dieselbe Fähigkeitsfrage wie unter
|
|
@@ -1038,6 +1135,18 @@
|
|
|
1038
1135
|
return teil;
|
|
1039
1136
|
}
|
|
1040
1137
|
|
|
1138
|
+
// Bundles unter „Alle" (E69): die Zeilen bleiben nach Name und Quelle gebündelt (E53), ein
|
|
1139
|
+
// Bundle gehört aber zu genau einem Harness — deshalb hier ein Streifen je Bundle mit seinem
|
|
1140
|
+
// Harness-Zeichen statt einer Umgruppierung der Tabelle. Unter dem Harness-Chip zeigt linkTeil
|
|
1141
|
+
// die volle Gruppe über den Zeilen.
|
|
1142
|
+
teile.forEach(function (z) {
|
|
1143
|
+
(z.bundles || [])
|
|
1144
|
+
.filter(function (b) { return b.skills.length > 0; })
|
|
1145
|
+
.slice()
|
|
1146
|
+
.sort(function (a, b) { return a.name.localeCompare(b.name, 'de'); })
|
|
1147
|
+
.forEach(function (b) { teil.appendChild(bundleStreifen(b, z)); });
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1041
1150
|
var kopf = e('div', 'linkkopf');
|
|
1042
1151
|
kopf.appendChild(e('span'));
|
|
1043
1152
|
kopf.appendChild(e('span', 'etikett', 'Skill'));
|
|
@@ -1167,16 +1276,14 @@
|
|
|
1167
1276
|
.forEach(function (z) { symbole.appendChild(harnessZeichen(z.ziel.harness)); });
|
|
1168
1277
|
kopf.appendChild(symbole);
|
|
1169
1278
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
var gesamtZahl = teile.
|
|
1177
|
-
|
|
1178
|
-
}, 0);
|
|
1179
|
-
kopf.appendChild(e('span', 'zahl', gesamtZahl + (gesamtZahl === 1 ? ' Link' : ' Links')));
|
|
1279
|
+
// An der Verzeichnis-Karte ist die Warnzahl die Summe über die Teile (E75): der Nutzer sieht
|
|
1280
|
+
// hier einen Ordner, nicht die Harness-Abschnitte darunter.
|
|
1281
|
+
var ausloeserGesamt = teile.reduce(function (summe, z) { return summe + zielAusloeser(z); }, 0);
|
|
1282
|
+
if (ausloeserGesamt > 0) kopf.appendChild(warnzeichen(ausloeserGesamt));
|
|
1283
|
+
// Skills statt Einträge (E71): eine Summe über die Harness-Abschnitte zählte jeden Skill so oft,
|
|
1284
|
+
// wie ihn Harnesses am selben Verzeichnis führen — der Körper bündelt sie zu einer Zeile.
|
|
1285
|
+
var gesamtZahl = buendleEigene(teile).reihenfolge.length;
|
|
1286
|
+
kopf.appendChild(e('span', 'zahl', gesamtZahl + (gesamtZahl === 1 ? ' Skill' : ' Skills')));
|
|
1180
1287
|
var pfeil = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1181
1288
|
pfeil.setAttribute('viewBox', '0 0 16 16');
|
|
1182
1289
|
pfeil.setAttribute('class', 'akkordeonpfeil');
|
|
@@ -1223,7 +1330,8 @@
|
|
|
1223
1330
|
var gewaehlt = teile.filter(function (z) { return z.ziel.harness === aktivChip; })[0];
|
|
1224
1331
|
// Welcher Ordner im Verzeichnis gelesen wird, stand vorher im Abschnittskopf (25cddf6) und
|
|
1225
1332
|
// gehört zum gewählten Harness, nicht zur Karte: er steht rechts in der Chip-Leiste, relativ
|
|
1226
|
-
// zum Verzeichnis,
|
|
1333
|
+
// zum Verzeichnis. Chips gibt es nur für aktive Harnesses (E74), eine Inaktiv-Marke braucht
|
|
1334
|
+
// die Leiste darum nicht mehr.
|
|
1227
1335
|
if (gewaehlt) {
|
|
1228
1336
|
// Nur die Anzeige: liegt der gelesene Ordner unterhalb des Verzeichnisses, steht er
|
|
1229
1337
|
// relativ, sonst gekürzt. Beide Trenner, sonst greift das unter Windows nie (E68).
|
|
@@ -1231,7 +1339,6 @@
|
|
|
1231
1339
|
|| gewaehlt.verzeichnis.indexOf(pfad + '\\') === 0;
|
|
1232
1340
|
chipleiste.appendChild(e('span', 'kartenordner mono',
|
|
1233
1341
|
unterhalb ? gewaehlt.verzeichnis.slice(pfad.length + 1) : pfadKurz(gewaehlt.verzeichnis)));
|
|
1234
|
-
if (!harnessAktiv(aktivChip)) chipleiste.appendChild(e('span', 'ruhend', 'Harness inaktiv'));
|
|
1235
1342
|
}
|
|
1236
1343
|
koerper.appendChild(chipleiste);
|
|
1237
1344
|
if (gewaehlt) koerper.appendChild(linkTeil(gewaehlt));
|
|
@@ -1326,10 +1433,24 @@
|
|
|
1326
1433
|
// entfernenKontext.eintraege: [{harness, ort}], immer eines bei frageEntfernen (Einzel-Ziel),
|
|
1327
1434
|
// mehrere bei frageEntfernenAlle (E53 Pkt. 2, „Alle"-Zeile über mehrere Harnesses).
|
|
1328
1435
|
var entfernenKontext = null;
|
|
1329
|
-
|
|
1330
|
-
|
|
1436
|
+
/** `bundleName` gesetzt heißt: der Link gehört zu einem Bundle, das Entfernen zerbricht es (E69). */
|
|
1437
|
+
function frageEntfernen(name, ziel, bundleName) {
|
|
1438
|
+
entfernenKontext = { art: 'link', name: name, eintraege: [{ harness: ziel.harness, ort: ziel.ort }] };
|
|
1331
1439
|
$('dx-titel').textContent = 'Link auf ' + name + ' entfernen?';
|
|
1332
|
-
|
|
1440
|
+
var text = 'Der Link in ' + zielTitel({ ziel: ziel }) + ' verschwindet. Der Checkout bleibt liegen, bis kein Link mehr darauf zeigt.';
|
|
1441
|
+
if (bundleName) {
|
|
1442
|
+
text += ' Damit zerbricht das Bundle „' + bundleName + '“ hier: seine übrigen Links bleiben liegen, aber nicht mehr als Bundle.';
|
|
1443
|
+
}
|
|
1444
|
+
$('dx-text').textContent = text;
|
|
1445
|
+
zeige($('d-entfernen'));
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
/** Entfernen des ganzen Bundles an einem Ziel (E69): alle seine Links auf einmal. */
|
|
1449
|
+
function frageBundleEntfernen(b, ziel) {
|
|
1450
|
+
entfernenKontext = { art: 'bundle', bundle: b.name, ziel: ziel };
|
|
1451
|
+
$('dx-titel').textContent = 'Bundle ' + b.name + ' entfernen?';
|
|
1452
|
+
$('dx-text').textContent = zahlwort(b.skills.length, 'Link', 'Links') + ' aus ' + zielTitel({ ziel: ziel })
|
|
1453
|
+
+ ' verschwinden: ' + b.skills.join(', ') + '. Die Checkouts bleiben liegen, bis kein Link mehr darauf zeigt.';
|
|
1333
1454
|
zeige($('d-entfernen'));
|
|
1334
1455
|
}
|
|
1335
1456
|
/** Entfernen einer „Alle"-Zeile (E53 Pkt. 2): entfernbareZiele sind die Ziele (voller z.ziel), in
|
|
@@ -1337,6 +1458,7 @@
|
|
|
1337
1458
|
und liegen bleibt. Der Dialog nennt beides. */
|
|
1338
1459
|
function frageEntfernenAlle(name, pfad, entfernbareZiele, nichtEntfernbareHarnesse) {
|
|
1339
1460
|
entfernenKontext = {
|
|
1461
|
+
art: 'link',
|
|
1340
1462
|
name: name,
|
|
1341
1463
|
eintraege: entfernbareZiele.map(function (z) { return { harness: z.ziel.harness, ort: z.ziel.ort }; }),
|
|
1342
1464
|
};
|
|
@@ -1353,6 +1475,19 @@
|
|
|
1353
1475
|
nachDialog($('d-entfernen'), async function (wert) {
|
|
1354
1476
|
if (wert !== 'ja' || entfernenKontext === null) return;
|
|
1355
1477
|
var kontext = entfernenKontext;
|
|
1478
|
+
// Bundle als Ganzes (E69): ein Aufruf, der Kern nimmt alle Links des Bundles weg.
|
|
1479
|
+
if (kontext.art === 'bundle') {
|
|
1480
|
+
var wegB = await rufe(function () {
|
|
1481
|
+
return window.hmgr.bundleEntfernen({
|
|
1482
|
+
bundle: kontext.bundle,
|
|
1483
|
+
harness: kontext.ziel.harness,
|
|
1484
|
+
ort: kontext.ziel.ort,
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
await ladeAlles();
|
|
1488
|
+
melde('Bundle ' + kontext.bundle + ' entfernt, ' + zahlwort(wegB.entfernt.length, 'Link', 'Links') + ' weniger.');
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1356
1491
|
// Ein Fehlschlag in einem Harness hält die übrigen nicht auf: rufe hat ihn schon gemeldet, die
|
|
1357
1492
|
// Schleife läuft weiter, und die Liste wird danach in jedem Fall neu geladen. Sonst stünde nach
|
|
1358
1493
|
// einem Fehler die halb erledigte Arbeit unsichtbar in einer veralteten Liste.
|
|
@@ -1663,12 +1798,52 @@
|
|
|
1663
1798
|
return zeilen;
|
|
1664
1799
|
}
|
|
1665
1800
|
|
|
1801
|
+
/** ISO-Zeitpunkt als deutsches Datum (E70). Ungültiges oder fehlendes Datum ergibt null. */
|
|
1802
|
+
function datumDeutsch(iso) {
|
|
1803
|
+
if (!iso) return null;
|
|
1804
|
+
var d = new Date(iso);
|
|
1805
|
+
if (isNaN(d.getTime())) return null;
|
|
1806
|
+
return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/** Reihenfolge der Skills-Indizes für die Katalog-Ansicht (E79, Richtung E81). SKILLS selbst
|
|
1810
|
+
bleibt unverändert (Name, dann Quelle sortiert, siehe ladeAlles), damit li.dataset.index
|
|
1811
|
+
stabil bleibt. Bei 'name'/'ab' einfach umgekehrt. Bei 'datum' stehen je nach Richtung die
|
|
1812
|
+
jüngsten oder die ältesten zuerst, Skills ohne Datum in BEIDEN Richtungen ganz hinten, bei
|
|
1813
|
+
Gleichstand (Datum oder „ohne Datum") in beiden Richtungen wie bisher nach Name/Quelle (also
|
|
1814
|
+
die ursprüngliche Reihenfolge). */
|
|
1815
|
+
function skillReihenfolge() {
|
|
1816
|
+
var indizes = SKILLS.map(function (_, i) { return i; });
|
|
1817
|
+
if (skillSortierung === 'name') {
|
|
1818
|
+
if (skillRichtung === 'ab') indizes.reverse();
|
|
1819
|
+
return indizes;
|
|
1820
|
+
}
|
|
1821
|
+
indizes.sort(function (a, b) {
|
|
1822
|
+
// Numerisch vergleichen, nicht als String: geaendert ist ISO-8601 mit dem Zeitzonen-Offset
|
|
1823
|
+
// des Committers (git %cI), zwei verschiedene Offsets ordnet ein String-Vergleich falsch.
|
|
1824
|
+
// Nicht parsebar zählt wie „ohne Datum" (hinten, in beiden Richtungen).
|
|
1825
|
+
var ta = SKILLS[a].geaendert ? Date.parse(SKILLS[a].geaendert) : NaN;
|
|
1826
|
+
var tb = SKILLS[b].geaendert ? Date.parse(SKILLS[b].geaendert) : NaN;
|
|
1827
|
+
var oa = isNaN(ta);
|
|
1828
|
+
var ob = isNaN(tb);
|
|
1829
|
+
if (oa && ob) return a - b;
|
|
1830
|
+
if (oa) return 1;
|
|
1831
|
+
if (ob) return -1;
|
|
1832
|
+
if (ta === tb) return a - b;
|
|
1833
|
+
if (skillRichtung === 'auf') return ta < tb ? -1 : 1;
|
|
1834
|
+
return ta < tb ? 1 : -1;
|
|
1835
|
+
});
|
|
1836
|
+
return indizes;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1666
1839
|
function baueSkills() {
|
|
1667
1840
|
var liste = $('katalog');
|
|
1668
1841
|
liste.textContent = '';
|
|
1669
|
-
|
|
1842
|
+
skillReihenfolge().forEach(function (i) {
|
|
1843
|
+
var skill = SKILLS[i];
|
|
1670
1844
|
var li = e('li', 'zeile');
|
|
1671
1845
|
li.dataset.index = i;
|
|
1846
|
+
if (skill.geaendert) li.dataset.geaendert = skill.geaendert;
|
|
1672
1847
|
var idx = quellenIndex(skill.quelleText);
|
|
1673
1848
|
li.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
1674
1849
|
|
|
@@ -1686,12 +1861,17 @@
|
|
|
1686
1861
|
// title. Fehlt die Quelle in der Liste (Katalog und Quellen kämen auseinander), bleibt es
|
|
1687
1862
|
// beim reinen Text — angeschrieben ist die Zeile so oder so.
|
|
1688
1863
|
var herkunft = e('p', 'herkunft mono');
|
|
1864
|
+
// Das Änderungsdatum steht hinter dem Ref (E70, F70=O1): der jüngste Commit, der den
|
|
1865
|
+
// Skill-Ordner berührt. Nicht der Pull-Zeitpunkt — der steht als „Zuletzt geprüft" in der
|
|
1866
|
+
// Update-Ansicht und wäre für alle Skills einer Quelle derselbe.
|
|
1867
|
+
var geaendert = datumDeutsch(skill.geaendert);
|
|
1868
|
+
var nachRef = ' · Ref ' + skill.ref + (geaendert ? ' · geändert ' + geaendert : '');
|
|
1689
1869
|
if (herkunftQuelle) {
|
|
1690
1870
|
herkunft.appendChild(quelleLinkElement(herkunftQuelle));
|
|
1691
|
-
herkunft.appendChild(e('b', null,
|
|
1871
|
+
herkunft.appendChild(e('b', null, nachRef));
|
|
1692
1872
|
} else {
|
|
1693
1873
|
herkunft.title = skill.quelleText;
|
|
1694
|
-
herkunft.appendChild(e('b', null, skill.quelleName +
|
|
1874
|
+
herkunft.appendChild(e('b', null, skill.quelleName + nachRef));
|
|
1695
1875
|
}
|
|
1696
1876
|
links.appendChild(herkunft);
|
|
1697
1877
|
|
|
@@ -1762,6 +1942,32 @@
|
|
|
1762
1942
|
}
|
|
1763
1943
|
$('suchfeld').addEventListener('input', filtereSkills);
|
|
1764
1944
|
|
|
1945
|
+
/** Sortierknöpfe der Skills-Rubrik (E79, Richtung E81): statisches Markup, deshalb Listener nur
|
|
1946
|
+
einmal gebunden, nicht in baueSkills. Klick auf den bereits aktiven Chip kehrt nur die
|
|
1947
|
+
Richtung um; Klick auf den anderen Chip wechselt den Schlüssel und setzt dessen
|
|
1948
|
+
Standardrichtung. Nur der aktive Chip trägt den Pfeil (↑/↓) und data-richtung, das liest der
|
|
1949
|
+
Smoke. */
|
|
1950
|
+
function setzeSkillSortierung(wert) {
|
|
1951
|
+
if (wert === skillSortierung) {
|
|
1952
|
+
skillRichtung = skillRichtung === 'auf' ? 'ab' : 'auf';
|
|
1953
|
+
} else {
|
|
1954
|
+
skillSortierung = wert;
|
|
1955
|
+
skillRichtung = SKILL_RICHTUNG_STANDARD[wert];
|
|
1956
|
+
}
|
|
1957
|
+
var pfeil = skillRichtung === 'auf' ? '↑' : '↓';
|
|
1958
|
+
[['sortnach-name', 'name'], ['sortnach-datum', 'datum']].forEach(function (paar) {
|
|
1959
|
+
var chip = $(paar[0]);
|
|
1960
|
+
var aktiv = skillSortierung === paar[1];
|
|
1961
|
+
chip.setAttribute('aria-pressed', aktiv ? 'true' : 'false');
|
|
1962
|
+
chip.querySelector('.sortpfeil').textContent = aktiv ? pfeil : '';
|
|
1963
|
+
if (aktiv) chip.dataset.richtung = skillRichtung;
|
|
1964
|
+
else delete chip.dataset.richtung;
|
|
1965
|
+
});
|
|
1966
|
+
baueSkills();
|
|
1967
|
+
}
|
|
1968
|
+
$('sortnach-name').addEventListener('click', function () { setzeSkillSortierung('name'); });
|
|
1969
|
+
$('sortnach-datum').addEventListener('click', function () { setzeSkillSortierung('datum'); });
|
|
1970
|
+
|
|
1765
1971
|
var laufendeSkill = null;
|
|
1766
1972
|
function starteVerlinken(index) {
|
|
1767
1973
|
laufendeSkill = index;
|
|
@@ -1769,7 +1975,14 @@
|
|
|
1769
1975
|
$('dz-skill').textContent = skill.name;
|
|
1770
1976
|
var box = $('dz-ziele');
|
|
1771
1977
|
box.textContent = '';
|
|
1772
|
-
|
|
1978
|
+
// E76: Ziele inaktiver Harnesses stehen gar nicht erst zur Wahl — der Kern lehnte den Link
|
|
1979
|
+
// ohnehin ab, und seit E74 zeigt keine Ansicht sie. Dieselbe Regel wie die Zielliste.
|
|
1980
|
+
var waehlbar = ZIELE.filter(zielSichtbar);
|
|
1981
|
+
if (waehlbar.length === 0) {
|
|
1982
|
+
box.appendChild(e('p', 'leerhinweis',
|
|
1983
|
+
'Kein Ziel wählbar: alle Harnesses sind deaktiviert. Unter Einstellungen einen aktivieren.'));
|
|
1984
|
+
}
|
|
1985
|
+
waehlbar.forEach(function (z) {
|
|
1773
1986
|
var schon = z.eintraege.some(function (ei) {
|
|
1774
1987
|
var n = ei.art === 'link' ? ei.link.name : ei.name;
|
|
1775
1988
|
return n === skill.name;
|
|
@@ -1840,7 +2053,8 @@
|
|
|
1840
2053
|
Skill nur aus, wenn er in ALLEN beteiligten Harnesses hängt, und verlinkt je Harness, wo er fehlt. */
|
|
1841
2054
|
var skillsKontext = null;
|
|
1842
2055
|
function dskBilanzAktualisieren() {
|
|
1843
|
-
var n = $('dsk-liste').querySelectorAll('input:checked').length
|
|
2056
|
+
var n = $('dsk-liste').querySelectorAll('input:checked').length
|
|
2057
|
+
+ $('dsk-bundles').querySelectorAll('input:checked').length;
|
|
1844
2058
|
$('dsk-bilanz').textContent = zahlwort(n, 'ausgewählt', 'ausgewählt');
|
|
1845
2059
|
}
|
|
1846
2060
|
function filtereDskListe() {
|
|
@@ -1849,6 +2063,60 @@
|
|
|
1849
2063
|
var skill = SKILLS[+label.dataset.index];
|
|
1850
2064
|
label.hidden = !!suche && skill.name.toLowerCase().indexOf(suche) === -1;
|
|
1851
2065
|
});
|
|
2066
|
+
// Die Suche gilt auch für Bundles (E69): sonst bliebe die Sektion bei jeder Eingabe voll stehen.
|
|
2067
|
+
document.querySelectorAll('#dsk-bundles .wahlzeile').forEach(function (label) {
|
|
2068
|
+
var bundle = BUNDLES[+label.dataset.index];
|
|
2069
|
+
label.hidden = !!suche && bundle.name.toLowerCase().indexOf(suche) === -1;
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* Bundle-Sektion des Dialogs (E69, F68): je Bundle eine Wahlzeile mit Beschreibung, Zahl seiner
|
|
2075
|
+
* Mitglieder und Herkunft. `installiertIn(bundle)` sagt, in welchen der angesteuerten Ziele es
|
|
2076
|
+
* schon hängt — hängt es überall, ist die Zeile ausgegraut, wie bei einem Skill.
|
|
2077
|
+
*/
|
|
2078
|
+
function fuelleBundleSektion(installiertIn, zieleZahl) {
|
|
2079
|
+
var box = $('dsk-bundles');
|
|
2080
|
+
box.textContent = '';
|
|
2081
|
+
BUNDLES.forEach(function (bundle, i) {
|
|
2082
|
+
var schon = installiertIn(bundle);
|
|
2083
|
+
var ueberall = schon >= zieleZahl;
|
|
2084
|
+
var label = e('label', 'wahlzeile quellfarbe');
|
|
2085
|
+
label.dataset.index = i;
|
|
2086
|
+
var idx = quellenIndex(bundle.quelleText);
|
|
2087
|
+
label.style.borderLeftColor = 'var(--q' + (idx + 1) + ')';
|
|
2088
|
+
var input = document.createElement('input');
|
|
2089
|
+
input.type = 'checkbox';
|
|
2090
|
+
input.value = String(i);
|
|
2091
|
+
input.disabled = ueberall;
|
|
2092
|
+
input.addEventListener('change', dskBilanzAktualisieren);
|
|
2093
|
+
label.appendChild(input);
|
|
2094
|
+
var txt = e('span');
|
|
2095
|
+
txt.appendChild(e('b', null, bundle.name));
|
|
2096
|
+
txt.appendChild(e('span', 'notiz', bundle.beschreibung));
|
|
2097
|
+
var herkunft = e('span', 'mono', zahlwort(bundle.mitglieder, 'Skill', 'Skills')
|
|
2098
|
+
+ ' aus ' + zahlwort(bundle.quellen.length, 'Quelle', 'Quellen') + ' · ' + bundle.quelleName);
|
|
2099
|
+
herkunft.title = bundle.quellen.join('\n');
|
|
2100
|
+
txt.appendChild(herkunft);
|
|
2101
|
+
if (ueberall) txt.appendChild(e('span', 'notiz', zieleZahl > 1 ? 'Hängt schon überall.' : 'Hängt hier schon.'));
|
|
2102
|
+
else if (schon > 0) txt.appendChild(e('span', 'notiz', 'Hängt schon in ' + schon + ' von ' + zieleZahl + ' Harnesses.'));
|
|
2103
|
+
label.appendChild(txt);
|
|
2104
|
+
box.appendChild(label);
|
|
2105
|
+
});
|
|
2106
|
+
$('dsk-bundlebox').hidden = BUNDLES.length === 0 && BUNDLES_KAPUTT.length === 0;
|
|
2107
|
+
// Eine Definition, die sich nicht lesen ließ, wird genannt statt verschluckt (E69).
|
|
2108
|
+
var fehlerZeile = $('dsk-bundlefehler');
|
|
2109
|
+
fehlerZeile.hidden = BUNDLES_KAPUTT.length === 0;
|
|
2110
|
+
fehlerZeile.textContent = BUNDLES_KAPUTT.map(function (k) {
|
|
2111
|
+
return k.quelleName + ': ' + k.datei + ' — ' + k.fehler;
|
|
2112
|
+
}).join('\n');
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/** Wie oft ein Bundle in den angesteuerten Zielen schon installiert ist (Name plus Quelle, E69). */
|
|
2116
|
+
function bundleHaengtIn(teile, bundle) {
|
|
2117
|
+
return teile.filter(function (z) {
|
|
2118
|
+
return (z.bundles || []).some(function (b) { return b.name === bundle.name && b.quelleText === bundle.quelleText; });
|
|
2119
|
+
}).length;
|
|
1852
2120
|
}
|
|
1853
2121
|
$('dsk-suche').addEventListener('input', filtereDskListe);
|
|
1854
2122
|
|
|
@@ -1883,6 +2151,7 @@
|
|
|
1883
2151
|
label.appendChild(txt);
|
|
1884
2152
|
box.appendChild(label);
|
|
1885
2153
|
});
|
|
2154
|
+
fuelleBundleSektion(function (bundle) { return bundleHaengtIn([z], bundle); }, 1);
|
|
1886
2155
|
dskBilanzAktualisieren();
|
|
1887
2156
|
zeige($('d-skills'));
|
|
1888
2157
|
$('dsk-suche').focus();
|
|
@@ -1927,6 +2196,7 @@
|
|
|
1927
2196
|
label.appendChild(txt);
|
|
1928
2197
|
box.appendChild(label);
|
|
1929
2198
|
});
|
|
2199
|
+
fuelleBundleSektion(function (bundle) { return bundleHaengtIn(teile, bundle); }, teile.length);
|
|
1930
2200
|
dskBilanzAktualisieren();
|
|
1931
2201
|
zeige($('d-skills'));
|
|
1932
2202
|
$('dsk-suche').focus();
|
|
@@ -1934,7 +2204,8 @@
|
|
|
1934
2204
|
|
|
1935
2205
|
$('d-skills').addEventListener('submit', function (ev) {
|
|
1936
2206
|
if (ev.submitter && ev.submitter.value === 'los') {
|
|
1937
|
-
var gewaehlt = Array.from($('dsk-liste').querySelectorAll('input:checked'))
|
|
2207
|
+
var gewaehlt = Array.from($('dsk-liste').querySelectorAll('input:checked'))
|
|
2208
|
+
.concat(Array.from($('dsk-bundles').querySelectorAll('input:checked')));
|
|
1938
2209
|
if (gewaehlt.length === 0) ev.preventDefault();
|
|
1939
2210
|
}
|
|
1940
2211
|
});
|
|
@@ -1943,6 +2214,7 @@
|
|
|
1943
2214
|
if (wert !== 'los' || skillsKontext === null) return;
|
|
1944
2215
|
var kontext = skillsKontext;
|
|
1945
2216
|
var indizes = Array.from($('dsk-liste').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
2217
|
+
var bundleIndizes = Array.from($('dsk-bundles').querySelectorAll('input:checked')).map(function (i) { return +i.value; });
|
|
1946
2218
|
|
|
1947
2219
|
if (kontext.modus === 'einzeln') {
|
|
1948
2220
|
var z = kontext.z;
|
|
@@ -1952,6 +2224,26 @@
|
|
|
1952
2224
|
// Der zweite gehört daher als Fehler in die Bilanz, sonst meldet der Dialog Links, die es nicht gibt.
|
|
1953
2225
|
var vergeben = [];
|
|
1954
2226
|
var hinweiseZiel = [];
|
|
2227
|
+
// Bundles zuerst (E69): sie wirken als Ganzes und bringen ihre Mitglieder mit, ein danach
|
|
2228
|
+
// einzeln gewählter Skill gleichen Namens würde den eben gelegten Link stumm ersetzen.
|
|
2229
|
+
for (var bi = 0; bi < bundleIndizes.length; bi++) {
|
|
2230
|
+
var bundle = BUNDLES[bundleIndizes[bi]];
|
|
2231
|
+
try {
|
|
2232
|
+
var erg = await rufe(function () {
|
|
2233
|
+
return window.hmgr.bundleInstallieren({
|
|
2234
|
+
bundle: bundle.name,
|
|
2235
|
+
harness: z.ziel.harness,
|
|
2236
|
+
ort: z.ziel.ort,
|
|
2237
|
+
quelle: bundle.quelleText,
|
|
2238
|
+
});
|
|
2239
|
+
});
|
|
2240
|
+
hinweiseZiel.push(erg.hinweis);
|
|
2241
|
+
erg.gelegt.concat(erg.uebernommen).forEach(function (n) { vergeben.push(n); });
|
|
2242
|
+
ergebnisZeilen.push({ ok: true, bundle: bundle, erg: erg });
|
|
2243
|
+
} catch (fehler) {
|
|
2244
|
+
ergebnisZeilen.push({ ok: false, bundle: bundle, fehler: fehler.message });
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
1955
2247
|
for (var n = 0; n < indizes.length; n++) {
|
|
1956
2248
|
var skill = SKILLS[indizes[n]];
|
|
1957
2249
|
if (vergeben.indexOf(skill.name) > -1) {
|
|
@@ -1977,18 +2269,30 @@
|
|
|
1977
2269
|
// Arbeitsverzeichnis ist das die gebündelte Verzeichnis-Karte, nicht das Ziel allein (E49).
|
|
1978
2270
|
ZIEL_OFFEN[kartenKey(z.ziel)] = true;
|
|
1979
2271
|
await ladeAlles();
|
|
1980
|
-
|
|
2272
|
+
// Ein Bundle zählt als eines, egal wie viele Links es mitbringt: die Zeile darunter nennt sie.
|
|
2273
|
+
var erfolge = ergebnisZeilen.filter(function (r) { return r.ok && !r.bundle; }).length;
|
|
2274
|
+
var erfolgeBundle = ergebnisZeilen.filter(function (r) { return r.ok && r.bundle; }).length;
|
|
1981
2275
|
$('de-titel').textContent = 'Verlinkt';
|
|
1982
|
-
$('de-text').textContent = zahlwort(
|
|
2276
|
+
$('de-text').textContent = (erfolgeBundle > 0 ? zahlwort(erfolgeBundle, 'Bundle', 'Bundles') + ' und ' : '')
|
|
2277
|
+
+ zahlwort(erfolge, 'Skill hängt', 'Skills hängen') + ' jetzt in ' + zielTitel(z) + '.';
|
|
1983
2278
|
setzeErfolgHinweise(hinweiseZiel);
|
|
1984
2279
|
var ul = $('de-liste');
|
|
1985
2280
|
ul.textContent = '';
|
|
1986
2281
|
ergebnisZeilen.forEach(function (r) {
|
|
1987
2282
|
var li = e('li');
|
|
1988
|
-
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? 'Link' : 'Fehler'));
|
|
2283
|
+
li.appendChild(e('span', r.ok ? 'ok' : 'aus', r.ok ? (r.bundle ? 'Bundle' : 'Link') : 'Fehler'));
|
|
1989
2284
|
var s = e('span');
|
|
1990
|
-
s.appendChild(e('b', null, r.skill.name));
|
|
2285
|
+
s.appendChild(e('b', null, r.bundle ? r.bundle.name : r.skill.name));
|
|
1991
2286
|
if (!r.ok) s.appendChild(e('span', 'mono', r.fehler));
|
|
2287
|
+
else if (r.bundle) {
|
|
2288
|
+
var teileText = r.erg.gelegt.concat(r.erg.uebernommen).join(', ');
|
|
2289
|
+
if (r.erg.neueQuellen.length > 0) {
|
|
2290
|
+
// Neue Quellen (F66) gehören genannt: hmgr hat sie beim Installieren selbst angelegt.
|
|
2291
|
+
teileText += ' · neue ' + zahlwort(r.erg.neueQuellen.length, 'Quelle', 'Quellen') + ': '
|
|
2292
|
+
+ r.erg.neueQuellen.join(', ');
|
|
2293
|
+
}
|
|
2294
|
+
s.appendChild(e('span', 'mono', teileText));
|
|
2295
|
+
}
|
|
1992
2296
|
li.appendChild(s);
|
|
1993
2297
|
ul.appendChild(li);
|
|
1994
2298
|
});
|
|
@@ -2007,6 +2311,32 @@
|
|
|
2007
2311
|
// den eben gelegten Link sonst stumm.
|
|
2008
2312
|
var vergebenJeHarness = {};
|
|
2009
2313
|
var hinweiseAlle = [];
|
|
2314
|
+
// Bundles zuerst, je Harness, in dem sie noch nicht hängen (E69): ein Bundle gehört zu genau
|
|
2315
|
+
// einem Ziel, unter „Alle" also einmal je beteiligtem Harness.
|
|
2316
|
+
for (var bj = 0; bj < bundleIndizes.length; bj++) {
|
|
2317
|
+
var bundleAlle = BUNDLES[bundleIndizes[bj]];
|
|
2318
|
+
for (var bt = 0; bt < teile.length; bt++) {
|
|
2319
|
+
var zb = teile[bt];
|
|
2320
|
+
if (bundleHaengtIn([zb], bundleAlle) > 0) continue;
|
|
2321
|
+
try {
|
|
2322
|
+
var ergAlle = await rufe(function () {
|
|
2323
|
+
return window.hmgr.bundleInstallieren({
|
|
2324
|
+
bundle: bundleAlle.name,
|
|
2325
|
+
harness: zb.ziel.harness,
|
|
2326
|
+
ort: zb.ziel.ort,
|
|
2327
|
+
quelle: bundleAlle.quelleText,
|
|
2328
|
+
});
|
|
2329
|
+
});
|
|
2330
|
+
hinweiseAlle.push(ergAlle.hinweis);
|
|
2331
|
+
ergAlle.gelegt.concat(ergAlle.uebernommen).forEach(function (n) {
|
|
2332
|
+
vergebenJeHarness[zb.ziel.harness + '\u0000' + n] = true;
|
|
2333
|
+
});
|
|
2334
|
+
zeilenErgebnis.push({ ok: true, bundle: bundleAlle, harness: zb.ziel.harness, erg: ergAlle });
|
|
2335
|
+
} catch (fehler) {
|
|
2336
|
+
zeilenErgebnis.push({ ok: false, bundle: bundleAlle, harness: zb.ziel.harness, fehler: fehler.message });
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2010
2340
|
for (var m = 0; m < indizes.length; m++) {
|
|
2011
2341
|
var sk = SKILLS[indizes[m]];
|
|
2012
2342
|
for (var t = 0; t < teile.length; t++) {
|
|
@@ -2061,81 +2391,140 @@
|
|
|
2061
2391
|
/* ================= AKTUALISIEREN ================= */
|
|
2062
2392
|
|
|
2063
2393
|
/**
|
|
2064
|
-
* Ein Aktualisieren-Lauf, von Hand über den
|
|
2394
|
+
* Ein Aktualisieren-Lauf, von Hand über den Kringel oder selbsttätig angestoßen (Start, Intervall
|
|
2395
|
+
* — E44) — `ausloeser` sagt, welcher, und geht an den Kern für den Verlauf (E80). Eine Sperre
|
|
2065
2396
|
* über den disabled-Zustand des Knopfs verhindert, dass ein Anstoß während eines laufenden Laufs
|
|
2066
|
-
* etwas tut.
|
|
2397
|
+
* etwas tut. Start und Intervall rufen den Kern ohne Rückfrage, sie räumen also nichts weg.
|
|
2067
2398
|
*/
|
|
2068
|
-
/* Stand-Zeile
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2399
|
+
/* Stand-Zeile, gemeinsam für Seitenleiste (#standtext) und Update-Ansicht (#updatestand) (E80):
|
|
2400
|
+
vor dem ersten Lauf „Noch nicht geprüft", während eines Laufs „Prüft …", danach ruhig „Alles
|
|
2401
|
+
aktuell, HH:MM geprüft", oder bei Fehlschlägen (E51) „Prüfung fehlgeschlagen, HH:MM" (alle
|
|
2402
|
+
Quellen-Abrufe gescheitert) bzw. „Geprüft mit Fehlern, HH:MM" (Mischung). Hat ein Start- oder
|
|
2403
|
+
Intervall-Lauf Neues gebracht und die Ansicht war noch nicht offen, hervorgehoben „N Skills
|
|
2404
|
+
aktualisiert, HH:MM" (Fallback „N Quellen"/„N Bundles mit Neuem, HH:MM") — dieses Signal hat
|
|
2405
|
+
Vorrang, bis die Ansicht öffnet (zeigeAnsicht räumt `standNeu` dann ab) oder ein neuer Lauf es
|
|
2406
|
+
ersetzt. `.neu` am Standknopf bleibt die Hervorhebung, solange der Zustand gilt. */
|
|
2074
2407
|
var standZeit = null;
|
|
2075
2408
|
var standFehlerZustand = 'keine';
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2409
|
+
var standLaeuft = false;
|
|
2410
|
+
var standNeu = null; // { zahlwort: string, anhang: string } | null
|
|
2411
|
+
|
|
2412
|
+
function fuelleStand(el) {
|
|
2413
|
+
el.textContent = '';
|
|
2414
|
+
if (standLaeuft) { el.textContent = 'Prüft …'; return; }
|
|
2415
|
+
if (standNeu !== null) {
|
|
2416
|
+
el.appendChild(e('b', null, standNeu.zahlwort));
|
|
2417
|
+
el.appendChild(document.createTextNode(' ' + standNeu.anhang + ', ' + standZeit));
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
var text = 'Noch nicht geprüft';
|
|
2421
|
+
if (standZeit !== null) {
|
|
2422
|
+
text = standFehlerZustand === 'alle' ? 'Prüfung fehlgeschlagen, ' + standZeit
|
|
2423
|
+
: standFehlerZustand === 'gemischt' ? 'Geprüft mit Fehlern, ' + standZeit
|
|
2424
|
+
: 'Alles aktuell, ' + standZeit + ' geprüft';
|
|
2425
|
+
}
|
|
2426
|
+
el.textContent = text;
|
|
2427
|
+
}
|
|
2428
|
+
function standText() {
|
|
2429
|
+
fuelleStand($('standtext'));
|
|
2430
|
+
fuelleStand($('updatestand'));
|
|
2431
|
+
$('standknopf').classList.toggle('neu', standNeu !== null);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
function standNachLauf(ergebnis, ausloeser, uhrzeit) {
|
|
2086
2435
|
standZeit = uhrzeit;
|
|
2087
2436
|
var quellen = {};
|
|
2088
2437
|
ergebnis.checkouts.forEach(function (c) {
|
|
2089
2438
|
if (c.aktualisierung.ergebnis === 'aktualisiert') quellen[quelleKurz(c.checkout.quelle)] = true;
|
|
2090
2439
|
});
|
|
2091
2440
|
var neue = Object.keys(quellen).length;
|
|
2441
|
+
// Skill-Ebene (E70): Namen über alle Checkouts, doppelte zählen einmal. Damit sagt die
|
|
2442
|
+
// Stand-Zeile „5 Skills aktualisiert" statt nur „1 Quelle mit Neuem".
|
|
2443
|
+
var geaenderteSkills = {};
|
|
2444
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
2445
|
+
(c.geaenderteSkills || []).forEach(function (name) { geaenderteSkills[name] = true; });
|
|
2446
|
+
});
|
|
2447
|
+
var skillZahl = Object.keys(geaenderteSkills).length;
|
|
2448
|
+
// Ein nachgezogenes Bundle ist Neues wie ein neuer Commit (E69, F64=O1): sonst bliebe die
|
|
2449
|
+
// Stand-Zeile still, obwohl sich am Ziel gerade Links geändert haben.
|
|
2450
|
+
var bundlesNeu = (ergebnis.bundles || []).filter(function (b) { return b.ergebnis === 'aktualisiert'; }).length;
|
|
2092
2451
|
var fehlgeschlagen = ergebnis.checkouts.filter(function (c) { return c.aktualisierung.ergebnis === 'fehlgeschlagen'; }).length;
|
|
2093
2452
|
var gesamt = ergebnis.checkouts.length;
|
|
2094
2453
|
standFehlerZustand = fehlgeschlagen === 0 ? 'keine' : fehlgeschlagen === gesamt ? 'alle' : 'gemischt';
|
|
2095
|
-
// Von Hand läuft es in der Update-Ansicht,
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2454
|
+
// Von Hand (Klick) läuft es in der Update-Ansicht, die schon offen ist: nichts anzukündigen.
|
|
2455
|
+
var automatisch = ausloeser !== 'klick';
|
|
2456
|
+
if ((neue === 0 && bundlesNeu === 0) || !automatisch || !$('ansicht-update').hidden) {
|
|
2457
|
+
standNeu = null;
|
|
2458
|
+
} else if (skillZahl > 0) {
|
|
2459
|
+
// Ist die Skill-Zahl bekannt, sagt sie mehr als die Zahl der Quellen (E70). Sonst bleibt es
|
|
2460
|
+
// beim alten Signal: eine Quelle kann Neues haben, ohne dass ein Skill davon betroffen ist.
|
|
2461
|
+
standNeu = { zahlwort: zahlwort(skillZahl, 'Skill', 'Skills'), anhang: 'aktualisiert' };
|
|
2462
|
+
} else {
|
|
2463
|
+
standNeu = {
|
|
2464
|
+
zahlwort: neue > 0 ? zahlwort(neue, 'Quelle', 'Quellen') : zahlwort(bundlesNeu, 'Bundle', 'Bundles'),
|
|
2465
|
+
anhang: 'mit Neuem',
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
standText();
|
|
2103
2469
|
}
|
|
2104
2470
|
|
|
2105
|
-
async function aktualisierenLauf(
|
|
2471
|
+
async function aktualisierenLauf(ausloeser) {
|
|
2106
2472
|
var knopf = $('updatestart');
|
|
2107
2473
|
if (knopf.disabled) return;
|
|
2108
2474
|
knopf.disabled = true;
|
|
2109
|
-
knopf.
|
|
2110
|
-
|
|
2475
|
+
knopf.classList.add('laeuft');
|
|
2476
|
+
standLaeuft = true;
|
|
2477
|
+
standText();
|
|
2478
|
+
var lauf = rufe(function () {
|
|
2479
|
+
return window.hmgr.aktualisieren({ rueckfragen: ausloeser !== 'klick', ausloeser: ausloeser });
|
|
2480
|
+
});
|
|
2481
|
+
// Jeder Lauf prüft auch hmgr selbst (E80): angestoßen, nicht abgewartet — ein Fund meldet sich
|
|
2482
|
+
// später von sich aus in der Seitenleiste (onSelbstUpdate).
|
|
2483
|
+
window.hmgr.selbstUpdatePruefen();
|
|
2111
2484
|
try {
|
|
2112
|
-
var ergebnis = await
|
|
2113
|
-
return automatisch ? window.hmgr.aktualisieren({ rueckfragen: false }) : window.hmgr.aktualisieren();
|
|
2114
|
-
});
|
|
2115
|
-
baueBericht(ergebnis);
|
|
2485
|
+
var ergebnis = await lauf;
|
|
2116
2486
|
var uhrzeit = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
standNachLauf(ergebnis, automatisch, uhrzeit);
|
|
2487
|
+
standNachLauf(ergebnis, ausloeser, uhrzeit);
|
|
2488
|
+
await ladeVerlauf();
|
|
2120
2489
|
await ladeAlles();
|
|
2121
2490
|
} finally {
|
|
2491
|
+
standLaeuft = false;
|
|
2122
2492
|
knopf.disabled = false;
|
|
2123
|
-
knopf.
|
|
2493
|
+
knopf.classList.remove('laeuft');
|
|
2124
2494
|
// Scheiterte der Lauf, bliebe sonst „Prüft …" stehen.
|
|
2125
|
-
if ($('standtext').textContent === 'Prüft …')
|
|
2495
|
+
if ($('standtext').textContent === 'Prüft …') standText();
|
|
2126
2496
|
}
|
|
2127
2497
|
}
|
|
2128
2498
|
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2499
|
+
/* Update-Hinweise über hmgr selbst (E77/E80), unten in der Seitenleiste über dem
|
|
2500
|
+
Einstellungen-Knopf. Zwei getrennte Zeilen, weil beide zugleich zutreffen können: ein
|
|
2501
|
+
geladenes GUI-Paket und eine neuere CLI auf npm sind zwei Pakete und zwei Kanäle (Gitea bzw.
|
|
2502
|
+
npm). Nur die GUI-Zeile trägt seit E80 einen Knopf (Neustart installiert das geladene Paket),
|
|
2503
|
+
die CLI installiert die GUI nicht selbst (F82). */
|
|
2504
|
+
function selbstUpdateZeigen(nachricht) {
|
|
2505
|
+
if (nachricht.art === 'gui') {
|
|
2506
|
+
$('selbstupdate-gui').textContent = 'hmgr ' + nachricht.version + ' bereit — wird beim Beenden installiert';
|
|
2507
|
+
$('selbstupdate-gui').hidden = false;
|
|
2508
|
+
$('selbstupdate-neustart').hidden = false;
|
|
2509
|
+
} else if (nachricht.art === 'cli') {
|
|
2510
|
+
var p = $('selbstupdate-cli');
|
|
2511
|
+
p.textContent = '';
|
|
2512
|
+
p.appendChild(document.createTextNode('hmgr-CLI ' + nachricht.installiert + ' — npm hat ' + nachricht.neu + ': '));
|
|
2513
|
+
p.appendChild(e('code', null, nachricht.befehl));
|
|
2514
|
+
p.hidden = false;
|
|
2515
|
+
} else return;
|
|
2516
|
+
$('selbstupdate').hidden = false;
|
|
2517
|
+
// Die Datei hat gerade ein hmgr-Ereignis bekommen (E80): der Verlauf zeigt den aktuellen Stand.
|
|
2518
|
+
void ladeVerlauf();
|
|
2519
|
+
}
|
|
2520
|
+
window.hmgr.onSelbstUpdate(selbstUpdateZeigen);
|
|
2521
|
+
$('selbstupdate-neustart').addEventListener('click', function () { window.hmgr.neuStarten(); });
|
|
2522
|
+
|
|
2523
|
+
$('updatestart').addEventListener('click', function () { aktualisierenLauf('klick'); });
|
|
2524
|
+
// Der Standknopf öffnet nur noch die Ansicht (E80): kein eigener Lauf mehr an diesem Klick, der
|
|
2525
|
+
// Kringel in der Ansicht startet ihn bei Bedarf.
|
|
2526
|
+
$('standknopf').addEventListener('click', function () { zeigeAnsicht('update'); });
|
|
2527
|
+
window.hmgr.onAktualisierenAnstossen(function (ausloeser) { aktualisierenLauf(ausloeser); });
|
|
2139
2528
|
|
|
2140
2529
|
// Stilles Neuladen bei Änderungen außerhalb der GUI (E62, Fokus-Wechsel und Datei-Watcher): nur
|
|
2141
2530
|
// ladeAlles, keine Meldung, kein Spinner-Zwang. Nachrutsch-sicher: höchstens ein Durchlauf läuft,
|
|
@@ -2166,58 +2555,76 @@
|
|
|
2166
2555
|
}
|
|
2167
2556
|
window.hmgr.onNeuLadenAnstossen(function () { void stillerReload(); });
|
|
2168
2557
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
var
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2558
|
+
/**
|
|
2559
|
+
* Eine Ereigniszeile im Verlauf (E80): Marke + Text, Muster wie die frühere baueBericht-Zeile
|
|
2560
|
+
* (Checkout/Bundle) und die CLI-Ausgabe (verlaufEreignisZeile, src/cli/main.ts).
|
|
2561
|
+
*/
|
|
2562
|
+
function verlaufEreignisZeile(li, ereignis) {
|
|
2563
|
+
if (ereignis.art === 'checkout') {
|
|
2564
|
+
li.appendChild(e('span', 'marke-ok', 'aktualisiert'));
|
|
2565
|
+
var skills = ereignis.skills.length > 0
|
|
2566
|
+
? ' · ' + zahlwort(ereignis.skills.length, 'Skill', 'Skills') + ': ' + ereignis.skills.join(', ')
|
|
2567
|
+
: '';
|
|
2568
|
+
var stand = ' (' + ereignis.von.slice(0, 7) + ' → ' + ereignis.nach.slice(0, 7) + ')';
|
|
2569
|
+
li.appendChild(e('span', 'pfad', ereignis.quelle + ' @ ' + ereignis.ref + skills + stand));
|
|
2570
|
+
} else if (ereignis.art === 'bundle') {
|
|
2571
|
+
li.appendChild(e('span', 'marke-ok', 'Bundle'));
|
|
2572
|
+
var teile = [];
|
|
2573
|
+
if (ereignis.gelegt.length > 0) teile.push('gelegt ' + ereignis.gelegt.join(', '));
|
|
2574
|
+
if (ereignis.entfernt.length > 0) teile.push('entfernt ' + ereignis.entfernt.join(', '));
|
|
2575
|
+
// Ziel als Objekt seit E80-Nachtrag; ein String-Ziel (ältere Einträge) bleibt unverändert.
|
|
2576
|
+
var zielText = typeof ereignis.ziel === 'string'
|
|
2577
|
+
? ereignis.ziel
|
|
2578
|
+
: harnessName(ereignis.ziel.harness) + ' · ' + ortName(ereignis.ziel.ort);
|
|
2579
|
+
li.appendChild(e('span', 'pfad', ereignis.bundle + ' → ' + zielText
|
|
2580
|
+
+ (teile.length > 0 ? ' · ' + teile.join(' · ') : '')));
|
|
2581
|
+
} else if (ereignis.art === 'aufgeraeumt') {
|
|
2582
|
+
li.appendChild(e('span', 'marke-ok', 'aufgeräumt'));
|
|
2583
|
+
li.appendChild(e('span', 'pfad', ereignis.checkout));
|
|
2584
|
+
} else if (ereignis.art === 'fehler') {
|
|
2585
|
+
li.appendChild(e('span', 'marke-aus', 'fehlgeschlagen'));
|
|
2586
|
+
li.appendChild(e('span', 'pfad', ereignis.betrifft + ': ' + ereignis.fehler));
|
|
2587
|
+
} else if (ereignis.art === 'hmgr-installiert') {
|
|
2588
|
+
li.appendChild(e('span', 'marke-ok', 'installiert'));
|
|
2589
|
+
li.appendChild(e('span', 'pfad', (ereignis.paket === 'gui' ? 'hmgr-GUI ' : 'hmgr ') + ereignis.version));
|
|
2590
|
+
} else if (ereignis.art === 'gui-update-geladen') {
|
|
2591
|
+
li.appendChild(e('span', 'marke-ok', 'geladen'));
|
|
2592
|
+
li.appendChild(e('span', 'pfad', 'GUI-Update ' + ereignis.version));
|
|
2593
|
+
} else if (ereignis.art === 'cli-verfuegbar') {
|
|
2594
|
+
li.appendChild(e('span', 'marke-ok', 'verfügbar'));
|
|
2595
|
+
li.appendChild(e('span', 'pfad', 'CLI ' + ereignis.version));
|
|
2205
2596
|
}
|
|
2597
|
+
}
|
|
2206
2598
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2599
|
+
/**
|
|
2600
|
+
* Baut das Changelog in der Update-Ansicht aus ~/.hmgr/verlauf.json (E80): ein Block je Lauf,
|
|
2601
|
+
* neueste zuerst, das Etikett der lokale Zeitpunkt, darunter eine Zeile je Ereignis. Die Datei
|
|
2602
|
+
* ist die Wahrheit, kein eigener Zustand im Renderer — jeder Aufruf holt sie neu.
|
|
2603
|
+
*/
|
|
2604
|
+
async function ladeVerlauf() {
|
|
2605
|
+
var eintraege = await rufe(function () { return window.hmgr.verlauf(); });
|
|
2606
|
+
var box = $('verlauf');
|
|
2607
|
+
box.textContent = '';
|
|
2608
|
+
if (eintraege.length === 0) {
|
|
2609
|
+
box.appendChild(e('p', 'leerhinweis', 'Noch keine Ereignisse.'));
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
eintraege.forEach(function (eintrag) {
|
|
2613
|
+
var block = e('div', 'block');
|
|
2614
|
+
var zeit = new Date(eintrag.zeit);
|
|
2615
|
+
var zwei = function (n) { return String(n).padStart(2, '0'); };
|
|
2616
|
+
var etikett = zwei(zeit.getDate()) + '.' + zwei(zeit.getMonth() + 1) + '.' + zeit.getFullYear()
|
|
2617
|
+
+ ' ' + zwei(zeit.getHours()) + ':' + zwei(zeit.getMinutes());
|
|
2618
|
+
block.appendChild(e('span', 'etikett', etikett));
|
|
2619
|
+
var ul = e('ul');
|
|
2620
|
+
eintrag.ereignisse.forEach(function (ereignis) {
|
|
2212
2621
|
var li = e('li');
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
li.appendChild(quelleSpan('pfad', v.checkout.quelle, ' @ ' + v.checkout.ref));
|
|
2216
|
-
rul.appendChild(li);
|
|
2622
|
+
verlaufEreignisZeile(li, ereignis);
|
|
2623
|
+
ul.appendChild(li);
|
|
2217
2624
|
});
|
|
2218
|
-
|
|
2219
|
-
box.appendChild(
|
|
2220
|
-
}
|
|
2625
|
+
block.appendChild(ul);
|
|
2626
|
+
box.appendChild(block);
|
|
2627
|
+
});
|
|
2221
2628
|
}
|
|
2222
2629
|
|
|
2223
2630
|
/* ================= PLUGINS ================= */
|
|
@@ -2691,6 +3098,132 @@
|
|
|
2691
3098
|
+ '. Nicht von hmgr verwaltete Einträge liegen unverändert in den Verzeichnissen.';
|
|
2692
3099
|
}
|
|
2693
3100
|
|
|
3101
|
+
/* ---------------- Homedirs eines Harness (E78) ---------------- */
|
|
3102
|
+
|
|
3103
|
+
/**
|
|
3104
|
+
* Die Homedir-Zeilen unter einem Harness-Schalter (E78): das primäre mit „Ändern"/„Zurücksetzen"
|
|
3105
|
+
* (F89), darunter jedes zusätzliche mit „Vergessen" (F87), zuletzt der Knopf zum Eintragen eines
|
|
3106
|
+
* weiteren. Der Knopf steht nur bei einem Harness, der die Fähigkeit hat, und nur unter Windows —
|
|
3107
|
+
* dieselbe Grenze wie im Kern, hier nur damit niemand einen Weg vor sich sieht, der abbricht.
|
|
3108
|
+
*/
|
|
3109
|
+
function heimeFuerHarness(h) {
|
|
3110
|
+
var kasten = e('div', 'heime');
|
|
3111
|
+
var eintrag = null;
|
|
3112
|
+
HARNESS_HEIME.forEach(function (x) { if (x.harness === h) eintrag = x; });
|
|
3113
|
+
if (!eintrag) return kasten;
|
|
3114
|
+
eintrag.heime.forEach(function (heim) {
|
|
3115
|
+
var zeile = e('div', 'heimzeile');
|
|
3116
|
+
zeile.appendChild(e('span', 'heimart', heim.adresse ? heim.adresse : 'Homedir'));
|
|
3117
|
+
var pfad = e('code', 'heimpfad', pfadKurz(heim.pfad));
|
|
3118
|
+
pfad.title = heim.pfad;
|
|
3119
|
+
zeile.appendChild(pfad);
|
|
3120
|
+
var knoepfe = e('span', 'heimknoepfe');
|
|
3121
|
+
if (heim.adresse) {
|
|
3122
|
+
var weg = e('button', 'knopf leise klein', 'Vergessen');
|
|
3123
|
+
weg.type = 'button';
|
|
3124
|
+
weg.setAttribute('aria-label', heim.adresse + ' bei ' + harnessName(h) + ' vergessen');
|
|
3125
|
+
weg.addEventListener('click', function () { frageHeimVergessen(h, heim.adresse); });
|
|
3126
|
+
knoepfe.appendChild(weg);
|
|
3127
|
+
} else {
|
|
3128
|
+
var aendern = e('button', 'knopf leise klein', 'Ändern …');
|
|
3129
|
+
aendern.type = 'button';
|
|
3130
|
+
aendern.setAttribute('aria-label', 'Homedir von ' + harnessName(h) + ' ändern');
|
|
3131
|
+
aendern.addEventListener('click', function () { oeffneHeimSetzen(h, heim.pfad); });
|
|
3132
|
+
knoepfe.appendChild(aendern);
|
|
3133
|
+
if (heim.eingestellt) {
|
|
3134
|
+
var zurueck = e('button', 'knopf leise klein', 'Zurücksetzen');
|
|
3135
|
+
zurueck.type = 'button';
|
|
3136
|
+
zurueck.addEventListener('click', function () {
|
|
3137
|
+
rufe(function () { return window.hmgr.harnessHeimZuruecksetzen(h); }).then(async function () {
|
|
3138
|
+
await ladeAlles();
|
|
3139
|
+
melde(harnessName(h) + ' liest sein Homedir wieder am Standardort. Am alten Ort bleibt alles liegen.');
|
|
3140
|
+
});
|
|
3141
|
+
});
|
|
3142
|
+
knoepfe.appendChild(zurueck);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
zeile.appendChild(knoepfe);
|
|
3146
|
+
kasten.appendChild(zeile);
|
|
3147
|
+
});
|
|
3148
|
+
if (eintrag.kannZusaetzliche && PLATTFORM === 'win32') {
|
|
3149
|
+
var neu = e('button', 'knopf leise klein', 'Homedir in WSL hinzufügen …');
|
|
3150
|
+
neu.type = 'button';
|
|
3151
|
+
neu.addEventListener('click', function () { oeffneHeimHinzufuegen(h); });
|
|
3152
|
+
kasten.appendChild(neu);
|
|
3153
|
+
}
|
|
3154
|
+
return kasten;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
var heimHarness = null;
|
|
3158
|
+
function oeffneHeimSetzen(h, jetzt) {
|
|
3159
|
+
heimHarness = h;
|
|
3160
|
+
$('dhh-titel').textContent = 'Homedir von ' + harnessName(h);
|
|
3161
|
+
$('dhh-pfad').value = jetzt;
|
|
3162
|
+
zeige($('d-heim-pfad'));
|
|
3163
|
+
$('dhh-pfad').focus();
|
|
3164
|
+
}
|
|
3165
|
+
nachDialog($('d-heim-pfad'), async function (wert) {
|
|
3166
|
+
var h = heimHarness;
|
|
3167
|
+
heimHarness = null;
|
|
3168
|
+
if (wert !== 'ok' || !h) return;
|
|
3169
|
+
var pfad = $('dhh-pfad').value.trim();
|
|
3170
|
+
if (!pfad) return;
|
|
3171
|
+
var gesetzt = await rufe(function () { return window.hmgr.harnessHeimSetzen({ harness: h, pfad: pfad }); });
|
|
3172
|
+
await ladeAlles();
|
|
3173
|
+
melde(harnessName(h) + ' liest sein Homedir jetzt unter ' + gesetzt
|
|
3174
|
+
+ '. Was am alten Ort liegt, bleibt dort liegen — hmgr zeigt es nicht mehr.');
|
|
3175
|
+
});
|
|
3176
|
+
|
|
3177
|
+
async function oeffneHeimHinzufuegen(h) {
|
|
3178
|
+
heimHarness = h;
|
|
3179
|
+
var auswahl = $('dhw-distro');
|
|
3180
|
+
auswahl.textContent = '';
|
|
3181
|
+
var distros = await rufe(function () { return window.hmgr.wslDistros(); });
|
|
3182
|
+
(distros || []).forEach(function (name) {
|
|
3183
|
+
var option = document.createElement('option');
|
|
3184
|
+
option.value = name;
|
|
3185
|
+
option.textContent = name;
|
|
3186
|
+
auswahl.appendChild(option);
|
|
3187
|
+
});
|
|
3188
|
+
$('dhw-leer').hidden = (distros || []).length > 0;
|
|
3189
|
+
$('dhw-distro').hidden = (distros || []).length === 0;
|
|
3190
|
+
$('dhw-ok').disabled = (distros || []).length === 0;
|
|
3191
|
+
zeige($('d-heim-wsl'));
|
|
3192
|
+
}
|
|
3193
|
+
nachDialog($('d-heim-wsl'), async function (wert) {
|
|
3194
|
+
var h = heimHarness;
|
|
3195
|
+
heimHarness = null;
|
|
3196
|
+
if (wert !== 'ok' || !h) return;
|
|
3197
|
+
var distro = $('dhw-distro').value;
|
|
3198
|
+
if (!distro) return;
|
|
3199
|
+
var heim = await rufe(function () {
|
|
3200
|
+
return window.hmgr.harnessHeimHinzufuegen({ harness: h, adresse: 'wsl:' + distro });
|
|
3201
|
+
});
|
|
3202
|
+
await ladeAlles();
|
|
3203
|
+
melde(harnessName(h) + ' hat jetzt ein zweites Ziel: ' + heim.adresse + ' mit dem Homedir ' + heim.pfad
|
|
3204
|
+
+ '. Links legst du dort einzeln, gespiegelt wird nichts.');
|
|
3205
|
+
});
|
|
3206
|
+
|
|
3207
|
+
function frageHeimVergessen(h, adresse) {
|
|
3208
|
+
heimHarness = h;
|
|
3209
|
+
$('dhv-titel').textContent = adresse + ' vergessen?';
|
|
3210
|
+
$('dhv-text').textContent = 'hmgr verwaltet dieses Homedir dann nicht mehr und zeigt sein Ziel nicht mehr an. '
|
|
3211
|
+
+ 'Die Links, die dort liegen, bleiben liegen.';
|
|
3212
|
+
$('d-heim-weg').dataset.adresse = adresse;
|
|
3213
|
+
zeige($('d-heim-weg'));
|
|
3214
|
+
}
|
|
3215
|
+
nachDialog($('d-heim-weg'), async function (wert) {
|
|
3216
|
+
var h = heimHarness;
|
|
3217
|
+
heimHarness = null;
|
|
3218
|
+
var adresse = $('d-heim-weg').dataset.adresse;
|
|
3219
|
+
if (wert !== 'ok' || !h || !adresse) return;
|
|
3220
|
+
await rufe(function () { return window.hmgr.harnessHeimEntfernen({ harness: h, adresse: adresse }); });
|
|
3221
|
+
await ladeAlles();
|
|
3222
|
+
melde(harnessName(h) + ' verwaltet ' + adresse + ' nicht mehr. Die Links dort bleiben liegen.');
|
|
3223
|
+
});
|
|
3224
|
+
|
|
3225
|
+
enterBestaetigt($('dhh-pfad'), $('dhh-ok'));
|
|
3226
|
+
|
|
2694
3227
|
function baueHarnessliste() {
|
|
2695
3228
|
var ul = $('harnessliste');
|
|
2696
3229
|
// Der Neuaufbau wirft den Schalter weg, auf dem der Fokus steht. Nach einer Rückfrage ist genau
|
|
@@ -2721,6 +3254,7 @@
|
|
|
2721
3254
|
txt.appendChild(e('small', null, stand));
|
|
2722
3255
|
label.appendChild(txt);
|
|
2723
3256
|
li.appendChild(label);
|
|
3257
|
+
li.appendChild(heimeFuerHarness(h));
|
|
2724
3258
|
ul.appendChild(li);
|
|
2725
3259
|
});
|
|
2726
3260
|
if (merk) {
|
|
@@ -2769,40 +3303,27 @@
|
|
|
2769
3303
|
var liegen = bilanzText(harnessBilanz(h));
|
|
2770
3304
|
await rufe(function () { return window.hmgr.harnessDeaktivieren(h); });
|
|
2771
3305
|
await ladeAlles();
|
|
2772
|
-
|
|
3306
|
+
// E74: die Ziele verschwinden mit dem Harness aus der Ansicht, ein Säubern-Knopf im Ziel
|
|
3307
|
+
// existiert nicht mehr. Was bleibt, ist der Weg über das Wiedereinschalten.
|
|
3308
|
+
melde(harnessName(h) + ' deaktiviert. ' + liegen + ' bleiben liegen, hmgr pflegt sie nicht mehr. Die Ziele verschwinden aus der Liste und erscheinen wieder, sobald du ' + harnessName(h) + ' wieder einschaltest.');
|
|
2773
3309
|
} else {
|
|
2774
3310
|
// Abbruch: der Schalter im DOM steht noch auf aus, die Einstellung nicht. Neu aufbauen setzt ihn zurück.
|
|
2775
3311
|
baueHarnessliste();
|
|
2776
3312
|
}
|
|
2777
3313
|
});
|
|
2778
3314
|
|
|
2779
|
-
/*
|
|
2780
|
-
|
|
2781
|
-
|
|
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
|
-
});
|
|
3315
|
+
/* Einen eigenen Säubern-Weg für einen schon inaktiven Harness gibt es nicht mehr (E74): sein Ziel
|
|
3316
|
+
steht nicht mehr im Bild, also auch kein Knopf darin. Wer doch säubern will, schaltet den
|
|
3317
|
+
Harness ein und wieder aus — dann fragt der Dialog oben erneut. */
|
|
2799
3318
|
|
|
2800
3319
|
function baueEinstellungen() {
|
|
2801
3320
|
baueHarnessliste();
|
|
2802
3321
|
var liste = $('quellenliste');
|
|
2803
3322
|
liste.textContent = '';
|
|
2804
3323
|
if (QUELLEN.length === 0) {
|
|
2805
|
-
|
|
3324
|
+
// Eigene Klasse statt nacktem Text im Listen-Grid (E75): `.quellenliste li` ist ein Grid mit
|
|
3325
|
+
// 20px-Icon-Spalte, in die der anonyme Textknoten fiel — jedes Wort brach einzeln um.
|
|
3326
|
+
liste.appendChild(e('li', 'leerhinweis', 'Noch keine Quelle. „Quelle hinzufügen …" legt die erste an.'));
|
|
2806
3327
|
return;
|
|
2807
3328
|
}
|
|
2808
3329
|
QUELLEN.forEach(function (q) {
|