iobroker.alias-workbench 0.10.0 → 0.12.0

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.
Files changed (54) hide show
  1. package/README.md +58 -98
  2. package/admin/css/werkbank.css +88 -0
  3. package/admin/i18n/de.json +766 -1
  4. package/admin/i18n/en.json +766 -1
  5. package/admin/i18n/es.json +766 -1
  6. package/admin/i18n/fr.json +766 -1
  7. package/admin/i18n/it.json +766 -1
  8. package/admin/i18n/nl.json +766 -1
  9. package/admin/i18n/pl.json +766 -1
  10. package/admin/i18n/pt.json +766 -1
  11. package/admin/i18n/ru.json +766 -1
  12. package/admin/i18n/uk.json +766 -1
  13. package/admin/i18n/zh-cn.json +766 -1
  14. package/admin/js/ablage.js +118 -0
  15. package/admin/js/attrappe.js +749 -0
  16. package/admin/js/baum.js +52 -5
  17. package/admin/js/detail.js +64 -3
  18. package/admin/js/entwurf.js +246 -31
  19. package/admin/js/ergebnis.js +77 -2
  20. package/admin/js/katalog.js +235 -40
  21. package/admin/js/mqtt.js +297 -27
  22. package/admin/js/musternamen.js +70 -26
  23. package/admin/js/objekte.js +2 -0
  24. package/admin/js/quellentausch.js +823 -16
  25. package/admin/js/schreiben.js +3 -2
  26. package/admin/js/sprache.js +11 -8
  27. package/admin/js/vorlagen.js +8 -3
  28. package/admin/js/vorlagenblatt.js +228 -64
  29. package/admin/js/werte.js +294 -5
  30. package/admin/js/zielleiste.js +19 -10
  31. package/admin/js/zuordnung.js +8 -7
  32. package/admin/js/zustand.js +32 -0
  33. package/admin/js/zustandsliste.js +9 -2
  34. package/admin/tab.html +24 -0
  35. package/admin/vorlagen/hm-dimmer.json +32 -8
  36. package/admin/vorlagen/hm-fensterkontakt-hm.json +20 -5
  37. package/admin/vorlagen/hm-fensterkontakt-hmip.json +28 -7
  38. package/admin/vorlagen/hm-heizgruppe.json +38 -11
  39. package/admin/vorlagen/hm-heizkoerper.json +42 -12
  40. package/admin/vorlagen/hm-rollladen.json +28 -7
  41. package/admin/vorlagen/hm-schaltaktor-lampe.json +24 -6
  42. package/admin/vorlagen/hm-schaltaktor-mehrfach.json +32 -8
  43. package/admin/vorlagen/hm-schaltaktor-steckdose.json +24 -6
  44. package/admin/vorlagen/hm-temperatur-feuchte.json +8 -2
  45. package/admin/vorlagen/hm-wandthermostat.json +36 -9
  46. package/admin/vorlagen/messpunkt.json +40 -10
  47. package/admin/vorlagen/tasmota-lampe.json +48 -12
  48. package/admin/vorlagen/tasmota-mehrfach.json +40 -10
  49. package/admin/vorlagen/tasmota-rgb.json +36 -9
  50. package/admin/vorlagen/tasmota-steckdose.json +56 -14
  51. package/io-package.json +41 -28
  52. package/package.json +3 -3
  53. package/admin/sprachen/de.json +0 -685
  54. package/admin/sprachen/en.json +0 -685
@@ -17,6 +17,7 @@ import {
17
17
  waehle,
18
18
  ordnerUnterAlias,
19
19
  kennungtauglich,
20
+ ohneUmlaute,
20
21
  kanalGruppen,
21
22
  teilEntwurf,
22
23
  aufteilBehaelter,
@@ -1448,8 +1449,8 @@ export function zeigeVerlegen(altVorgabe, zielVorgabe) {
1448
1449
  body.appendChild(vorschau);
1449
1450
 
1450
1451
  var saeubern = function (t) {
1451
- return String(t).trim()
1452
- .replace(/[^\w.\- \u00e4\u00f6\u00fc\u00c4\u00d6\u00dc\u00df]/g, '_')
1452
+ return ohneUmlaute(t).trim()
1453
+ .replace(/[^\w.\- ]/g, '_')
1453
1454
  .replace(/\s+/g, '_')
1454
1455
  .replace(/\.{2,}/g, '.')
1455
1456
  .replace(/^\.+|\.+$/g, '');
@@ -11,13 +11,12 @@ import { $ } from './basis.js';
11
11
  export var expertenVorgabe = false;
12
12
 
13
13
  /* ================== Sprachen ==================
14
- Woerterbuch als flache Schluessel-Text-Karte unter admin/sprachen/.
15
- (Nicht admin/i18n/ - dort liegt das Woerterbuch der Einstellungsseite,
16
- das der Admin selbst laedt.)
17
- Neue Sprache = neue Datei, sonst nichts. Faellt eine Uebersetzung
18
- aus, greift Englisch, danach der Schluessel selbst — so bleibt die
19
- Oberflaeche bedienbar statt leer. */
20
- export var SPRACHEN = ['de', 'en'];
14
+ Ein Woerterbuch unter admin/i18n/, dieselben Dateien wie die
15
+ Einstellungsseite. Der Admin laedt sie fuer die Seite, der Reiter
16
+ hier per fetch. Eine Sprache gilt, wenn sie hier steht und die Datei
17
+ da ist. Faellt eine Uebersetzung aus, greift Englisch, danach der
18
+ Schluessel selbst — so bleibt die Oberflaeche bedienbar statt leer. */
19
+ export var SPRACHEN = ['en', 'de', 'ru', 'pt', 'nl', 'fr', 'it', 'es', 'pl', 'uk', 'zh-cn'];
21
20
  var woerter = {};
22
21
  var woerterEn = {};
23
22
  export var sprache = 'en';
@@ -40,7 +39,7 @@ export function ladeSprache(fertig) {
40
39
  var vorgabe = (navigator.language || 'en').slice(0, 2).toLowerCase();
41
40
 
42
41
  function hole(code, ziel, danach) {
43
- fetch('./sprachen/' + code + '.json')
42
+ fetch('./i18n/' + code + '.json')
44
43
  .then(function (r) { return r.ok ? r.json() : {}; })
45
44
  .then(function (d) { Object.keys(d).forEach(function (k) { ziel[k] = d[k]; }); danach(); })
46
45
  .catch(function () { danach(); });
@@ -149,6 +148,10 @@ export function beschrifteHtml() {
149
148
  setz('#res .empty', tr('help.sources'));
150
149
  setz('#btn-dry-write', tr('write.now'));
151
150
  setz('#dlg-checks h3', tr('check.title'));
151
+ setz('#mock-titel', tr('tree.exportMock'));
152
+ setz('#btn-mock-go', tr('mock.save'));
153
+ setz('#mockin-titel', tr('tree.importMock'));
154
+ setz('#btn-mockin-go', tr('mockin.create'));
152
155
  document.querySelectorAll('dialog [data-close]').forEach(function (b) {
153
156
  b.textContent = tr('write.close');
154
157
  });
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { S } from './zustand.js';
8
8
  import { D, $, el } from './basis.js';
9
- import { hatPunkt, jsonVon, feldWert, kindZustaende, feldFormel } from './werte.js';
9
+ import { hatPunkt, jsonVon, feldWert, geraetePunkte, feldFormel } from './werte.js';
10
10
  import { eigenerKanalName, merkeHakenVorgabe, kennungtauglich } from './entwurf.js';
11
11
  import { tasmotaBefehle } from './mqtt.js';
12
12
  import { socket } from './verbindung.js';
@@ -170,7 +170,7 @@ export function instanzenVon(v, kanal) {
170
170
  var erf = (v.erkennung || {}).erforderlich || [];
171
171
  var mitPh = erf.filter(function (e) { return e.indexOf(ph) > -1; });
172
172
 
173
- var kurzNamen = kindZustaende(kanal).map(function (id) { return id.slice(kanal.length + 1); });
173
+ var kurzNamen = geraetePunkte(kanal).map(function (p) { return p.rel; });
174
174
 
175
175
  /* Die Nummern der Ausgaenge muessen nicht als eigene Objekte
176
176
  dastehen. Eine Tasmota meldet POWER1..POWER4 in tele/STATE, lange
@@ -575,7 +575,11 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
575
575
  caption: z.beschriftung || '',
576
576
  dec: z.nachkommastellen === undefined ? undefined : String(z.nachkommastellen),
577
577
  states: z.werteliste || undefined,
578
- manuell: false, ausVorlage: z.name, hinweis: z.hinweis || '', wege: []
578
+ manuell: false, ausVorlage: z.name,
579
+ /* Zeichenkette oder { de, en }. Ganz mitnehmen, nicht aufloesen:
580
+ beim Speichern zurueck in eine Vorlage bleiben beide Sprachen,
581
+ und die Anzeige waehlt ueber sprachtext. */
582
+ hinweis: z.hinweis || '', wege: []
579
583
  });
580
584
  return;
581
585
  }
@@ -650,6 +654,7 @@ export function wendeAn(v, kanal, gruende, instanz, auchOhneTreffer) {
650
654
  states: z.werteliste || undefined,
651
655
  manuell: false,
652
656
  ausVorlage: z.name,
657
+ /* Wie oben: das Sprachobjekt bleibt ganz, sprachtext waehlt. */
653
658
  hinweis: z.hinweis || '',
654
659
  /* Alle Lesewege mitnehmen, nicht nur den, der heute gegriffen hat.
655
660
  Wer aus diesem Geraet eine Vorlage macht, soll die Ersatzquellen
@@ -145,6 +145,124 @@ function vAendern(feld, wert) {
145
145
  zeichneErgebnis();
146
146
  }
147
147
 
148
+ /* Das Feld „Geraetetyp" — ein Suchfeld mit eigener Liste, nicht mehr ein
149
+ <select> mit 52 Eintraegen (C33).
150
+
151
+ Ein <select> laesst sich nicht durchsuchen. Die Beschriftung beginnt
152
+ mit dem deutschen Namen („Steckdose · socket"), also fand die
153
+ Tipp-Navigation des Browsers den Rohnamen nicht: wer `socket` tippte,
154
+ landete nirgends. Die Musterwahl beim Alias kann beides seit jeher;
155
+ hier nicht, und das war der Unterschied, ueber den Ricardo am
156
+ 20.09.2026 gestolpert ist.
157
+
158
+ Dieselbe Bauart wie `musterwahl.js`: Eingabefeld plus eigene Liste,
159
+ kein <datalist> — dessen Fenster malt das Betriebssystem, und in
160
+ eingebetteten Ansichten landet es neben dem Fenster. Ohne Bewertung
161
+ allerdings: hier wird eine Vorlage bearbeitet, es gibt kein Geraet,
162
+ gegen das sich etwas bewerten liesse. */
163
+ function geraetetypFeld(v, eigen) {
164
+ var alle = (D ? Object.keys(D.patterns).sort() : []).map(function (m) {
165
+ /* Der Wert ist der TYP, nicht der Musterschluessel — und beschriftet
166
+ wird ebenfalls nach dem Typ. Fuer die drei Muster, die anders
167
+ heissen als ihr Typ (`blinds`, `mediaPlayer`, `levelSlider`),
168
+ kennt der Admin zum Schluessel keinen Beinamen, und im Feld stand
169
+ am Rollladen deshalb „blinds" statt „Jalousien · blind" (I37,
170
+ 15.09.2026). */
171
+ return D.patterns[m].type || m;
172
+ }).filter(function (t, n, arr) { return arr.indexOf(t) === n; });
173
+
174
+ var wrap = el('div', 'feldwrap');
175
+ var feldEin = el('input', 'tx musterwahl');
176
+ feldEin.type = 'text';
177
+ feldEin.setAttribute('autocomplete', 'off');
178
+ feldEin.disabled = !eigen;
179
+ feldEin.value = v.geraetetyp ? musterZeile(v.geraetetyp) : '';
180
+ feldEin.placeholder = tr('tpls.noType');
181
+ wrap.appendChild(feldEin);
182
+ var liste = el('div', 'vorschlaege musterliste');
183
+ liste.hidden = true;
184
+ wrap.appendChild(liste);
185
+
186
+ var markiert = -1;
187
+ var treffer = [];
188
+
189
+ var setzen = function (typ) {
190
+ liste.hidden = true;
191
+ feldEin.value = typ ? musterZeile(typ) : '';
192
+ vAendern(function (x, w) { x.geraetetyp = w || undefined; }, typ);
193
+ };
194
+
195
+ var malen = function () {
196
+ /* Beim Aufklappen steht der gewaehlte Typ im Feld — das ist kein
197
+ Suchtext, sonst saehe man nur den einen Eintrag und muesste erst
198
+ loeschen, um wechseln zu koennen. */
199
+ var roh = String(feldEin.value || '').trim();
200
+ var such = (roh && roh !== musterZeile(v.geraetetyp)) ? roh.toLowerCase() : '';
201
+ treffer = such
202
+ ? alle.filter(function (t) {
203
+ /* Ueber Rohnamen UND Beinamen: „steckdose" findet socket,
204
+ „socket" auch. */
205
+ return t.toLowerCase().indexOf(such) > -1
206
+ || String(musterName(t) || '').toLowerCase().indexOf(such) > -1;
207
+ })
208
+ : alle.slice();
209
+
210
+ liste.textContent = '';
211
+ /* „kein Typ" gehoert dazu — eine Vorlage ohne Geraetetyp ist
212
+ erlaubt, und ohne diesen Eintrag kaeme man nicht mehr zurueck. */
213
+ if (!such) {
214
+ var z0 = el('div', 'vz' + (v.geraetetyp ? '' : ' gewaehlt'));
215
+ z0.appendChild(el('span', null, tr('tpls.noType')));
216
+ z0.addEventListener('mousedown', function (ev) { ev.preventDefault(); setzen(''); });
217
+ liste.appendChild(z0);
218
+ }
219
+ if (!treffer.length) {
220
+ var leer = el('div', 'vz zurueck');
221
+ leer.appendChild(el('span', null, tr('pattern.noMatch', feldEin.value)));
222
+ liste.appendChild(leer);
223
+ }
224
+ treffer.forEach(function (t, n) {
225
+ var z = el('div', 'vz' + (n === markiert ? ' an' : '') + (t === v.geraetetyp ? ' gewaehlt' : ''));
226
+ z.appendChild(el('span', null, musterZeile(t)));
227
+ /* mousedown statt click: sonst greift der Fokusverlust zuerst und
228
+ die Liste ist weg, bevor der Klick ankommt. */
229
+ z.addEventListener('mousedown', function (ev) { ev.preventDefault(); setzen(t); });
230
+ liste.appendChild(z);
231
+ });
232
+ liste.hidden = false;
233
+ };
234
+
235
+ feldEin.addEventListener('focus', function () { markiert = -1; malen(); });
236
+ feldEin.addEventListener('input', function () { markiert = -1; malen(); });
237
+ feldEin.addEventListener('blur', function () {
238
+ /* Was im Feld steht, ist nur Suchtext — gespeichert wird beim
239
+ Klicken. Beim Verlassen also den gewaehlten Typ zurueckschreiben,
240
+ sonst bliebe ein halbgetippter Rest stehen. */
241
+ setTimeout(function () {
242
+ liste.hidden = true;
243
+ feldEin.value = v.geraetetyp ? musterZeile(v.geraetetyp) : '';
244
+ }, 150);
245
+ });
246
+ feldEin.addEventListener('keydown', function (ev) {
247
+ if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
248
+ ev.preventDefault();
249
+ if (liste.hidden) { malen(); return; }
250
+ markiert += (ev.key === 'ArrowDown' ? 1 : -1);
251
+ if (markiert < 0) { markiert = treffer.length - 1; }
252
+ if (markiert >= treffer.length) { markiert = 0; }
253
+ malen();
254
+ } else if (ev.key === 'Enter') {
255
+ ev.preventDefault();
256
+ if (treffer[markiert]) { setzen(treffer[markiert]); }
257
+ else if (treffer.length === 1) { setzen(treffer[0]); }
258
+ } else if (ev.key === 'Escape') {
259
+ liste.hidden = true;
260
+ feldEin.value = v.geraetetyp ? musterZeile(v.geraetetyp) : '';
261
+ }
262
+ });
263
+ return wrap;
264
+ }
265
+
148
266
  export function zeichneVorlagenBlatt(host) {
149
267
  /* Die Knoepfe unten gehoeren zum Geraet, nicht zur Vorlage. In dieser
150
268
  Sicht waeren sie sonst noch auf das Geraet scharf, das vor dem
@@ -184,6 +302,17 @@ export function zeichneVorlagenBlatt(host) {
184
302
  head.appendChild(links);
185
303
  host.appendChild(head);
186
304
 
305
+ /* Der Kopfhinweis ist { de, en } oder eine Zeichenkette. Dieselbe
306
+ Wahl wie beim Namen: eingestellte Sprache, sonst Englisch. */
307
+ var kopfHinweis = sprachtext(v.hinweis);
308
+ if (kopfHinweis) {
309
+ var khw = el('div', 'hint');
310
+ khw.style.margin = '0 0 11px';
311
+ khw.style.whiteSpace = 'pre-wrap';
312
+ khw.textContent = kopfHinweis;
313
+ host.appendChild(khw);
314
+ }
315
+
187
316
  /* --- Was das Einlesen bewirken wuerde --- */
188
317
  if (vEinlesen) {
189
318
  var iw = el('div', 'aside w');
@@ -294,31 +423,7 @@ export function zeichneVorlagenBlatt(host) {
294
423
 
295
424
  var lt = el('label', 'feld');
296
425
  lt.appendChild(el('span', 'feldlabel', tr('tpls.deviceType')));
297
- var st = el('select', 'tx');
298
- st.disabled = !eigen;
299
- st.appendChild(opt('', tr('tpls.noType')));
300
- /* Wie im Speichern-Dialog: der Wert ist der Typ, nicht der
301
- Musterschluessel. musterwahl.js loest es seit laengerem so. */
302
- (D ? Object.keys(D.patterns).sort() : []).forEach(function (m) {
303
- var typ = D.patterns[m].type || m;
304
- /* Beschriftet wird nach dem TYP, nicht nach dem Musterschluessel.
305
-
306
- Der Wert stand seit dem 11.09.2026 richtig da, die Beschriftung
307
- nicht: `musterZeile(m)` schlug den Schluessel nach, und fuer die
308
- drei Muster, die anders heissen als ihr Typ (`blinds`,
309
- `mediaPlayer`, `levelSlider`), kennt der Admin keinen Beinamen.
310
- Im Feld stand am Rollladen deshalb „blinds" statt „Jalousien ·
311
- blind" - der Musterschluessel, den der Nutzer nirgends sonst
312
- sieht. Die Musterwahl (musterwahl.js) geht seit jeher ueber den
313
- Typ. Gefunden produktiv am 15.09.2026 (I37). */
314
- var o = opt(typ, musterZeile(typ));
315
- if (typ === v.geraetetyp) { o.selected = true; }
316
- st.appendChild(o);
317
- });
318
- st.addEventListener('change', function () {
319
- vAendern(function (x, w) { x.geraetetyp = w || undefined; }, st.value);
320
- });
321
- lt.appendChild(st);
426
+ lt.appendChild(geraetetypFeld(v, eigen));
322
427
  kb.appendChild(lt);
323
428
 
324
429
  /* Wozu das Geraet zaehlt.
@@ -376,6 +481,14 @@ export function zeichneVorlagenBlatt(host) {
376
481
  ek.appendChild(eh);
377
482
  var eb = el('div');
378
483
  eb.style.padding = '9px 12px 11px';
484
+ var erkHinweis = sprachtext((v.erkennung || {}).hinweis);
485
+ if (erkHinweis) {
486
+ var ehw = el('div', 'hint');
487
+ ehw.style.whiteSpace = 'pre-wrap';
488
+ ehw.style.marginBottom = '9px';
489
+ ehw.textContent = erkHinweis;
490
+ eb.appendChild(ehw);
491
+ }
379
492
  var listenFeld = function (label, wert, setzen, hilfe) {
380
493
  var w = el('div');
381
494
  w.style.marginBottom = '9px';
@@ -418,15 +531,25 @@ export function zeichneVorlagenBlatt(host) {
418
531
  var zh = el('div', 'ch');
419
532
  zh.appendChild(el('span', 'typ', tr('tpls.states')));
420
533
  zh.appendChild(el('span', 'chip mut', tr('tpls.statesCount', (v.zustaende || []).length)));
421
- if (eigen) {
422
- zh.appendChild(el('span', 'luecke'));
423
- zh.appendChild(el('span', 'hint', tr('tv.clickRow')));
424
- }
534
+ /* Aufklappen geht bei allen Vorlagen, auch bei den mitgelieferten —
535
+ dort nur zum Ansehen.
536
+
537
+ Vorher war die Zeilenansicht an `eigen` gebunden: wer sehen wollte,
538
+ welche Ersatzquellen oder Formeln eine mitgelieferte Vorlage fuehrt,
539
+ musste erst „Eigene Kopie zum Bearbeiten" druecken und hatte danach
540
+ eine Kopie im Bestand, die er gar nicht wollte. Aufgefallen beim
541
+ Beheben von H9: der vierte Leseweg fuer die IP liess sich in der
542
+ Vorlagendatei ergaenzen, im Reiter aber nur ueber diesen Umweg
543
+ nachsehen (Ricardo, 20.09.2026). */
544
+ zh.appendChild(el('span', 'luecke'));
545
+ zh.appendChild(el('span', 'hint', eigen ? tr('tv.clickRow') : tr('tv.clickRowRead')));
425
546
  zk.appendChild(zh);
426
547
  (v.zustaende || []).forEach(function (z, nr) {
427
548
  var auf = (vOffeneZeile === nr);
428
- var r = el('div', 'slot' + (eigen ? ' vklick' : '') + (auf ? ' offen' : ''));
549
+ var r = el('div', 'slot vklick' + (auf ? ' offen' : ''));
429
550
  r.style.gridTemplateColumns = '1fr 1.1fr 1.2fr 90px 15px';
551
+ var zeilenHinweis = sprachtext(z.hinweis);
552
+ if (zeilenHinweis) { r.title = zeilenHinweis; }
430
553
  r.appendChild(el('span', 'sn', z.name));
431
554
  r.appendChild(el('span', 'rx', z.rolle || '\u2014'));
432
555
  var pw = el('span', 'rx');
@@ -436,23 +559,30 @@ export function zeichneVorlagenBlatt(host) {
436
559
  Statusmarke - als Chip las er sich wie \u201eVorgabe" daneben. */
437
560
  if (z.schreiben) { pw.appendChild(el('span', 'leise', ' \u2192 ' + z.schreiben)); }
438
561
  if (z.absolut) { pw.appendChild(el('span', 'chip warn', tr('tpls.absolute'))); }
562
+ /* Dass eine Zeile Ersatzwege hat, stand hier nicht \u2014 man sah nur den
563
+ ersten (\u201eIP \u00b7 text \u00b7 tele.STATE \u00b7 IPAddress") und haette nie
564
+ vermutet, dass drei weitere dahinterliegen. Die Zahl ist die
565
+ Auskunft, die den Unterschied macht; welche es sind, steht
566
+ aufgeklappt (Ricardo, 20.09.2026). */
567
+ var ersatzZahl = ersatzListe(z).length;
568
+ if (ersatzZahl) {
569
+ pw.appendChild(el('span', 'chip mut', tr('tpls.fallbacks', ersatzZahl)));
570
+ }
439
571
  r.appendChild(pw);
440
572
  /* Dieselben zwei Angaben wie im Speichern-Dialog, gleiche Richtung
441
573
  und dieselben Woerter — vorher stand hier „Pflicht/freiwillig"
442
574
  und dort „Vorgabe / muss da sein", fuer genau dieselbe Sache. */
443
575
  var mm = el('span', 'rx');
444
576
  if (!z.vorgabeAus) { mm.appendChild(el('span', 'chip mut', tr('tpls.colDefault'))); }
445
- if (istPflichtpunkt(v, z)) { mm.appendChild(el('span', 'chip ok', tr('tpls.colRequired'))); }
577
+ if (istPflichtpunkt(z)) { mm.appendChild(el('span', 'chip ok', tr('tpls.colRequired'))); }
446
578
  r.appendChild(mm);
447
- r.appendChild(el('span', 'ca2', eigen ? (auf ? '\u25be' : '\u25b8') : ''));
448
- if (eigen) {
449
- r.addEventListener('click', function () {
450
- vOffeneZeile = auf ? null : nr;
451
- zeichneErgebnis();
452
- });
453
- }
579
+ r.appendChild(el('span', 'ca2', auf ? '\u25be' : '\u25b8'));
580
+ r.addEventListener('click', function () {
581
+ vOffeneZeile = auf ? null : nr;
582
+ zeichneErgebnis();
583
+ });
454
584
  zk.appendChild(r);
455
- if (eigen && auf) { zk.appendChild(vZeilenDetail(z, nr)); }
585
+ if (auf) { zk.appendChild(vZeilenDetail(z, nr, eigen)); }
456
586
  });
457
587
 
458
588
  if (eigen) {
@@ -696,9 +826,28 @@ function anzeigeBlock(hole, setze, platzhalter) {
696
826
 
697
827
  /* Zaehlt dieser Datenpunkt fuer die Erkennung? Nicht aus optional
698
828
  abgelesen, sondern aus der Pflichtliste — die ist die Wahrheit. */
699
- function istPflichtpunkt(v, z) {
700
- var erf = (v.erkennung || {}).erforderlich || [];
701
- return !!z.lesen && erf.indexOf(z.lesen) > -1;
829
+ /* Ist diese Zeile Pflicht? Das steht an der Zeile selbst (`optional`),
830
+ nicht in der Erkennungsliste.
831
+
832
+ Bis zum 02.10.2026 las diese Funktion `erkennung.erforderlich` — also
833
+ die Frage „muss das Geraet diesen Punkt haben, damit die Vorlage
834
+ ueberhaupt greift". Das ist eine andere Frage als „darf diese Zeile
835
+ beim Anwenden fehlen", und beide Antworten gehen in den Vorlagen
836
+ weit auseinander: an der HmIP-Heizgruppe sind SET, ACTUAL, HUMIDITY
837
+ und VALVE Pflicht, in der Erkennungsliste steht aber nur
838
+ `1.SET_POINT_TEMPERATURE` — also stand eine Marke statt vier
839
+ (Ricardo, 02.10.2026). Nachgezaehlt wichen **16 von 17** Vorlagen ab.
840
+
841
+ Teils ist das Absicht: `tasmota-lampe` schreibt eigens, `stat.POWER`
842
+ stehe bewusst nicht in der Erkennungsliste, weil der mqtt-Adapter den
843
+ Punkt erst beim ersten Schalten anlegt — gelesen wird ersatzweise aus
844
+ `tele.STATE`. Pflicht ist die Zeile trotzdem, und ueber den Hauptweg
845
+ war das nie zu sehen.
846
+
847
+ Die Erkennungsbedingungen stehen eigens im Kasten „Erkennung" darueber,
848
+ samt Zaehler; sie brauchen diese Marke nicht. */
849
+ function istPflichtpunkt(z) {
850
+ return !z.optional;
702
851
  }
703
852
 
704
853
  /* lesenSonst darf ein Punktname, ein Objekt oder eine Liste sein — alle
@@ -720,9 +869,16 @@ function schreibeErsatz(z, liste) {
720
869
  z.lesenSonst = (liste.length === 1) ? liste[0] : liste;
721
870
  }
722
871
 
723
- function vZeilenDetail(z, nr) {
872
+ function vZeilenDetail(z, nr, eigen) {
724
873
  var v = vArbeitsstand();
725
874
  var d = el('div', 'detail');
875
+ var zeilenHinweis = sprachtext(z.hinweis);
876
+ if (zeilenHinweis) {
877
+ var hw0 = el('div', 'hint');
878
+ hw0.style.whiteSpace = 'pre-wrap';
879
+ hw0.textContent = zeilenHinweis;
880
+ d.appendChild(hw0);
881
+ }
726
882
 
727
883
  /* Genau der Aufbau des Detailblocks am Geraet: Name, liest aus,
728
884
  schreibt nach, Formel, Schreibformel, Rolle, Anzeige. Nur was ein
@@ -894,27 +1050,20 @@ function vZeilenDetail(z, nr) {
894
1050
  zeile2.appendChild(haken(tr('tpls.colDefault'), !z.vorgabeAus,
895
1051
  function (zz, an) { if (an) { delete zz.vorgabeAus; } else { zz.vorgabeAus = true; } }));
896
1052
 
897
- /* Ein Pflichtpunkt ist zweierlei in einem: er darf beim Anwenden
898
- nicht fehlen (optional) und er entscheidet ueber die Erkennung
899
- (erforderlich). Der Haken haelt beides zusammen — sonst koennten
900
- Liste und Zeile Verschiedenes behaupten. */
901
- zeile2.appendChild(haken(tr('tpls.colRequired'), istPflichtpunkt(v, z), function (zz, an) {
902
- var w = vArbeitsstand();
903
- w.erkennung = w.erkennung || {};
904
- var liste = (w.erkennung.erforderlich || []).slice();
905
- var pfad = zz.lesen;
906
- var i = liste.indexOf(pfad);
907
- if (an) {
908
- delete zz.optional;
909
- if (pfad && i === -1) { liste.push(pfad); }
910
- if (zz.schreiben && liste.indexOf(zz.schreiben) === -1) { liste.push(zz.schreiben); }
911
- } else {
912
- zz.optional = true;
913
- if (i > -1) { liste.splice(i, 1); }
914
- var j = zz.schreiben ? liste.indexOf(zz.schreiben) : -1;
915
- if (j > -1) { liste.splice(j, 1); }
916
- }
917
- w.erkennung.erforderlich = liste;
1053
+ /* Der Haken setzt nur noch `optional` an der Zeile — also genau das,
1054
+ was er behauptet.
1055
+
1056
+ Vorher schrieb er zusaetzlich in `erkennung.erforderlich`, „damit
1057
+ Liste und Zeile nicht Verschiedenes behaupten". Das sind aber zwei
1058
+ verschiedene Fragen: ob ein Geraet einen Punkt haben muss, damit die
1059
+ Vorlage ueberhaupt greift, und ob eine Zeile beim Anwenden fehlen
1060
+ darf. Wer hier hakte, verengte unbemerkt die Erkennung — bei
1061
+ `tasmota-lampe` haette das Anhaken von SET die Vorlage an
1062
+ `stat.POWER` gebunden, das es an einem frisch eingebundenen Geraet
1063
+ noch gar nicht gibt. Die Erkennungsliste wird im Kasten „Erkennung"
1064
+ darueber gepflegt, mit eigenem Feld. */
1065
+ zeile2.appendChild(haken(tr('tpls.colRequired'), istPflichtpunkt(z), function (zz, an) {
1066
+ if (an) { delete zz.optional; } else { zz.optional = true; }
918
1067
  }));
919
1068
 
920
1069
  zeile2.appendChild(haken(tr('tpls.absolute'), !!z.absolut,
@@ -929,6 +1078,21 @@ function vZeilenDetail(z, nr) {
929
1078
  var hw = el('div', 'sugg');
930
1079
  hw.textContent = tr('tv.flagsHelp');
931
1080
  dtZeile(d, '', hw);
1081
+
1082
+ /* Eine mitgelieferte Vorlage wird angesehen, nicht geaendert.
1083
+
1084
+ Gesperrt wird am fertigen Block, nicht an jedem der rund dreissig
1085
+ Felder einzeln: so kann keines vergessen werden, wenn spaeter eines
1086
+ dazukommt. Ohne Eingabemoeglichkeit feuert auch kein `change`, also
1087
+ entsteht gar kein Arbeitsstand — wer nur nachsieht, hat hinterher
1088
+ keine ungewollte Kopie im Bestand. */
1089
+ if (!eigen) {
1090
+ Array.prototype.forEach.call(d.querySelectorAll('input, select, textarea, button'),
1091
+ function (x) { x.disabled = true; });
1092
+ var ro = el('div', 'hint');
1093
+ ro.textContent = tr('tv.readOnlyRow');
1094
+ d.insertBefore(ro, d.firstChild);
1095
+ }
932
1096
  return d;
933
1097
  }
934
1098