iobroker.alias-workbench 0.8.3 → 0.9.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.
- package/README.md +69 -3
- package/admin/css/werkbank.css +57 -8
- package/admin/detector.js +1 -1
- package/admin/i18n/de.json +13 -0
- package/admin/i18n/en.json +13 -0
- package/admin/js/baum.js +27 -4
- package/admin/js/detail.js +58 -6
- package/admin/js/entwurf.js +140 -4
- package/admin/js/ergebnis.js +118 -31
- package/admin/js/erkennung.js +54 -5
- package/admin/js/musternamen.js +91 -26
- package/admin/js/sprache.js +3 -0
- package/admin/js/vorlagen.js +22 -2
- package/admin/js/vorlagenblatt.js +2 -3
- package/admin/js/zuordnung.js +41 -8
- package/admin/js/zustand.js +7 -1
- package/admin/js/zustandsliste.js +143 -16
- package/admin/tab.html +8 -0
- package/io-package.json +14 -1
- package/package.json +1 -1
package/admin/js/erkennung.js
CHANGED
|
@@ -4,6 +4,55 @@
|
|
|
4
4
|
import { S } from './zustand.js';
|
|
5
5
|
import { D } from './basis.js';
|
|
6
6
|
|
|
7
|
+
/* Der Ausdruck eines Platzes als blosser Text — ohne Schraegstriche.
|
|
8
|
+
|
|
9
|
+
Der Detector liefert seine Muster ueber `ChannelDetector.getPatterns()`
|
|
10
|
+
(so haengt `src/detector-bundle.js` sie an `window`), und diese Funktion
|
|
11
|
+
gibt `role` nicht als RegExp weiter, sondern als `role.toString()` —
|
|
12
|
+
also MIT den Schraegstrichen:
|
|
13
|
+
|
|
14
|
+
/^indicator(\.maintenance)?\.(lowbat|battery)$/
|
|
15
|
+
|
|
16
|
+
Das ist bei uns der Normalfall, nicht die Ausnahme: ein RegExp bekommen
|
|
17
|
+
wir nur, wenn jemand die Muster direkt aus `typePatterns` liest, etwa
|
|
18
|
+
ein Test. Wer bloss `.source` abfragt und sonst `String(r)` nimmt, haelt
|
|
19
|
+
die Schraegstriche fuer Teil des Ausdrucks und findet danach weder den
|
|
20
|
+
Anker `^` noch das Ende `$`.
|
|
21
|
+
|
|
22
|
+
Gefunden am 08.09.2026 am Produktivsystem, und der eine Fehler zog weit:
|
|
23
|
+
`ROLLEN` hatte 210 statt 232 Eintraege (E27) — `indicator.lowbat` fehlte,
|
|
24
|
+
die Rolle, die hm-rpc schreibt —, `BRIGHTNESS` bekam in sechs Mustern gar
|
|
25
|
+
keine Rolle (E6), `plaetzeFuerRollen` fand zu keiner einzigen Rolle einen
|
|
26
|
+
Platz, und die Abweichungskarte behauptete „haette ohnehin nicht
|
|
27
|
+
gezaehlt" ueber Punkte mit Platz. Die Node-Tests sahen nichts davon: sie
|
|
28
|
+
fuettern die Zerlegung mit echten RegExp-Literalen.
|
|
29
|
+
|
|
30
|
+
Deshalb geht seither jede Stelle, die einen Platzausdruck liest, hier
|
|
31
|
+
durch — es sind sieben. */
|
|
32
|
+
export function ausdruckText(r) {
|
|
33
|
+
if (!r) { return ''; }
|
|
34
|
+
if (r.source !== undefined) { return r.source; }
|
|
35
|
+
var q = String(r);
|
|
36
|
+
var m = /^\/(.*)\/[a-z]*$/.exec(q);
|
|
37
|
+
return m ? m[1] : q;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Der Ausdruck als RegExp — fuer alle, die damit pruefen wollen. Wirft
|
|
41
|
+
nicht: ein unbrauchbarer Ausdruck gibt `null`, und der Aufrufer
|
|
42
|
+
entscheidet, was das heisst. */
|
|
43
|
+
export function ausdruckRegExp(r) {
|
|
44
|
+
var q = ausdruckText(r);
|
|
45
|
+
if (!q) { return null; }
|
|
46
|
+
try { return new RegExp(q); } catch { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Passt eine Rolle auf den Ausdruck eines Platzes? */
|
|
50
|
+
export function rolleTrifft(r, rolle) {
|
|
51
|
+
if (!rolle) { return false; }
|
|
52
|
+
var re = ausdruckRegExp(r);
|
|
53
|
+
return re ? re.test(rolle) : false;
|
|
54
|
+
}
|
|
55
|
+
|
|
7
56
|
/* Die Rollen kommen aus dem Paket, nicht aus einer eigenen Liste —
|
|
8
57
|
sonst pflegt man eine zweite Wahrheit, die auseinanderlaeuft. */
|
|
9
58
|
/* Manche Plaetze nennen keine Standardrolle, sondern nur einen
|
|
@@ -13,8 +62,8 @@ import { D } from './basis.js';
|
|
|
13
62
|
liess sich beliebig oft hinzufuegen, und die Rolle fehlte auch in der
|
|
14
63
|
Auswahlliste — der Platz war auf keinem Weg zu befuellen. */
|
|
15
64
|
export function rolleAusAusdruck(r) {
|
|
16
|
-
|
|
17
|
-
|
|
65
|
+
var q = ausdruckText(r);
|
|
66
|
+
if (!q) { return ''; }
|
|
18
67
|
if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return ''; }
|
|
19
68
|
var t = q.slice(1, -1).split(String.fromCharCode(92)).join('');
|
|
20
69
|
return /^[A-Za-z0-9_]+([.][A-Za-z0-9_]+)*$/.test(t) ? t : '';
|
|
@@ -41,8 +90,8 @@ export function rolleVonPlatz(st) { return st.defaultRole || rolleAusAusdruck(st
|
|
|
41
90
|
dann bleibt es bei der Vorgaberolle. Lieber eine Schreibweise zu wenig
|
|
42
91
|
als eine erfundene in der Liste. */
|
|
43
92
|
export function schreibweisenAus(r) {
|
|
44
|
-
|
|
45
|
-
|
|
93
|
+
var q = ausdruckText(r);
|
|
94
|
+
if (!q) { return []; }
|
|
46
95
|
if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return []; }
|
|
47
96
|
q = q.slice(1, -1);
|
|
48
97
|
|
|
@@ -160,7 +209,7 @@ export function plaetzeFuerRollen(musterName) {
|
|
|
160
209
|
ROLLEN.forEach(function (r) {
|
|
161
210
|
mu.states.forEach(function (st) {
|
|
162
211
|
if (raus[r] || !st.role) { return; }
|
|
163
|
-
|
|
212
|
+
if (rolleTrifft(st.role, r)) { raus[r] = st.name; }
|
|
164
213
|
});
|
|
165
214
|
});
|
|
166
215
|
return raus;
|
package/admin/js/musternamen.js
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
|
-
/* Die uebersetzten Namen der Muster, angezapft beim
|
|
1
|
+
/* Die uebersetzten Namen der Muster, angezapft beim Admin.
|
|
2
2
|
|
|
3
|
-
Der type-detector kennt nur rohe Typnamen (socket, thermostat)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
Der type-detector kennt nur rohe Typnamen (socket, thermostat) und
|
|
4
|
+
bringt selbst KEINE Uebersetzung mit - er ist eine Bibliothek
|
|
5
|
+
(`@iobroker/type-detector`), kein Adapter. Uebersetzt werden seine
|
|
6
|
+
Typen nur in den Sprachdateien der Adapter, die ihn benutzen, und
|
|
7
|
+
jeder schleppt seine eigene Kopie mit. Ein Block je Sprache, jeder
|
|
8
|
+
gibt sich am Eintrag "type-Device type" selbst zu erkennen; wir holen
|
|
9
|
+
den der Oberflaechensprache und schlagen darin nach. Eine eigene
|
|
10
|
+
Liste wuerde bei jedem type-detector-Update altern (Ricardos Punkt 13,
|
|
11
|
+
25.08.2026 - Weg 2, wie der Raum/Funktion-Katalog).
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
Gefragt wird ausschliesslich der ADMIN. Bis zum 08.09.2026 war es der
|
|
14
|
+
Geraete-Adapter; Ricardos Begruendung fuer den Wechsel: Der Admin ist
|
|
15
|
+
immer installiert, `devices` nicht, und er wird besser gepflegt. Was
|
|
16
|
+
er nicht uebersetzt, bleibt eben roh - gemessen am 08.09.2026:
|
|
17
|
+
Admin 8.0.11 kennt alle 53 Typen, Admin 7.8.23 nur 44.
|
|
18
|
+
|
|
19
|
+
Der Weg dorthin geht ueber zwei Verzeichnisse, weil keines allein
|
|
20
|
+
reicht: `index.html` nennt bei Admin 8 das richtige Buendel, bei
|
|
21
|
+
Admin 7 aber nur vier Bootstrap-Haeppchen, und die fuehren nicht
|
|
22
|
+
weiter (34 Dateien durchsucht, nichts gefunden). Dort steht die Karte
|
|
23
|
+
in einer Datei, die erst `mf-manifest.json` nennt - der Admin laedt
|
|
24
|
+
seine Oberflaeche ueber Module Federation. Also beide Listen
|
|
25
|
+
zusammen, kurze Dateien zuerst.
|
|
26
|
+
|
|
27
|
+
Bricht die Kette - Aufbau geaendert, Datei zu gross -, bleibt die
|
|
28
|
+
Karte leer und ueberall steht der rohe Name. Kein Fehler, nur weniger
|
|
29
|
+
Komfort. Beide Schreibweisen werden verstanden: Werte in Backticks
|
|
30
|
+
(Admin 8) und als JSON-Strings (Admin 7). */
|
|
16
31
|
|
|
17
32
|
import { sprache } from './sprache.js';
|
|
18
33
|
import { holeText } from './basis.js';
|
|
@@ -44,26 +59,76 @@ function ziehe(src) {
|
|
|
44
59
|
return Object.keys(raus).length ? raus : null;
|
|
45
60
|
}
|
|
46
61
|
|
|
62
|
+
/* Welche Dateien der Admin ueberhaupt ausliefert. Zwei Verzeichnisse,
|
|
63
|
+
zusammengelegt und ohne Doppel - siehe Kopf: keines allein reicht
|
|
64
|
+
fuer beide Admin-Fassungen. */
|
|
65
|
+
function adminDateien() {
|
|
66
|
+
var raus = [];
|
|
67
|
+
function dazu(f) {
|
|
68
|
+
/* Fuehrendes `./` weg, sonst zaehlt dieselbe Datei zweimal: das
|
|
69
|
+
Manifest schreibt `assets/lib-….js`, das index.html
|
|
70
|
+
`./assets/lib-….js`. Ohne dieses Abschneiden stuenden 30 Eintraege
|
|
71
|
+
in einer Liste, die nach 25 abgeschnitten wird - und die gesuchte
|
|
72
|
+
Datei faellt genauso heraus wie damals bei der Sortierung nach
|
|
73
|
+
Namenslaenge. */
|
|
74
|
+
f = f.replace(/^\.\//, '');
|
|
75
|
+
if (raus.indexOf(f) === -1) { raus.push(f); }
|
|
76
|
+
}
|
|
77
|
+
/* Gesucht werden PFADE, keine blossen Dateinamen.
|
|
78
|
+
|
|
79
|
+
Beide Verzeichnisse schreiben den Ordner dazu, wo einer gehoert:
|
|
80
|
+
`"sync":["assets/lib-DSjRYjfP.js"]` im Manifest, `assets/…js` im
|
|
81
|
+
index.html. Wer nur den Dateinamen aufsammelt und stur `assets/`
|
|
82
|
+
davorsetzt, erfindet zwei Pfade, die es nicht gibt: das Manifest
|
|
83
|
+
nennt unter `remoteEntry` und `ssrRemoteEntry` je einen Namen mit
|
|
84
|
+
ausdruecklich leerem `"path":""` - die zwei Dateien liegen unter `/`.
|
|
85
|
+
Das gab beim Laden zwei 404 in der Konsole (A1, gefunden 08.09.2026
|
|
86
|
+
produktiv). Geholfen haben sie ohnehin nie: hinter remoteEntry steckt
|
|
87
|
+
der Einstieg von Module Federation, nicht die Sprachkarte.
|
|
88
|
+
|
|
89
|
+
Der Schraegstrich im Muster ist zugleich die Bedingung - ein nackter
|
|
90
|
+
Name kommt so gar nicht erst in die Liste, und ein Admin, der seine
|
|
91
|
+
Buendel eines Tages woanders ablegt, wird trotzdem gefunden. */
|
|
92
|
+
var pfade = /[A-Za-z0-9_\-.]+\/[A-Za-z0-9_\-./]+\.js/g;
|
|
93
|
+
return holeText('/mf-manifest.json')['catch'](function () { return ''; })
|
|
94
|
+
.then(function (mf) {
|
|
95
|
+
(mf.match(pfade) || []).forEach(dazu);
|
|
96
|
+
return holeText('/index.html')['catch'](function () { return ''; });
|
|
97
|
+
})
|
|
98
|
+
.then(function (html) {
|
|
99
|
+
(html.match(pfade) || []).forEach(dazu);
|
|
100
|
+
/* Reihenfolge bleibt, wie die Verzeichnisse sie nennen. Erst
|
|
101
|
+
wurde nach Namenslaenge sortiert - uebernommen vom
|
|
102
|
+
Aufzaehlungskatalog, wo es das Riesenbuendel nach hinten
|
|
103
|
+
schiebt. Hier war es genau falsch: der Name der gesuchten Datei
|
|
104
|
+
ist bei Admin 8 mit 143 Zeichen der laengste ueberhaupt, sie
|
|
105
|
+
rutschte ans Ende und fiel aus der Auswahl. Ungeordnet steht
|
|
106
|
+
sie an fuenfter Stelle von 21, bei Admin 7 an vierter von 19 -
|
|
107
|
+
beide Verzeichnisse nennen das Wichtige zuerst (gemessen
|
|
108
|
+
08.09.2026). Das Groessenlimit unten haelt die dicken Buendel
|
|
109
|
+
ohnehin heraus. */
|
|
110
|
+
return raus.slice(0, 25);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
47
114
|
export function ladeMusterNamen(nachher) {
|
|
48
115
|
if (geholt) { return; }
|
|
49
116
|
geholt = true;
|
|
50
|
-
|
|
51
|
-
/* Die tab.html nennt mehrere Bausteine (Bootstrap-Haeppchen,
|
|
52
|
-
Vendor, Hauptbundle). Die Karte steckt im Hauptbundle - das
|
|
53
|
-
heisst in beiden bekannten Fassungen index-<hash>.js, also
|
|
54
|
-
dieses zuerst, die uebrigen als Rueckfall der Reihe nach. */
|
|
55
|
-
var dateien = (html.match(/assets\/[A-Za-z0-9_\-.]+\.js/g) || [])
|
|
56
|
-
.filter(function (x, i, a) { return a.indexOf(x) === i; })
|
|
57
|
-
.sort(function (a, b) {
|
|
58
|
-
return (b.indexOf('assets/index-') === 0 ? 1 : 0)
|
|
59
|
-
- (a.indexOf('assets/index-') === 0 ? 1 : 0);
|
|
60
|
-
})
|
|
61
|
-
.slice(0, 8);
|
|
117
|
+
adminDateien().then(function (dateien) {
|
|
62
118
|
if (!dateien.length) { throw new Error('kein Bundle'); }
|
|
63
119
|
return dateien.reduce(function (kette, f) {
|
|
64
120
|
return kette.then(function (fertig) {
|
|
65
121
|
if (fertig) { return true; }
|
|
66
|
-
return holeText('/
|
|
122
|
+
return holeText('/' + f).then(function (src) {
|
|
123
|
+
/* Die Grenze liegt hoeher als beim Aufzaehlungskatalog (3 MB):
|
|
124
|
+
bei Admin 8 steckt die Karte ausgerechnet im grossen
|
|
125
|
+
GUI-Buendel, gemessen 8,4 MB (08.09.2026). Mit der alten
|
|
126
|
+
Grenze wurde genau diese Datei uebersprungen und produktiv
|
|
127
|
+
blieb jeder Beiname roh. Teuer ist es nicht: derselbe Admin
|
|
128
|
+
hat das Buendel gerade selbst geladen, der zweite Griff
|
|
129
|
+
kommt aus dem Zwischenspeicher des Browsers. Alles darueber
|
|
130
|
+
ist kein Buendel mehr, sondern ein Versehen. */
|
|
131
|
+
if (src.length > 12000000) { return false; }
|
|
67
132
|
var karte = ziehe(src);
|
|
68
133
|
if (karte) { musterNamen = karte; return true; }
|
|
69
134
|
return false;
|
package/admin/js/sprache.js
CHANGED
|
@@ -110,6 +110,9 @@ export function beschrifteHtml() {
|
|
|
110
110
|
setz('#tpl-titel', tr('tpls.title', ''));
|
|
111
111
|
setz('#btn-tpl-save', tr('tpls.saveNew'));
|
|
112
112
|
setz('#mqtt-titel', tr('mq.dialogTitle'));
|
|
113
|
+
/* Der Titel des Info-Dialogs traegt den Musternamen und wird beim
|
|
114
|
+
Oeffnen gesetzt; hier steht nur der Rueckfall ohne Muster. */
|
|
115
|
+
setz('#info-titel', tr('info.dlgTitle', ''));
|
|
113
116
|
setz('#guess-titel', tr('guess.tplTitle'));
|
|
114
117
|
setz('#btn-guess-go', tr('guess.tplGo'));
|
|
115
118
|
setz('#tree .empty', tr('tree.loading'));
|
package/admin/js/vorlagen.js
CHANGED
|
@@ -12,7 +12,7 @@ import { tasmotaBefehle } from './mqtt.js';
|
|
|
12
12
|
import { socket } from './verbindung.js';
|
|
13
13
|
|
|
14
14
|
import { tr, sprachtext } from './sprache.js';
|
|
15
|
-
import { musterVon } from './erkennung.js';
|
|
15
|
+
import { musterVon, rolleTrifft } from './erkennung.js';
|
|
16
16
|
|
|
17
17
|
/* Welche Instanz — der Reiter wird mit ?<instanz>&noFooter geladen. */
|
|
18
18
|
var INSTANZ = (function () {
|
|
@@ -29,6 +29,24 @@ export function zeigeObjektzahl() {
|
|
|
29
29
|
if (z) { z.textContent = tr('app.objectsCount', S.keysSorted.length); }
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/* Wie viele Wertabos laufen gerade? Steht neben der Objektzahl.
|
|
33
|
+
|
|
34
|
+
Nicht aus Neugier: die Abos werden beim Wechsel des Knotens abgemeldet
|
|
35
|
+
und neu gesetzt, und ob dabei wirklich alle wieder verschwinden, sieht
|
|
36
|
+
man sonst nirgends. Bleibt die Zahl beim Durchklicken stabil, stimmt
|
|
37
|
+
es; waechst sie, sammelt sich etwas an (Ricardo, 08.09.2026). */
|
|
38
|
+
export function zeigeAbozahl() {
|
|
39
|
+
var z = $('#meta-abos');
|
|
40
|
+
if (!z) { return; }
|
|
41
|
+
var n = (S.abo || []).length;
|
|
42
|
+
z.textContent = ' \u00b7 ' + (n === 1 ? tr('app.subsCount1') : tr('app.subsCount', n));
|
|
43
|
+
/* Die Muster gehoeren dazu, sonst ist die Zahl ein Raetsel: „2 Abos"
|
|
44
|
+
bei zwanzig sichtbaren Werten liest sich falsch, bis man sieht, dass
|
|
45
|
+
ein Muster einen ganzen Zweig deckt (Ricardo, 08.09.2026). */
|
|
46
|
+
z.title = tr('app.subsHint') + String.fromCharCode(10) +
|
|
47
|
+
(S.abo || []).map(function (mu) { return ' ' + mu; }).join(String.fromCharCode(10));
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
export function setzeMeta() {
|
|
33
51
|
var m = $('#meta');
|
|
34
52
|
if (!m) { return; }
|
|
@@ -41,6 +59,8 @@ export function setzeMeta() {
|
|
|
41
59
|
steht sie neben ihresgleichen; ein eigener Span, damit das Abo sie
|
|
42
60
|
nachfuehren kann, ohne die ganze Zeile neu zu bauen. */
|
|
43
61
|
m.appendChild(el('span', null, tr('app.objectsCount', S.keysSorted.length))).id = 'meta-objekte';
|
|
62
|
+
m.appendChild(el('span', null, '')).id = 'meta-abos';
|
|
63
|
+
zeigeAbozahl();
|
|
44
64
|
if (S.vorlagenUnvollstaendig) {
|
|
45
65
|
var w = el('span', 'chip bad', tr('tpls.loadFailed'));
|
|
46
66
|
w.style.marginLeft = '8px';
|
|
@@ -559,7 +579,7 @@ export function anMusterAnpassen(e, neuTyp) {
|
|
|
559
579
|
var s = null;
|
|
560
580
|
muster.states.forEach(function (st) {
|
|
561
581
|
if (s || !st.role) { return; }
|
|
562
|
-
|
|
582
|
+
if (rolleTrifft(st.role, rolle)) { s = st; } /* eine kaputte Rolle macht die Vorlage nicht passend */
|
|
563
583
|
});
|
|
564
584
|
return s;
|
|
565
585
|
}
|
|
@@ -9,7 +9,7 @@ import { D, $, el, kurz } from './basis.js';
|
|
|
9
9
|
import { tr, sprachtext, sprache } from './sprache.js';
|
|
10
10
|
import { katalogFehlt } from './katalog.js';
|
|
11
11
|
import { funktionsAuswahl, kennungZuFunktion } from './aufzaehlungen.js';
|
|
12
|
-
import { musterVon } from './erkennung.js';
|
|
12
|
+
import { musterVon, rolleTrifft } from './erkennung.js';
|
|
13
13
|
import { musterZeile, musterName } from './musternamen.js';
|
|
14
14
|
import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, INSTANZ_ID } from './vorlagen.js';
|
|
15
15
|
import { kindZustaende, hatPunkt, feldAusFormel } from './werte.js';
|
|
@@ -551,8 +551,7 @@ function rollenBlock(rolle, geraetetyp, setzen) {
|
|
|
551
551
|
if (must) {
|
|
552
552
|
var treffer = null;
|
|
553
553
|
must.states.forEach(function (pp) {
|
|
554
|
-
if (!treffer && pp.role && rolle
|
|
555
|
-
new RegExp(pp.role.source ? pp.role.source : pp.role).test(rolle)) { treffer = pp; }
|
|
554
|
+
if (!treffer && pp.role && rolleTrifft(pp.role, rolle)) { treffer = pp; }
|
|
556
555
|
});
|
|
557
556
|
var hin = el('div', 'sugg');
|
|
558
557
|
if (treffer) {
|
package/admin/js/zuordnung.js
CHANGED
|
@@ -20,7 +20,7 @@ import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js'
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
import { ausgangName } from './vorlagen.js';
|
|
23
|
-
import { musterVon } from './erkennung.js';
|
|
23
|
+
import { musterVon, rolleTrifft } from './erkennung.js';
|
|
24
24
|
|
|
25
25
|
/* Welche Aufzaehlungen aendern sich, wenn dieser Entwurf geschrieben wird?
|
|
26
26
|
|
|
@@ -539,14 +539,47 @@ export function uebernehmeBestand(e) {
|
|
|
539
539
|
Quelle zeigte `thermostat` statt `airCondition`, die Schreibquelle
|
|
540
540
|
war weg, und von neun Punkten blieben zwei angehakt.
|
|
541
541
|
|
|
542
|
-
|
|
543
|
-
sonst
|
|
544
|
-
sich" haette nichts mehr zu melden.
|
|
542
|
+
Auch mit Vorlage. Hier stand `if (e.vorlage) return;`, mit der
|
|
543
|
+
Begruendung, sonst uebertoene der Bestand die Vorlage stillschweigend
|
|
544
|
+
und „aendert sich" haette nichts mehr zu melden. Das war zweimal
|
|
545
|
+
falsch.
|
|
546
|
+
|
|
547
|
+
Erstens ist es nicht mehr stillschweigend: `vorlagenWert` haelt fest,
|
|
548
|
+
was die Vorlage wollte, die Zeile traegt dann „weicht von der Vorlage
|
|
549
|
+
ab", und der ←-Knopf holt jedes Feld einzeln zurueck. Genau so haelt
|
|
550
|
+
es die Zwillingsfunktion `bestandVorrang` seit jeher - sie uebernimmt
|
|
551
|
+
den Bestand auch bei gesetzter Vorlage.
|
|
552
|
+
|
|
553
|
+
Zweitens widersprachen sich damit die beiden Ansichten, und das ist
|
|
554
|
+
der Punkt, den U21 verlangt: derselbe Alias, derselbe Stand. Gemessen
|
|
555
|
+
am 08.09.2026 produktiv an `alias.0.Wohnkueche.Lavalampe`: sieben der
|
|
556
|
+
acht Punkte stimmten (sie liefen ueber `bestandVorrang`), ACTUAL
|
|
557
|
+
nicht. Der laeuft ueber den Umbenennungs-Zweig unten - die Vorlage
|
|
558
|
+
`tasmota-lampe` nennt den Punkt ON_ACTUAL, im Alias heisst er ACTUAL
|
|
559
|
+
- und blieb deshalb auf dem Vorlagenstand stehen: `sensor.light`
|
|
560
|
+
statt `state`, Beschriftung leer statt „Lavalampe ACTUAL". Ein
|
|
561
|
+
Aktualisieren aus der Quellenansicht haette beides ueberschrieben,
|
|
562
|
+
und die Marke nannte im Tooltip nur die Rolle, nicht den Namen.
|
|
563
|
+
|
|
564
|
+
`vorlagenWert` wird nur gesetzt, wo noch keiner steht: fuer die
|
|
565
|
+
Zeilen, deren Name schon passt, hat `bestandVorrang` ihn vorher
|
|
566
|
+
gefuellt - mit dem echten Vorlagenstand. Ihn hier zu ueberschreiben
|
|
567
|
+
hiesse, dem Vorlagenknopf den Wert zu nehmen, den er zurueckholen
|
|
568
|
+
soll. */
|
|
545
569
|
function vomAliasUebernehmen(z, o) {
|
|
546
|
-
if (e.vorlage) { return; }
|
|
547
570
|
var c = o.common || {}, a = c.alias || {}, q2 = aliasQuellen(o);
|
|
548
|
-
if (
|
|
549
|
-
|
|
571
|
+
if (!z.vorlagenWert) {
|
|
572
|
+
z.vorlagenWert = {
|
|
573
|
+
on: z.on, role: z.role, typ: z.typ, unit: z.unit, caption: z.caption,
|
|
574
|
+
f: z.f, fw: z.fw, srcR: z.srcR, srcW: z.srcW, states: z.states
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
/* `!== undefined`, nicht bloss wahr: ein Punkt mit leerer Rolle im
|
|
578
|
+
Alias hat sie bewusst leer, und die Zwillingsfunktion
|
|
579
|
+
`bestandVorrang` fragt genauso. Solange die beiden dieselbe Frage
|
|
580
|
+
stellen, kann der Stand nicht wieder auseinanderlaufen. */
|
|
581
|
+
if (c.role !== undefined) { z.role = c.role; }
|
|
582
|
+
if (c.type !== undefined) { z.typ = c.type; }
|
|
550
583
|
z.unit = c.unit || '';
|
|
551
584
|
z.states = c.states || undefined;
|
|
552
585
|
z.f = (typeof a.read === 'string') ? a.read : '';
|
|
@@ -727,7 +760,7 @@ export function unterschiede(e) {
|
|
|
727
760
|
if (!mu || !rolle) { return false; }
|
|
728
761
|
return mu.states.some(function (st) {
|
|
729
762
|
if (!st.role) { return false; }
|
|
730
|
-
|
|
763
|
+
return rolleTrifft(st.role, rolle);
|
|
731
764
|
});
|
|
732
765
|
}
|
|
733
766
|
/* Was die Vorlage von sich aus vorgeschlagen hat. Ein Punkt, der
|
package/admin/js/zustand.js
CHANGED
|
@@ -23,7 +23,10 @@ export var S = {
|
|
|
23
23
|
baumModusVorher: 'quellen',
|
|
24
24
|
aufgeklappt: {},
|
|
25
25
|
alleKlapp: null,
|
|
26
|
-
|
|
26
|
+
/* Die laufenden Wertabos, als Liste von Mustern. War einmal ein
|
|
27
|
+
einzelnes Muster - seit im Aliasmodus auch die Quellen abonniert
|
|
28
|
+
werden, sind es mehrere (08.09.2026). */
|
|
29
|
+
abo: [],
|
|
27
30
|
trockenAlle: null,
|
|
28
31
|
/* Vergleich im Trockenlauf: Felder geordnet (Vorgabe) oder in der
|
|
29
32
|
Reihenfolge, in der sie wirklich im Speicher stehen. */
|
|
@@ -39,6 +42,9 @@ export var S = {
|
|
|
39
42
|
loeschZiel: null,
|
|
40
43
|
loeschAlleAusgaenge: false,
|
|
41
44
|
verlegeZiel: null,
|
|
45
|
+
/* Einmal registrierter Schliesser fuer die Quellenauswahl am
|
|
46
|
+
Sprungknopf - siehe ergebnis.js. */
|
|
47
|
+
sprungZuhoerer: false,
|
|
42
48
|
VORLAGEN: [],
|
|
43
49
|
vorlagenUnvollstaendig: false,
|
|
44
50
|
fokusOrdnerfeld: false,
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
werden oben bei der Musterwahl gebaut und hier nur eingehaengt. */
|
|
8
8
|
|
|
9
9
|
import { S } from './zustand.js';
|
|
10
|
-
import { el } from './basis.js';
|
|
10
|
+
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, bestandsAbweichung, waehle } from './entwurf.js';
|
|
14
|
+
import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle, quelleHier, quellenVerteilung } 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';
|
|
@@ -98,6 +98,8 @@ export function berechnePlaetze(e, haupt) {
|
|
|
98
98
|
uebernommenen Zeilen hatten im thermostat-Muster keinen Platz und
|
|
99
99
|
wurden abgewaehlt, uebrig blieben zwei. */
|
|
100
100
|
var bestandDa = !!(e.ziel && kindZustaende(e.ziel).length > 0);
|
|
101
|
+
|
|
102
|
+
|
|
101
103
|
if (!e.vorbelegt && !e.vorlage && S.current.indexOf('alias.') !== 0) {
|
|
102
104
|
e.vorbelegt = true;
|
|
103
105
|
if (bestandDa) {
|
|
@@ -133,6 +135,38 @@ export function berechnePlaetze(e, haupt) {
|
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
138
|
+
/* Bezugspunkt der Marken: die Quelle, an der ich gerade STEHE.
|
|
139
|
+
|
|
140
|
+
Markiert wird, was nicht von hier kommt. Erst lag der Bezug beim
|
|
141
|
+
angeklickten Knoten - dann bekamen an einer Sparte (`…Licht.stat`)
|
|
142
|
+
alle Zeilen aus `…Licht.tele` eine Marke, obwohl es dasselbe Geraet
|
|
143
|
+
ist. Danach lag er bei der Hauptquelle - dann waren an
|
|
144
|
+
`…Stromzaehler.stat` genau die Zeilen markiert, die von DORT kamen,
|
|
145
|
+
also die falschen (Ricardo, 08.09.2026).
|
|
146
|
+
|
|
147
|
+
`quelleHier` loest beides: es sucht den Eintrag der Verteilung, zu
|
|
148
|
+
dem dieser Knoten gehoert - Sparten fallen darin schon zusammen. Am
|
|
149
|
+
Alias selbst steht man nirgends; dort gilt die Hauptquelle. Findet
|
|
150
|
+
sich gar kein Bezug (ein Sammelknoten weit ueber den Quellen), wird
|
|
151
|
+
nichts markiert - eine Marke ohne Standpunkt sagt nichts. */
|
|
152
|
+
var istAliasSicht = S.current.indexOf('alias.') === 0;
|
|
153
|
+
var aliasFuerMarke = istAliasSicht
|
|
154
|
+
? S.current
|
|
155
|
+
: (e.ziel && kindZustaende(e.ziel).length ? e.ziel : '');
|
|
156
|
+
/* Am Alias steht man nirgends - dort tragen ALLE Zeilen ihren Tag,
|
|
157
|
+
sobald es mehrere Quellen gibt.
|
|
158
|
+
|
|
159
|
+
Vorher galt auch dort die Mehrheit, und nur die Minderheit war
|
|
160
|
+
markiert. Das ist willkuerlich: bei drei zu drei entscheidet, wessen
|
|
161
|
+
Punkt alphabetisch zuerst kommt, und ein Umbenennen kippt die
|
|
162
|
+
Markierung auf die andere Haelfte (Ricardo, 08.09.2026). Traegt
|
|
163
|
+
jede Zeile ihren Tag, gibt es keine Mehrheit mehr zu bestimmen. */
|
|
164
|
+
var vert = aliasFuerMarke ? quellenVerteilung(aliasFuerMarke) : [];
|
|
165
|
+
var alleTaggen = istAliasSicht && vert.length > 1;
|
|
166
|
+
var hauptQuelle = aliasFuerMarke
|
|
167
|
+
? (istAliasSicht ? '' : (quelleHier(aliasFuerMarke, S.current) || ''))
|
|
168
|
+
: (e.kanal || '');
|
|
169
|
+
|
|
136
170
|
var platzVon = pl.platzVon, platzAnzahl = pl.platzAnzahl;
|
|
137
171
|
|
|
138
172
|
/* --- Gerätekarte ---
|
|
@@ -210,6 +244,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
210
244
|
dazu haengt sich erst danach in die Leiste, weil vorher niemand
|
|
211
245
|
weiss, ob es ueberhaupt eine braucht. */
|
|
212
246
|
var ohnePlatzZahl = 0;
|
|
247
|
+
var ohnePlatzNamen = [];
|
|
213
248
|
var an = e.states.filter(function (s) { return s.on; }).length;
|
|
214
249
|
bar.appendChild(document.createTextNode(tr('list.willBeCreated')));
|
|
215
250
|
bar.appendChild(el('span', 'cnt', tr('list.countOf', an, e.states.length)));
|
|
@@ -325,6 +360,16 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
325
360
|
});
|
|
326
361
|
card.appendChild(cap);
|
|
327
362
|
|
|
363
|
+
/* Platz fuer die Legende. Sie steht zwischen Kopfzeile und erster
|
|
364
|
+
Zeile — nicht mehr oben in der Leiste, wo sie wie eine Marke zum
|
|
365
|
+
Geraet aussah und niemand sie mit der Toenung der Zeilen darunter
|
|
366
|
+
verband (Ricardo, 08.09.2026). Hier ist sie selbst ein Beispiel:
|
|
367
|
+
dieselbe Toenung, dieselbe Breite, unmittelbar ueber dem, was sie
|
|
368
|
+
erklaert. Gefuellt wird sie erst nach dem Zeichnen der Zeilen —
|
|
369
|
+
vorher weiss niemand, ob es etwas zu erklaeren gibt. */
|
|
370
|
+
var legendenPlatz = el('div');
|
|
371
|
+
card.appendChild(legendenPlatz);
|
|
372
|
+
|
|
328
373
|
/* --- Zustandsliste --- */
|
|
329
374
|
e.states.forEach(function (s, i) {
|
|
330
375
|
var pl = platzVon[s.n];
|
|
@@ -337,7 +382,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
337
382
|
einmal als Legende ueber der Liste, und der lange Text haengt als
|
|
338
383
|
Hinweis an der Zeile. */
|
|
339
384
|
var ohnePlatz = (s.on && !unfertig && !pl);
|
|
340
|
-
if (ohnePlatz) { ohnePlatzZahl++; }
|
|
385
|
+
if (ohnePlatz) { ohnePlatzZahl++; ohnePlatzNamen.push(s.n); }
|
|
341
386
|
var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : '') +
|
|
342
387
|
(ohnePlatz ? ' ohneplatz' : ''));
|
|
343
388
|
row.tabIndex = 0;
|
|
@@ -367,10 +412,16 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
367
412
|
row.appendChild(mk);
|
|
368
413
|
|
|
369
414
|
var n3 = el('span', 'nm3', s.n || tr('list.newPoint'));
|
|
370
|
-
/* Die Beschriftung (kuenftiger Anzeigename) steht
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
415
|
+
/* Die Beschriftung (kuenftiger Anzeigename) steht im Hinweis an der
|
|
416
|
+
Kennung, nicht in der Zeile.
|
|
417
|
+
|
|
418
|
+
Sie stand einmal gedaempft dahinter - wie die Namen im Baum, und
|
|
419
|
+
aus demselben Grund: wer sie im Detail tippt, soll eine Wirkung
|
|
420
|
+
sehen. Seit die Zeile auch Marken traegt („aus mqtt-client.0",
|
|
421
|
+
„geaendert", „weicht ab") wurde sie damit zu voll, und Rolle und
|
|
422
|
+
Wert rutschten nach rechts weg (Ricardo, 08.09.2026). Die Kennung
|
|
423
|
+
traegt jetzt den Hinweis; sichtbar bleibt sie im Detail. */
|
|
424
|
+
if (s.caption && s.caption !== s.n) { n3.title = s.caption; }
|
|
374
425
|
if (s.manuell) { n3.appendChild(el('span', 'hand', tr('list.byHand'))); }
|
|
375
426
|
if (s.geaendert) { n3.appendChild(el('span', 'geae', tr('list.changed'))); }
|
|
376
427
|
/* Was die Vorlage anders will, steht an der Zeile - sonst sieht man
|
|
@@ -421,6 +472,41 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
421
472
|
/* Die Marke ist zugleich der Griff: solange sie steht, ist die Zeile
|
|
422
473
|
unbestaetigt und faellt beim Zuruecknehmen. Ein Haken laesst sie
|
|
423
474
|
verschwinden, ein Kreuz nimmt genau diese eine zurueck. */
|
|
475
|
+
/* Der Punkt liest woanders her als der Rest.
|
|
476
|
+
|
|
477
|
+
Bewusst eine Marke und keine Flaechentoenung: die Flaeche ist
|
|
478
|
+
schon vergeben („kein Platz im Muster"), und beides gilt
|
|
479
|
+
unabhaengig voneinander - eine Zeile kann getoent und fremd sein.
|
|
480
|
+
Die Marke traegt nur den Adapter, nicht die ganze Kennung; die
|
|
481
|
+
volle steht im Hinweis. Und sie ist ruhig gehalten: sie meldet
|
|
482
|
+
keinen Fehler, sie sagt, woher der Wert kommt. */
|
|
483
|
+
var vonHier = !!hauptQuelle && s.srcR &&
|
|
484
|
+
(s.srcR === hauptQuelle || s.srcR.indexOf(hauptQuelle + '.') === 0);
|
|
485
|
+
if (s.on && s.srcR && (alleTaggen || (hauptQuelle && !vonHier))) {
|
|
486
|
+
/* Der Tag nennt die QUELLE, nicht den Adapter.
|
|
487
|
+
|
|
488
|
+
Er trug erst nur `mqtt-client.0` - was reichte, solange nur die
|
|
489
|
+
Ausreisser markiert waren. Seit am Alias jede Zeile einen Tag
|
|
490
|
+
traegt, muss er unterscheiden: `alias.0.Solar.Balkon.Einspeisung`
|
|
491
|
+
liest aus zwei mqtt-Quellen, und beide hiessen „aus
|
|
492
|
+
mqtt-client.0" (08.09.2026).
|
|
493
|
+
|
|
494
|
+
Gezeigt werden die letzten zwei Glieder des Quellknotens -
|
|
495
|
+
`Solar_Balkon`, `HM600.ch0`, `Balkon.Einspeisung`. Kurz genug
|
|
496
|
+
fuer die Zeile, lang genug zum Unterscheiden; die volle Kennung
|
|
497
|
+
steht im Hinweis. */
|
|
498
|
+
var qHier = aliasFuerMarke ? (quelleHier(aliasFuerMarke, s.srcR) || '') : '';
|
|
499
|
+
var basis = qHier || s.srcR.slice(0, s.srcR.lastIndexOf('.'));
|
|
500
|
+
var teileQ = basis.split('.');
|
|
501
|
+
var kurzQ = teileQ.slice(Math.max(2, teileQ.length - 2)).join('.') || basis;
|
|
502
|
+
var fq = el('span', 'geae fremdquelle', tr('list.otherSource', kurzQ));
|
|
503
|
+
/* Am Alias ist es eine blosse Herkunftsangabe, an einer Quelle
|
|
504
|
+
ein Unterschied - der Hinweis sagt beides jeweils richtig. */
|
|
505
|
+
fq.title = alleTaggen
|
|
506
|
+
? tr('list.sourceHint', s.srcR)
|
|
507
|
+
: tr('list.otherSourceHint', hauptQuelle, s.srcR);
|
|
508
|
+
n3.appendChild(fq);
|
|
509
|
+
}
|
|
424
510
|
if (s.geraten) {
|
|
425
511
|
n3.appendChild(el('span', 'rat', tr('guess.mark')));
|
|
426
512
|
var jaK = el('span', 'ratknopf', '\u2713');
|
|
@@ -554,16 +640,57 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
|
|
|
554
640
|
});
|
|
555
641
|
card.appendChild(add);
|
|
556
642
|
|
|
557
|
-
/* Die Legende
|
|
558
|
-
|
|
559
|
-
|
|
643
|
+
/* Die Legende — einmal, und nur wenn es etwas zu erklaeren gibt.
|
|
644
|
+
|
|
645
|
+
Zwei Anlaeufe hat sie gebraucht. Zuerst stand sie in der Leiste mit
|
|
646
|
+
einem 14 px grossen Farbkaestchen daneben: die Toenung ist
|
|
647
|
+
absichtlich blass, das Kaestchen war winzig, der Text in --ink-3 —
|
|
648
|
+
zusammen sah man nichts. Dann als hinterlegte Marke in der Leiste:
|
|
649
|
+
sichtbar, aber sie las sich wie eine Aussage ueber das GERAET, und
|
|
650
|
+
dass die getoenten Zeilen darunter gemeint sind, sagte sie nicht
|
|
651
|
+
(Ricardo, 08.09.2026, beide Male).
|
|
652
|
+
|
|
653
|
+
Jetzt steht sie in der Liste selbst, direkt ueber der ersten Zeile,
|
|
654
|
+
in derselben Toenung und ueber dieselbe Breite. Sie erklaert die
|
|
655
|
+
Toenung nicht, sie zeigt sie.
|
|
656
|
+
|
|
657
|
+
Die lange Erklaerung steht hinter einem „i" daneben — sie ersetzt
|
|
658
|
+
die Karte, die frueher unten stand („Gehoert nicht zum <Muster>").
|
|
659
|
+
Zweimal dieselbe Auskunft, einmal oben und einmal weit unten, war
|
|
660
|
+
eine zu viel.
|
|
661
|
+
|
|
662
|
+
Als Tooltip taugte sie nicht: der Kasten wurde ueber zehn Zeilen
|
|
663
|
+
hoch und legte sich ueber die Liste, die man gerade lesen wollte
|
|
664
|
+
(Ricardo, 08.09.2026). Ein Klick auf das „i" oeffnet sie stattdessen
|
|
665
|
+
als Dialog — dort darf sie so lang sein, wie sie ist. */
|
|
560
666
|
if (ohnePlatzZahl) {
|
|
561
|
-
var
|
|
562
|
-
lg
|
|
563
|
-
lg.appendChild(document.createTextNode(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
667
|
+
var mn = musterName(e.want) || e.want || '?';
|
|
668
|
+
var lg = el('div', 'legendenzeile');
|
|
669
|
+
lg.appendChild(document.createTextNode(tr('pattern.noPlaceLegend', mn)));
|
|
670
|
+
|
|
671
|
+
var ib = el('button', 'infoknopf', 'i');
|
|
672
|
+
ib.type = 'button';
|
|
673
|
+
ib.title = tr('info.more');
|
|
674
|
+
ib.setAttribute('aria-label', tr('info.more'));
|
|
675
|
+
ib.addEventListener('click', function (ev) {
|
|
676
|
+
ev.stopPropagation();
|
|
677
|
+
var t = $('#info-titel');
|
|
678
|
+
/* „Gehoert nicht zum Steckdose" waere falsches Deutsch — mit
|
|
679
|
+
„-Muster" dahinter stimmt es fuer jeden Musternamen. */
|
|
680
|
+
if (t) { t.textContent = tr('info.dlgTitle', mn); }
|
|
681
|
+
var b = $('#info-body');
|
|
682
|
+
if (b) {
|
|
683
|
+
b.textContent = '';
|
|
684
|
+
var k = el('div', 'aside w');
|
|
685
|
+
k.appendChild(el('b', null, ohnePlatzNamen.join(', ')));
|
|
686
|
+
k.appendChild(document.createTextNode(' — ' + tr('info.explain', mn)));
|
|
687
|
+
b.appendChild(k);
|
|
688
|
+
}
|
|
689
|
+
var d = $('#dlg-info');
|
|
690
|
+
if (d && !d.open) { d.showModal(); }
|
|
691
|
+
});
|
|
692
|
+
lg.appendChild(ib);
|
|
693
|
+
legendenPlatz.appendChild(lg);
|
|
567
694
|
}
|
|
568
695
|
|
|
569
696
|
host.appendChild(card);
|
package/admin/tab.html
CHANGED
|
@@ -120,6 +120,14 @@
|
|
|
120
120
|
</div>
|
|
121
121
|
</dialog>
|
|
122
122
|
|
|
123
|
+
<dialog id="dlg-info" style="max-width:600px;">
|
|
124
|
+
<h3 id="info-titel">Gehört nicht zum Muster</h3>
|
|
125
|
+
<div class="dbody" id="info-body"></div>
|
|
126
|
+
<div class="dfoot">
|
|
127
|
+
<button class="btn primary" data-close>Schließen</button>
|
|
128
|
+
</div>
|
|
129
|
+
</dialog>
|
|
130
|
+
|
|
123
131
|
<dialog id="dlg-guess" style="max-width:560px;">
|
|
124
132
|
<h3 id="guess-titel">Freie Plätze trotz Vorlage?</h3>
|
|
125
133
|
<div class="dbody" id="guess-body"></div>
|