iobroker.alias-workbench 0.8.0 → 0.8.2

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.
@@ -22,6 +22,89 @@ export function rolleAusAusdruck(r) {
22
22
 
23
23
  export function rolleVonPlatz(st) { return st.defaultRole || rolleAusAusdruck(st.role); }
24
24
 
25
+ /* Alle Schreibweisen, die ein Platz annimmt — nicht nur seine Vorgabe.
26
+
27
+ Der LOWBAT-Platz nennt als Vorgabe `indicator.maintenance.lowbat`,
28
+ sein Ausdruck erlaubt aber vier Formen:
29
+
30
+ /^indicator(\.maintenance)?\.(lowbat|battery)$/
31
+
32
+ Nur die Vorgabe in die Auswahlliste zu stellen hiess: Wer die Rolle
33
+ sucht, die sein eigenes Geraet traegt, findet sie nicht. hm-rpc
34
+ schreibt `indicator.lowbat`, alle elf Homematic-Vorlagen benutzen sie,
35
+ und die ioBroker-Rollenliste fuehrt beide nebeneinander — nur die
36
+ Werkbank kannte eine davon nicht (Ricardo, 06.09.2026).
37
+
38
+ Ausmultipliziert werden genau zwei Formen, weil nur die im Detector
39
+ vorkommen: Alternativen `(a|b)` und optionale Gruppen `(…)?`. Alles
40
+ andere — Zeichenklassen, `.*`, Verschachtelung — gibt leer zurueck;
41
+ dann bleibt es bei der Vorgaberolle. Lieber eine Schreibweise zu wenig
42
+ als eine erfundene in der Liste. */
43
+ export function schreibweisenAus(r) {
44
+ if (!r) { return []; }
45
+ var q = (r.source !== undefined) ? r.source : String(r);
46
+ if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return []; }
47
+ q = q.slice(1, -1);
48
+
49
+ var teile = [''];
50
+ var rest = q;
51
+ var sicher = 0;
52
+ while (rest && sicher++ < 40) {
53
+ var g = /^\(([^()]*)\)(\?)?/.exec(rest);
54
+ if (g) {
55
+ var wahl = g[1].split('|');
56
+ if (g[2]) { wahl = wahl.concat(['']); } /* optional: auch weglassen */
57
+ var neu = [];
58
+ teile.forEach(function (v) {
59
+ wahl.forEach(function (w) { neu.push(v + w.split('\\').join('')); });
60
+ });
61
+ teile = neu;
62
+ rest = rest.slice(g[0].length);
63
+ continue;
64
+ }
65
+ var lit = /^(\\.|[A-Za-z0-9_.])+/.exec(rest);
66
+ if (!lit) { return []; } /* etwas Unbekanntes */
67
+ var text = lit[0].split('\\').join('');
68
+ teile = teile.map(function (v) { return v + text; });
69
+ rest = rest.slice(lit[0].length);
70
+ }
71
+ if (rest) { return []; }
72
+
73
+ var gut = [];
74
+ teile.forEach(function (t) {
75
+ if (/^[A-Za-z0-9_]+([.][A-Za-z0-9_]+)*$/.test(t) && gut.indexOf(t) === -1) { gut.push(t); }
76
+ });
77
+ return gut;
78
+ }
79
+
80
+ /* Rollen, die der Detector aus Ruecksicht auf alte Adapter noch annimmt,
81
+ die aber nicht mehr benutzt werden sollen. Aus der Tabelle „Deprecated
82
+ role aliases" in ioBroker.docs (docs/en/dev/stateroles.md, gelesen am
83
+ 06.09.2026) — dort steht zu jeder auch, was stattdessen gilt.
84
+
85
+ Sie stehen hier, weil die Zerlegung oben sonst gerade sie in die
86
+ Auswahlliste holt: `indicator(\.maintenance)?\.(lowbat|battery)`
87
+ erlaubt eben auch `indicator.battery`. Eine laengere Liste ist kein
88
+ Gewinn, wenn die Haelfte davon veraltet ist.
89
+
90
+ Vorgaberollen des Detectors werden NICHT gefiltert: was er selbst als
91
+ Vorgabe fuehrt, gilt, auch wenn die Doku es anders saehe. Gefiltert
92
+ wird nur, was diese Zerlegung zusaetzlich vorschlaegt. */
93
+ var VERALTET = {};
94
+ ('action.close action.close.blind action.close.tilt action.home action.next ' +
95
+ 'action.open action.open.blind action.open.tilt action.pause action.play ' +
96
+ 'action.prev action.stop action.stop.blind action.stop.tilt ' +
97
+ 'indicator.battery indicator.fire indicator.flood ' +
98
+ 'indicator.maintenance.battery indicator.unreach ' +
99
+ 'level.thermostat sensor.co sensor.fire sensor.flood ' +
100
+ 'state.active state.alarm.co state.alarm.fire state.alarm.flood state.co ' +
101
+ 'state.door state.fire state.flood state.light state.motion state.window ' +
102
+ 'switch.active switch.autofocus switch.autowhitebalance switch.boost ' +
103
+ 'switch.brightness switch.nightmode switch.party ' +
104
+ 'value.accuracy value.brush value.brush.side value.elevation ' +
105
+ 'value.latitude value.longitude value.radius value.sensors'
106
+ ).split(' ').forEach(function (r) { VERALTET[r] = 1; });
107
+
25
108
  export var ROLLEN = (function () {
26
109
  if (!D) { return []; }
27
110
  var m = {};
@@ -29,6 +112,10 @@ export var ROLLEN = (function () {
29
112
  D.patterns[t].states.forEach(function (st) {
30
113
  var r = rolleVonPlatz(st);
31
114
  if (r) { m[r] = 1; }
115
+ /* Dazu, was der Platz sonst noch annimmt — ohne das Veraltete. */
116
+ schreibweisenAus(st.role).forEach(function (x) {
117
+ if (!VERALTET[x]) { m[x] = 1; }
118
+ });
32
119
  });
33
120
  });
34
121
  ['state', 'text', 'json', 'value'].forEach(function (r) { m[r] = 1; });
@@ -355,6 +355,22 @@ function zeichneTausch() {
355
355
  var z1 = el('div');
356
356
  z1.appendChild(el('b', null, tr('swap.from') + ' '));
357
357
  z1.appendChild(el('span', 'sn', alt || tr('swap.unknown')));
358
+ /* Den Namen dahinter, wo er mehr sagt als die Kennung.
359
+
360
+ `hm-rpc.0.0000DBE9A2B8AF` sagt einem nichts; woran man das Geraet
361
+ erkennt, steht allein im Namen (Ricardo, 06.09.2026). Dieselbe Regel
362
+ wie im Baum: gleicht der Name dem letzten Stueck der Kennung, bringt
363
+ er nichts und bleibt weg.
364
+
365
+ Ist das Geraet weg — der Normalfall bei „Geraet kaputt" —, gibt es
366
+ kein Objekt und damit keinen Namen mehr. Dann steht dort wie bisher
367
+ nur die Kennung, und die Zeile darunter sagt, dass es sie nicht mehr
368
+ gibt. */
369
+ var altObj = alt && S.objects[alt];
370
+ var altName = altObj ? txt(altObj.common && altObj.common.name).trim() : '';
371
+ if (altName && altName !== String(alt).split('.').pop()) {
372
+ z1.appendChild(el('span', 'leise', ' ' + altName));
373
+ }
358
374
  kopf.appendChild(z1);
359
375
  if (alt && !S.objects[alt] && !kindZustaende(alt).length) {
360
376
  kopf.appendChild(el('div', 'hint', tr('swap.gone')));
@@ -18,7 +18,8 @@ import {
18
18
  ordnerUnterAlias,
19
19
  gemeinsameKanaele,
20
20
  kennungtauglich,
21
- entwurfFuer
21
+ entwurfFuer,
22
+ aliasFuer
22
23
  } from './entwurf.js';
23
24
  import { zeichneErgebnis } from './ergebnis.js';
24
25
 
@@ -488,6 +489,20 @@ export function zeigeTrockenlauf(alleAusgaenge) {
488
489
  }
489
490
  body.appendChild(kopf);
490
491
 
492
+ /* Dieselbe Auskunft wie in der Zielleiste, noch einmal hier - der
493
+ Trockenlauf ist die letzte Stelle vor dem Schreiben, und wer den
494
+ Balken oben ueberliest, soll es spaetestens jetzt sehen: es gibt
495
+ schon einen Alias auf diese Quelle, und der bleibt stehen. */
496
+ var zwQuelle = S.entwurf && S.entwurf.kanal;
497
+ var zwZiel = S.entwurf && (S.entwurf.ziel || S.entwurf.kanal);
498
+ var zwAlt = zwQuelle ? aliasFuer(zwQuelle) : null;
499
+ if (zwAlt && S.objects[zwAlt] && zwAlt !== zwZiel) {
500
+ var zwK = el('div', 'aside w');
501
+ zwK.style.marginBottom = '11px';
502
+ zwK.appendChild(el('b', null, tr('target.twinExists', zwAlt)));
503
+ body.appendChild(zwK);
504
+ }
505
+
491
506
  if (weg.length) {
492
507
  var wk = el('div', 'card');
493
508
  wk.style.marginBottom = '11px';
@@ -986,17 +1001,27 @@ export function sucheInSkripten(kennung, fertig) {
986
1001
  } catch { fertigMit(); }
987
1002
  }
988
1003
 
989
- export function zeigeVerlegen() {
1004
+ export function zeigeVerlegen(altVorgabe, zielVorgabe) {
990
1005
  /* Der Knopf steht auch an der Quelle, sobald es den Alias gibt (T1).
991
1006
  Dort ist S.current die Quelle, nicht der Alias - gemeint ist immer
992
1007
  das Ziel des Entwurfs. Genauso haelt es zeigeLoeschen; zeigeTausch
993
1008
  tat es bis 05.09.2026 nicht, und beide Knoepfe waren an der Quelle
994
- sichtbar, aber wirkungslos: kein Dialog, keine Meldung. */
995
- var alt = (S.entwurf && (S.entwurf.ziel || S.entwurf.kanal)) || S.current;
1009
+ sichtbar, aber wirkungslos: kein Dialog, keine Meldung.
1010
+
1011
+ Die zwei Vorgaben kommen vom Knopf „stattdessen verlegen" in der
1012
+ Zielleiste: dort ist das Ziel des Entwurfs der NEUE Pfad, den es
1013
+ noch gar nicht gibt - ohne Vorgabe stiege der Dialog gleich in der
1014
+ ersten Zeile wieder aus. Verlegt wird der vorhandene Alias
1015
+ (`altVorgabe`), und die Felder stehen schon auf dem, was in der
1016
+ Zielleiste getippt wurde (`zielVorgabe`). */
1017
+ var alt = (typeof altVorgabe === 'string' && altVorgabe) ||
1018
+ (S.entwurf && (S.entwurf.ziel || S.entwurf.kanal)) || S.current;
996
1019
  if (!alt || alt.indexOf('alias.') !== 0 || !S.objects[alt]) { return; }
997
1020
  S.verlegeZiel = alt;
998
1021
 
999
- var teile = alt.split('.');
1022
+ var vorbelegt = (typeof zielVorgabe === 'string' && zielVorgabe &&
1023
+ zielVorgabe.indexOf('alias.') === 0) ? zielVorgabe : alt;
1024
+ var teile = vorbelegt.split('.');
1000
1025
  var name0 = teile.pop();
1001
1026
  var ordner0 = teile.join('.');
1002
1027
 
@@ -370,8 +370,17 @@ export function schreibZiel(z, kanal, auf) {
370
370
  return befehlMoeglich(kanal, pfad) ? (kanal + '.' + pfad) : '';
371
371
  }
372
372
 
373
- /* Vorlage auf einen Kanal anwenden → Entwurf. */
374
- export function wendeAn(v, kanal, gruende, instanz) {
373
+ /* Vorlage auf einen Kanal anwenden → Entwurf.
374
+
375
+ `auchOhneTreffer` laesst einen Entwurf auch dann entstehen, wenn keine
376
+ einzige Zeile der Vorlage eine Quelle am Geraet findet. Das gilt nur fuer
377
+ die Wahl von Hand: Genau dafuer ist das Auswahlfeld gedacht — „sonst steht
378
+ man bei einem Geraet, das noch nichts gesendet hat, ohne jede Moeglichkeit
379
+ da". Bis 06.09.2026 fiel gerade dieser Fall durch das Sieb unten, und der
380
+ Wechsel scheiterte still (Ricardo). Die automatische Erkennung ruft ohne
381
+ die Fahne auf und bleibt unveraendert — dort waere ein Entwurf ohne eine
382
+ einzige Zeile ein Fehlgriff. */
383
+ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
375
384
  var auf = function (t) { return setzeInstanz(t, v, instanz); };
376
385
  var e = { kanal: kanal, states: [], want: v.geraetetyp, wantAuto: v.geraetetyp,
377
386
  alleMuster: false, vorschlag: true, roh: false,
@@ -499,7 +508,7 @@ export function wendeAn(v, kanal, gruende, instanz) {
499
508
  });
500
509
  });
501
510
 
502
- return e.states.length ? e : null;
511
+ return (e.states.length || auchOhneTreffer) ? e : null;
503
512
  }
504
513
 
505
514
  /* Beste Vorlage suchen und anwenden. */
@@ -521,7 +530,7 @@ export function vorschlag(kanal, vorlagenId, instanz) {
521
530
  gewaehlt = treffer[0];
522
531
  }
523
532
 
524
- var e = wendeAn(gewaehlt.vorlage, kanal, gewaehlt.gruende, gewaehlt.instanz);
533
+ var e = wendeAn(gewaehlt.vorlage, kanal, gewaehlt.gruende, gewaehlt.instanz, !!vorlagenId);
525
534
  if (!e) { return null; }
526
535
  if (!vorlagenId && gewaehlt.passt) {
527
536
  var gleichAuf = treffer.filter(function (t) { return t.punkte === gewaehlt.punkte; });
@@ -13,6 +13,7 @@ import { enumListe } from './aufzaehlungen.js';
13
13
  import { enumZeile, setzeZiel } from './zuordnung.js';
14
14
  import { aliasFuer, ordnerUnterAlias, kennungtauglich } from './entwurf.js';
15
15
  import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
16
+ import { zeigeVerlegen } from './schreiben.js';
16
17
 
17
18
  export function baueZielleiste(host, e) {
18
19
  var istQuelle = S.current.indexOf('alias.') !== 0;
@@ -224,7 +225,23 @@ export function baueZielleiste(host, e) {
224
225
  });
225
226
  iO.addEventListener('change', uebernehmen);
226
227
  iO.addEventListener('keydown', function (ev) {
227
- if (ev.key === 'Escape') { schliessen(); return; }
228
+ /* Escape heisst „doch nicht" — die Liste zu machen reicht dafuer
229
+ nicht, der getippte Text muss auch weg. Sonst steht er weiter im
230
+ Feld und wird beim Verlassen uebernommen: Wer Escape drueckt und
231
+ danebenklickt, legt den Alias unter einem Ordner an, den er
232
+ gerade verworfen zu haben glaubte.
233
+
234
+ Musterfeld (`musterwahl.js`, ruft mBeschriften) und
235
+ Aufzaehlungsfeld (`zuordnung.js`, setzt ei.value zurueck) machen
236
+ das laengst; hier und im Namensfeld daneben fehlte es
237
+ (Ricardo, 06.09.2026). */
238
+ if (ev.key === 'Escape') {
239
+ iO.value = (e.zielOrdner === 'alias.0') ? '' : e.zielOrdner.slice('alias.0.'.length);
240
+ xBtn.hidden = !iO.value;
241
+ schliessen();
242
+ iO.blur();
243
+ return;
244
+ }
228
245
  if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
229
246
  if (vlist.hidden) { malen(true); return; }
230
247
  ev.preventDefault();
@@ -263,6 +280,14 @@ export function baueZielleiste(host, e) {
263
280
  var iN2 = el('input', 'tx');
264
281
  iN2.type = 'text';
265
282
  iN2.value = e.zielName;
283
+ /* Dasselbe fuer den Namen. Hier gab es bisher gar keinen
284
+ Tastenhandler — Escape tat nichts, der getippte Name blieb stehen
285
+ und wurde beim Verlassen uebernommen (Ricardo, 06.09.2026). */
286
+ iN2.addEventListener('keydown', function (ev) {
287
+ if (ev.key !== 'Escape') { return; }
288
+ iN2.value = e.zielName;
289
+ iN2.blur();
290
+ });
266
291
  iN2.addEventListener('change', function () {
267
292
  /* Dieselbe Regel wie im Vorschlag und im Verlege-Dialog. Die eigene
268
293
  Fassung hier liess Punkte und doppelte Unterstriche stehen - ein
@@ -291,7 +316,13 @@ export function baueZielleiste(host, e) {
291
316
  Aenderung folgen, und der Blick ist beim angefassten Feld, nicht
292
317
  beim anderen. Der Text nennt deshalb die Wirkung woanders. */
293
318
  var angebot = null;
294
- if (e.zuletzt === 'ordner' && !aliasFuer(e.kanal)) {
319
+ /* Frueher hing hier zusaetzlich `!aliasFuer(e.kanal)` - an einem
320
+ Geraet, das schon einen Alias hat, kam die Frage also nie. Gedacht
321
+ war das als „am fertigen Alias wird nicht mehr geraten"; in
322
+ Wahrheit ist gerade das der Fall, in dem sie gebraucht wird: wer
323
+ den Ordner eines vorhandenen Alias umschreibt, zieht um, und der
324
+ Raum soll mit (Ricardo, 06.09.2026). */
325
+ if (e.zuletzt === 'ordner') {
295
326
  var otx = String(e.zielOrdner || '').slice('alias.0.'.length);
296
327
  if (otx && otx.indexOf('.') === -1) {
297
328
  var vorhandenerRaum = enumListe('rooms', true).filter(function (r) {
@@ -322,6 +353,42 @@ export function baueZielleiste(host, e) {
322
353
  setTimeout(function () { iO.focus(); }, 0);
323
354
  }
324
355
 
356
+ /* Der Zwilling: es gibt schon einen Alias auf diese Quelle, und das
357
+ Ziel zeigt woandershin.
358
+
359
+ Die Werkbank weiss das in diesem Moment - `aliasFuer` liefert den
360
+ alten Pfad -, sagte es aber nirgends. Am Chip stand nur „wird neu
361
+ angelegt", was stimmt und die Haelfte verschweigt: der alte bleibt
362
+ stehen, und aus einem Umbenennen des Ordners wird lautlos ein
363
+ zweiter Alias auf dieselbe Quelle (Ricardo, 06.09.2026, am
364
+ Bastelzimmer_Decklenlicht_RGB).
365
+
366
+ Verboten wird nichts. Zwei Aliase auf eine Quelle sind ein
367
+ ordentlicher Fall - einer fuers Licht, einer fuer den Verbrauch;
368
+ „alle anlegen" tut bei mehreren Ausgaengen nichts anderes. Es
369
+ fehlte die Auskunft, nicht die Sperre. Wer wirklich einen zweiten
370
+ will, drueckt denselben Knopf wie bisher und weiss jetzt, was er
371
+ tut; wer umziehen wollte, hat den Weg dafuer daneben.
372
+
373
+ Ohne Rueckprobe ueber `quelleVon`: an einem Homematic-Kanal
374
+ (…B8AF.0) zeigt die Quelle des Alias auf das Geraet (…B8AF), die
375
+ Probe schluege fehl und der Hinweis bliebe genau dort aus, wo er
376
+ gebraucht wird. `aliasFuer` ist dieselbe Funktion, mit der die
377
+ Werkbank auch „zum Alias →" und ihre Zielvorschlaege bestimmt -
378
+ irrt sie hier, irrt sie dort ebenso. Und der Hinweis haelt
379
+ niemanden auf. */
380
+ var zwilling = null;
381
+ var vorhAlias = aliasFuer(e.kanal);
382
+ if (vorhAlias && S.objects[vorhAlias] && vorhAlias !== e.ziel) {
383
+ var zw = el('div', 'uebernahme zwilling');
384
+ zw.appendChild(el('span', 'zq', tr('target.twinExists', vorhAlias)));
385
+ var vb = el('button', 'btn mini', tr('target.moveInstead'));
386
+ vb.title = tr('target.moveInsteadHint');
387
+ vb.addEventListener('click', function () { zeigeVerlegen(vorhAlias, e.ziel); });
388
+ zw.appendChild(vb);
389
+ zwilling = zw;
390
+ }
391
+
325
392
  /* Das Angebot ganz ans Ende der Leiste.
326
393
 
327
394
  Zuerst hing es direkt hinter dem Ordnerfeld - und weil es eine
@@ -329,6 +396,7 @@ export function baueZielleiste(host, e) {
329
396
  Namensfeld stand ploetzlich darunter statt daneben. Eine Zeile,
330
397
  die umbricht, gehoert hinter das, was in der Zeile bleiben soll. */
331
398
  if (angebot) { zb.appendChild(angebot); }
399
+ if (zwilling) { zb.appendChild(zwilling); }
332
400
 
333
401
  zb.appendChild(ergebnis);
334
402
 
@@ -577,6 +577,21 @@ export function uebernehmeBestand(e) {
577
577
  stehen; neue Punkte bekommen den neuen Namen. */
578
578
  if (q.read && kenntQuelle[q.read] && !S.objects[e.ziel + '.' + kenntQuelle[q.read].n]) {
579
579
  var vorhanden = kenntQuelle[q.read];
580
+ /* Die Zeile bekommt den Namen aus dem Alias — und damit einen
581
+ anderen, als die Erkennung eben gesehen hat. `platzVon` kennt
582
+ noch den Vorlagennamen, die Zeile heisst jetzt anders, und die
583
+ Platzpruefung findet nichts: Am `Bastelzimmer_Licht` stand nach
584
+ einem Wechsel auf die Steckdosen-Vorlage „kein Platz im
585
+ Steckdose-Muster" an ON_ACTUAL, obwohl der Platz ACTUAL frei
586
+ besetzt war. Nach jedem beliebigen Neuzeichnen war es weg
587
+ (Ricardo, 06.09.2026).
588
+
589
+ `bestandZog` loest genau dafuer das einmalige Nachzeichnen aus.
590
+ Gesetzt wurde es bisher nur in `vomAliasUebernehmen` — und das
591
+ steigt bei gesetzter Vorlage sofort aus, damit der Vorlagenstand
592
+ gilt. Die Umbenennung findet aber trotzdem statt, also gehoert
593
+ die Fahne hierher. */
594
+ if (vorhanden.n !== n) { e.bestandZog = true; }
580
595
  vorhanden.n = n;
581
596
  vorhanden.on = true;
582
597
  vorhanden.ausBestand = true;
@@ -11,7 +11,7 @@ import { el } from './basis.js';
11
11
  import { tr } from './sprache.js';
12
12
  import { erkenneEntwurf, musterVon, rolleVonPlatz } from './erkennung.js';
13
13
  import { kindZustaende, wertVon, fmt } from './werte.js';
14
- import { steuerKanaele, vorlagenAbweichung, waehle } from './entwurf.js';
14
+ import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle } from './entwurf.js';
15
15
  import { musterName } from './musternamen.js';
16
16
  import { detailZeile } from './detail.js';
17
17
  import { rateAnzahl, rateZurueck, rateZurueckEine, rateBehalten } from './vorschlagen.js';
@@ -206,6 +206,10 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
206
206
  }
207
207
 
208
208
  var bar = el('div', 'listbar');
209
+ /* Wird beim Zeichnen der Zeilen weiter unten hochgezaehlt; die Legende
210
+ dazu haengt sich erst danach in die Leiste, weil vorher niemand
211
+ weiss, ob es ueberhaupt eine braucht. */
212
+ var ohnePlatzZahl = 0;
209
213
  var an = e.states.filter(function (s) { return s.on; }).length;
210
214
  bar.appendChild(document.createTextNode(tr('list.willBeCreated')));
211
215
  bar.appendChild(el('span', 'cnt', tr('list.countOf', an, e.states.length)));
@@ -325,8 +329,19 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
325
329
  e.states.forEach(function (s, i) {
326
330
  var pl = platzVon[s.n];
327
331
  var unfertig = (!s.n || !s.srcR);
328
- var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : ''));
332
+ /* Angehakt, vollstaendig, aber ohne Platz im Muster. Frueher stand
333
+ dazu in JEDER betroffenen Zeile derselbe Satz — bei einem
334
+ Thermostat mit 42 Punkten zwanzigmal dasselbe, und die Spalte
335
+ wurde so breit, dass Rolle und Wert wegrutschten (Ricardo,
336
+ 06.09.2026). Jetzt traegt die Zeile eine Marke, der Satz steht
337
+ einmal als Legende ueber der Liste, und der lange Text haengt als
338
+ Hinweis an der Zeile. */
339
+ var ohnePlatz = (s.on && !unfertig && !pl);
340
+ if (ohnePlatz) { ohnePlatzZahl++; }
341
+ var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : '') +
342
+ (ohnePlatz ? ' ohneplatz' : ''));
329
343
  row.tabIndex = 0;
344
+ if (ohnePlatz) { row.title = tr('pattern.noPlaceLong', musterName(e.want) || e.want || '?'); }
330
345
 
331
346
  var cbw = el('span');
332
347
  var cb = document.createElement('input');
@@ -359,20 +374,49 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
359
374
  if (s.manuell) { n3.appendChild(el('span', 'hand', tr('list.byHand'))); }
360
375
  if (s.geaendert) { n3.appendChild(el('span', 'geae', tr('list.changed'))); }
361
376
  /* Was die Vorlage anders will, steht an der Zeile - sonst sieht man
362
- es erst im Trockenlauf (Ricardo, 25.08.2026). */
363
- if (s.on && !s.geaendert) {
377
+ es erst im Trockenlauf (Ricardo, 25.08.2026).
378
+
379
+ Nicht mehr an `!s.geaendert` gebunden: Seit man ein einzelnes Feld
380
+ aus der Vorlage uebernehmen kann, ist die Zeile danach „geaendert"
381
+ — und haette die Marke daran gehangen, waeren die uebrigen
382
+ Abweichungen in dem Moment unsichtbar geworden. Massgeblich ist,
383
+ ob wirklich noch etwas abweicht. Eine Zeile kann beides tragen:
384
+ „geaendert" und „weicht von der Vorlage ab" (06.09.2026). */
385
+ if (s.on) {
364
386
  var abw = vorlagenAbweichung(s);
365
387
  if (abw.length) {
366
- /* Formeln koennen lang sein - die Marke soll die Zeile nicht
367
- sprengen; der Tooltip zeigt alles. */
368
- var wert = String(abw[0].vorlage || '—');
369
- if (wert.length > 28) { wert = wert.slice(0, 27) + '…'; }
370
- var av = el('span', 'geae tplabw', tr('list.tplDiffers', wert));
388
+ /* Nur, DASS etwas abweicht — nicht was.
389
+
390
+ Die Marke nannte frueher den Wert der ersten Abweichung
391
+ („Vorlage: indicator.lowbat"). Bei mehreren Feldern log sie
392
+ damit: sie zeigte eines und verschwieg die anderen, und lang
393
+ war sie obendrein. Welches Feld es betrifft, sieht man
394
+ aufgeklappt an der Markierung; der Tooltip hier zaehlt alle
395
+ auf (Ricardo, 06.09.2026). */
396
+ var av = el('span', 'geae tplabw', tr('list.tplDiffers'));
371
397
  av.title = abw.map(function (x) {
372
398
  return x.feld + ': ' + (x.jetzt || '—') + ' → ' + (x.vorlage || '—');
373
399
  }).join(String.fromCharCode(10));
374
400
  n3.appendChild(av);
375
401
  }
402
+ /* Und dieselbe Auskunft gegen den gespeicherten Alias.
403
+
404
+ Wer das Muster oder die Vorlage wechselt, bekommt die Rollen
405
+ angepasst — das ist gewollt (`switch.light` passt auf kein
406
+ socket-SET). Nur sah man an der Zeile nicht, dass damit etwas
407
+ anderes geschrieben wuerde als bisher dasteht; sichtbar war es
408
+ allein an den Chips oben und im Trockenlauf. Drei Aussagen, drei
409
+ Marken, jede mit eigener Bedeutung: „geaendert" heisst, der
410
+ Nutzer war es; „weicht von der Vorlage ab" vergleicht mit der
411
+ Vorlage; diese hier mit der Datenbank (Ricardo, 06.09.2026). */
412
+ var bAbw = bestandsAbweichung(s, e.ziel);
413
+ if (bAbw.length) {
414
+ var bv = el('span', 'geae bstabw', tr('list.aliasDiffers'));
415
+ bv.title = bAbw.map(function (x) {
416
+ return x.feld + ': ' + (x.bestand || '—') + ' → ' + (x.jetzt || '—');
417
+ }).join(String.fromCharCode(10));
418
+ n3.appendChild(bv);
419
+ }
376
420
  }
377
421
  /* Die Marke ist zugleich der Griff: solange sie steht, ist die Zeile
378
422
  unbestaetigt und faellt beim Zuruecknehmen. Ein Haken laesst sie
@@ -418,14 +462,12 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
418
462
  var pe = el('em', null, pl);
419
463
  pe.title = tr('pattern.slotName', pl, musterName(e.want) || e.want || '?');
420
464
  n3.appendChild(pe);
421
- } else if (!pl) {
422
- /* Frueher stand der Vermerk nur an Zeilen, die das info-Muster
423
- aufnaehme (imInfo). SABOTAGE mit indicator.alarm nimmt aber
424
- nicht mal das - solche Zeilen blieben stumm, und oben stand
425
- „3/7 belegt“ bei vier Haken ohne jede Erklaerung (Ricardo,
426
- 25.08.2026). Angehakt ohne Platz heisst immer: sagen. */
427
- n3.appendChild(el('em', null, tr('pattern.noPlaceIn', musterName(e.want) || e.want || '?')));
428
465
  }
466
+ /* Kein Vermerk mehr je Zeile: die Marke an der Zeile und die
467
+ Legende ueber der Liste sagen dasselbe, einmal statt zwanzigmal.
468
+ Dass es ueberhaupt gesagt wird, bleibt wichtig — frueher blieben
469
+ solche Zeilen stumm, und oben stand „3/7 belegt“ bei vier Haken
470
+ ohne jede Erklaerung (Ricardo, 25.08.2026). */
429
471
  }
430
472
  row.appendChild(n3);
431
473
 
@@ -511,5 +553,18 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
511
553
  zeichneErgebnis();
512
554
  });
513
555
  card.appendChild(add);
556
+
557
+ /* Die Legende zum Streifen — einmal, und nur wenn es etwas zu erklaeren
558
+ gibt. Sie steht in der Leiste ueber der Liste, gleich neben der Zahl:
559
+ dort sucht man die Erklaerung fuer „11 von 42", nicht am Fuss. */
560
+ if (ohnePlatzZahl) {
561
+ var lg = el('span', 'legende');
562
+ lg.appendChild(el('span', 'lgmarke'));
563
+ lg.appendChild(document.createTextNode(
564
+ tr('pattern.noPlaceIn', musterName(e.want) || e.want || '?')));
565
+ lg.title = tr('pattern.noPlaceLong', musterName(e.want) || e.want || '?');
566
+ bar.insertBefore(lg, bar.querySelector('.sp2'));
567
+ }
568
+
514
569
  host.appendChild(card);
515
570
  }
package/admin/tab.html CHANGED
@@ -144,5 +144,16 @@
144
144
  <div class="dfoot"><button class="btn" data-close>Schlie&szlig;en</button></div>
145
145
  </dialog>
146
146
 
147
+ <!-- Wer im Baum weiterklickt, verlor bis zum 06.09.2026 seine Aenderungen
148
+ ohne ein Wort (Ricardo). Der Text wird zur Laufzeit gesetzt. -->
149
+ <dialog id="dlg-leave">
150
+ <h3 id="leave-titel">Ungespeicherte &Auml;nderungen</h3>
151
+ <div class="dbody"><p id="leave-body"></p></div>
152
+ <div class="dfoot">
153
+ <button class="btn" id="btn-leave-stay">Hierbleiben</button>
154
+ <button class="btn primary" id="btn-leave-go">Verwerfen und wechseln</button>
155
+ </div>
156
+ </dialog>
157
+
147
158
  </body>
148
159
  </html>
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "alias-workbench",
4
- "version": "0.8.0",
4
+ "version": "0.8.2",
5
5
  "news": {
6
+ "0.8.2": {
7
+ "en": "A second alias on the same source is now announced instead of created silently — with a button to move the existing one instead. Switching to a template that finds nothing now works. Roles the workbench adapted are marked and can be taken back. Escape discards in every field of the target bar. New adapter icon.",
8
+ "de": "Ein zweiter Alias auf dieselbe Quelle wird jetzt angesagt statt stillschweigend angelegt — daneben ein Knopf, der stattdessen den vorhandenen verlegt. Der Wechsel auf eine Vorlage, die nichts findet, greift jetzt. Angepasste Rollen sind markiert und lassen sich zurückholen. Escape verwirft in jedem Feld des Zielbalkens. Neues Adaptersymbol.",
9
+ "ru": "Второй алиас на тот же источник теперь объявляется, а не создаётся молча — рядом кнопка, чтобы вместо этого перенести существующий. Переключение на шаблон, который ничего не находит, теперь работает. Изменённые роли помечаются и могут быть возвращены. Escape отменяет ввод в каждом поле целевой строки. Новый значок адаптера.",
10
+ "pt": "Um segundo alias sobre a mesma fonte passa a ser anunciado em vez de criado em silêncio — com um botão para mover o existente. A mudança para um modelo que não encontra nada passa a funcionar. Os papéis adaptados são assinalados e podem ser revertidos. Escape descarta em todos os campos da barra de destino. Novo ícone do adaptador.",
11
+ "nl": "Een tweede alias op dezelfde bron wordt nu gemeld in plaats van stil aangemaakt — met een knop om de bestaande te verplaatsen. Overschakelen naar een sjabloon dat niets vindt werkt nu. Aangepaste rollen worden gemarkeerd en kunnen worden teruggehaald. Escape verwerpt in elk veld van de doelbalk. Nieuw adapterpictogram.",
12
+ "fr": "Un deuxième alias sur la même source est désormais signalé au lieu d’être créé en silence — avec un bouton pour déplacer celui qui existe. Le passage à un modèle qui ne trouve rien fonctionne maintenant. Les rôles adaptés sont marqués et peuvent être repris. Échap annule dans chaque champ de la barre de destination. Nouvelle icône d’adaptateur.",
13
+ "it": "Un secondo alias sulla stessa sorgente viene ora annunciato invece che creato in silenzio — con un pulsante per spostare quello esistente. Il passaggio a un modello che non trova nulla ora funziona. I ruoli adattati sono contrassegnati e si possono ripristinare. Esc annulla in ogni campo della barra di destinazione. Nuova icona dell’adattatore.",
14
+ "es": "Un segundo alias sobre la misma fuente ahora se anuncia en lugar de crearse en silencio — con un botón para mover el existente. Cambiar a una plantilla que no encuentra nada ya funciona. Los roles adaptados se marcan y se pueden recuperar. Escape descarta en cada campo de la barra de destino. Nuevo icono del adaptador.",
15
+ "pl": "Drugi alias na to samo źródło jest teraz zapowiadany, a nie tworzony po cichu — obok przycisk, który zamiast tego przenosi istniejący. Przełączenie na szablon, który nic nie znajduje, teraz działa. Zmienione role są oznaczone i można je cofnąć. Escape odrzuca w każdym polu paska celu. Nowa ikona adaptera.",
16
+ "uk": "Другий аліас на те саме джерело тепер оголошується, а не створюється мовчки — поруч кнопка, щоб натомість перенести наявний. Перемикання на шаблон, який нічого не знаходить, тепер працює. Змінені ролі позначені й їх можна повернути. Escape скасовує в кожному полі цільового рядка. Нова піктограма адаптера.",
17
+ "zh-cn": "同一数据源上的第二个别名现在会有提示,而不是被静默创建——旁边有按钮可改为移动已有的别名。切换到没有匹配项的模板现在也能生效。被调整过的角色会被标记,并可撤回。Escape 在目标栏的每个字段中都会放弃输入。新的适配器图标。"
18
+ },
19
+ "0.8.1": {
20
+ "en": "Published through npm trusted publishing (OIDC) — releases are signed with provenance from now on. No functional changes.",
21
+ "de": "Veröffentlichung über npm Trusted Publishing (OIDC) — Ausgaben sind ab jetzt mit Herkunftsnachweis signiert. Keine funktionalen Änderungen.",
22
+ "ru": "Публикация через npm trusted publishing (OIDC) — выпуски теперь подписаны с подтверждением происхождения. Функциональных изменений нет.",
23
+ "pt": "Publicação através de npm trusted publishing (OIDC) — as versões passam a ser assinadas com proveniência. Sem alterações funcionais.",
24
+ "nl": "Publicatie via npm trusted publishing (OIDC) — releases worden voortaan ondertekend met herkomst. Geen functionele wijzigingen.",
25
+ "fr": "Publication via npm trusted publishing (OIDC) — les versions sont désormais signées avec provenance. Aucun changement fonctionnel.",
26
+ "it": "Pubblicazione tramite npm trusted publishing (OIDC) — le versioni sono ora firmate con provenienza. Nessuna modifica funzionale.",
27
+ "es": "Publicación mediante npm trusted publishing (OIDC): las versiones se firman con procedencia a partir de ahora. Sin cambios funcionales.",
28
+ "pl": "Publikacja przez npm trusted publishing (OIDC) — wydania są od teraz podpisywane z poświadczeniem pochodzenia. Bez zmian funkcjonalnych.",
29
+ "uk": "Публікація через npm trusted publishing (OIDC) — випуски тепер підписані з підтвердженням походження. Функціональних змін немає.",
30
+ "zh-cn": "通过 npm 可信发布(OIDC)发布——此后的版本均带有来源签名。无功能变更。"
31
+ },
6
32
  "0.8.0": {
7
33
  "en": "Ready for the repository: settings page in eleven languages, ESLint and a CI workshop, checks that run before every commit. The buttons at the bottom now follow the mode. New description.",
8
34
  "de": "Bereit fürs Repository: Einstellungsseite in elf Sprachen, ESLint und eine CI-Werkstatt, Prüfungen vor jedem Commit. Die Knöpfe unten richten sich jetzt nach dem Modus. Neue Beschreibung.",
@@ -15,19 +41,6 @@
15
41
  "pl": "Gotowe do repozytorium: strona ustawień w jedenastu językach, ESLint i warsztat CI, sprawdzenia przed każdym commitem. Przyciski na dole podążają teraz za trybem. Nowy opis.",
16
42
  "uk": "Готово до репозиторію: сторінка налаштувань одинадцятьма мовами, ESLint і CI-майстерня, перевірки перед кожним комітом. Кнопки внизу тепер залежать від режиму. Новий опис.",
17
43
  "zh-cn": "已为仓库做好准备:十一种语言的设置页面、ESLint 与 CI 工作流、每次提交前的检查。底部按钮现在会随模式变化。全新的说明文字。"
18
- },
19
- "0.7.12": {
20
- "en": "Tab rebuilt as ES modules; eleven Homematic templates; settings page; swap the source of an alias; picture preview beside room and function.",
21
- "de": "Reiter in ES-Modulen; elf Homematic-Vorlagen; Einstellungsseite; Quelle eines Alias tauschen; Bildvorschau neben Raum und Funktion.",
22
- "ru": "Вкладка перестроена на ES-модули; одиннадцать шаблонов Homematic; страница настроек; замена источника алиаса; предпросмотр значка рядом с комнатой и функцией.",
23
- "pt": "Separador reconstruído como módulos ES; onze modelos Homematic; página de definições; trocar a fonte de um alias; pré-visualização da imagem junto a divisão e função.",
24
- "nl": "Tabblad herbouwd als ES-modules; elf Homematic-sjablonen; instellingenpagina; de bron van een alias wisselen; afbeeldingsvoorbeeld naast ruimte en functie.",
25
- "fr": "Onglet reconstruit en modules ES ; onze modèles Homematic ; page de réglages ; changer la source d'un alias ; aperçu de l'image à côté de la pièce et de la fonction.",
26
- "it": "Scheda ricostruita come moduli ES; undici modelli Homematic; pagina delle impostazioni; sostituzione della sorgente di un alias; anteprima dell’immagine accanto a stanza e funzione.",
27
- "es": "Pestaña reconstruida como módulos ES; once plantillas Homematic; página de ajustes; cambiar la fuente de un alias; vista previa de la imagen junto a la habitación y la función.",
28
- "pl": "Zakładka przebudowana na moduły ES; jedenaście szablonów Homematic; strona ustawień; zamiana źródła aliasu; podgląd obrazka obok pomieszczenia i funkcji.",
29
- "uk": "Вкладку перебудовано на ES-модулі; одинадцять шаблонів Homematic; сторінка налаштувань; заміна джерела аліаса; попередній перегляд зображення поруч із кімнатою та функцією.",
30
- "zh-cn": "选项卡重构为 ES 模块;十一个 Homematic 模板;设置页面;更换别名的来源;房间和功能旁的图片预览。"
31
44
  }
32
45
  },
33
46
  "titleLang": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.alias-workbench",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Creates aliases the comfortable way — devices that vis, Alexa and the device adapter understand. Templates do the work: shipped ones for HomeMatic and Tasmota, your own for everything else. Live values while you build, dry run before anything is written.",
5
5
  "author": {
6
6
  "name": "Ricardo Hipp",
@@ -19,8 +19,7 @@
19
19
  "@iobroker/eslint-config": "^2.3.4",
20
20
  "@iobroker/testing": "^5.3.0",
21
21
  "@iobroker/type-detector": "^6.0.0",
22
- "esbuild": "^0.28.2",
23
- "mocha": "^12.0.0"
22
+ "esbuild": "^0.28.2"
24
23
  },
25
24
  "scripts": {
26
25
  "build": "esbuild src/detector-bundle.js --bundle --minify --format=iife --target=es2019 --outfile=admin/detector.js",