Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Visualisierung
  4. [Vorlage] MDCSS v2: ical Kalendar anzeigen

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.4k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    2.0k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.6k

[Vorlage] MDCSS v2: ical Kalendar anzeigen

Geplant Angeheftet Gesperrt Verschoben Visualisierung
vismaterial uimaterial css
56 Beiträge 15 Kommentatoren 8.3k Aufrufe 20 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • T Offline
    T Offline
    Tirador
    schrieb am zuletzt editiert von Tirador
    #27

    Bei mir werden dauerhaft noch alte Termine angezeigt:

    Beispiel:
    9f92f3f3-5042-4d54-81dc-355998e2465b-grafik.png

    Kann es daran liegen, dass dies ein Serientermin ist, der bis zum 18.4. (also heute) geht?

    UhulaU 1 Antwort Letzte Antwort
    1
    • T Tirador

      Bei mir werden dauerhaft noch alte Termine angezeigt:

      Beispiel:
      9f92f3f3-5042-4d54-81dc-355998e2465b-grafik.png

      Kann es daran liegen, dass dies ein Serientermin ist, der bis zum 18.4. (also heute) geht?

      UhulaU Offline
      UhulaU Offline
      Uhula
      schrieb am zuletzt editiert von
      #28

      @Tirador Ja, der ical-Adapter holt auch solche Termine, die noch andauern. Mein Script teilt die dann auf die einzelnen Tage auf, ohne Berücksichtigung des aktuellen Tages; was man aber abgrenzen könnte. Erkennen kann man diese Tage daran, dass sie leicht transparent dargestellt werden.

      Uhula - Leise und Weise
      Ex: ioBroker on Gigabyte NUC Proxmox

      T 1 Antwort Letzte Antwort
      0
      • UhulaU Uhula

        @Tirador Ja, der ical-Adapter holt auch solche Termine, die noch andauern. Mein Script teilt die dann auf die einzelnen Tage auf, ohne Berücksichtigung des aktuellen Tages; was man aber abgrenzen könnte. Erkennen kann man diese Tage daran, dass sie leicht transparent dargestellt werden.

        T Offline
        T Offline
        Tirador
        schrieb am zuletzt editiert von
        #29

        @Uhula OK, ich hatte schon den ical Adapter ins Visier genommen. Das Splitting der Termine ist super. Wenn vergangene Termine entfernt werden wäre es optimal.

        1 Antwort Letzte Antwort
        0
        • 2 Online
          2 Online
          2hot4you
          schrieb am zuletzt editiert von 2hot4you
          #30

          Hallo,

          wo/wie kann ich es einstellen das der aktive Tag eine andere Farbe hat ? , bei mir wird der Tag nur etwas heller dargestellt,
          ist aber die gleiche farbe

          ical.PNG

          weiter hin noch ne Frage zu den möglichen "icons", wie bekomme ich die hin, ich werde aus
          dem Skripte nicht schlau, ab Zeile 257 steht

                                     // Farben angegeben werden (diese überschreiben jene aus iCal)
                                     // 'icon'     : 'Icon-Name' (MDCSS/Google WebFont)
                                     // 'calColor' : *#rrggbb' abweichende Kalendarfarbe 
                                     // 'ignore'   : true|false Kalendar komplett ignorieren
                this.CALENDAR        = {'xxGWA'      : {'icon':'room', 'calcolor':'orange' },
                                        'xxGeburstag' : {'icon':'cake'},
                                        'Familie'          : {'ignore':true}
          

          (MDCSS/Google WebFont), verstehe ich nicht

          Gruß Michael

          1 Antwort Letzte Antwort
          0
          • UhulaU Offline
            UhulaU Offline
            Uhula
            schrieb am zuletzt editiert von
            #31

            Update 2020.05.01

            • Anpassung an neues MduiBase (intern)
            • Anpassung an MDCSS 2.5
            • Serientermine vor dem aktuellem Datum werden nicht mehr angezeigt

            Update auf GitHub, siehe 1.Beitrag

            Um ein Feedback zu erhalten wäre es schön, wenn jeder, der diese Vorlagen nutzt, den 1.Beitrag des Themas positiv bewertet (Pfeil nach oben oder unten ;-) ). Ich kann dann auch abschätzen, ob sich die weitere Pflege lohnt. Thx!

            Uhula - Leise und Weise
            Ex: ioBroker on Gigabyte NUC Proxmox

            T 1 Antwort Letzte Antwort
            0
            • UhulaU Uhula

              Update 2020.05.01

              • Anpassung an neues MduiBase (intern)
              • Anpassung an MDCSS 2.5
              • Serientermine vor dem aktuellem Datum werden nicht mehr angezeigt

              Update auf GitHub, siehe 1.Beitrag

              Um ein Feedback zu erhalten wäre es schön, wenn jeder, der diese Vorlagen nutzt, den 1.Beitrag des Themas positiv bewertet (Pfeil nach oben oder unten ;-) ). Ich kann dann auch abschätzen, ob sich die weitere Pflege lohnt. Thx!
              T Offline
              T Offline
              Tirador
              schrieb am zuletzt editiert von
              #32

              @Uhula super, dass mit den Serienterminen. Würde es dir etwas ausmachen, dass Skript etwas zubauen, so dass die Termine von x Tagen auch in einzelne Datenpunkte geschrieben werden. Das könnte man dann auch direkt im MessageHandler anzapfen. Ich würde das Skript erweitern und dir die Version senden.

              D UhulaU 2 Antworten Letzte Antwort
              0
              • T Tirador

                @Uhula super, dass mit den Serienterminen. Würde es dir etwas ausmachen, dass Skript etwas zubauen, so dass die Termine von x Tagen auch in einzelne Datenpunkte geschrieben werden. Das könnte man dann auch direkt im MessageHandler anzapfen. Ich würde das Skript erweitern und dir die Version senden.

                D Offline
                D Offline
                der-eine
                schrieb am zuletzt editiert von
                #33

                @Tirador sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                @Uhula super, dass mit den Serienterminen. Würde es dir etwas ausmachen, dass Skript etwas zubauen, so dass die Termine von x Tagen auch in einzelne Datenpunkte geschrieben werden. Das könnte man dann auch direkt im MessageHandler anzapfen. Ich würde das Skript erweitern und dir die Version senden.

                @Uhula das wäre super wenn Du das umsetzen könntest! :+1:

                1 Antwort Letzte Antwort
                0
                • T Tirador

                  @Uhula super, dass mit den Serienterminen. Würde es dir etwas ausmachen, dass Skript etwas zubauen, so dass die Termine von x Tagen auch in einzelne Datenpunkte geschrieben werden. Das könnte man dann auch direkt im MessageHandler anzapfen. Ich würde das Skript erweitern und dir die Version senden.

                  UhulaU Offline
                  UhulaU Offline
                  Uhula
                  schrieb am zuletzt editiert von
                  #34

                  @Tirador sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                  @Uhula super, dass mit den Serienterminen. Würde es dir etwas ausmachen, dass Skript etwas zubauen, so dass die Termine von x Tagen auch in einzelne Datenpunkte geschrieben werden. Das könnte man dann auch direkt im MessageHandler anzapfen. Ich würde das Skript erweitern und dir die Version senden.

                  Das wäre im Script ja konfigurierbar "fireEvent4MessageHandler" oder so, stört mich nicht.
                  Aber.
                  Das wird nicht viel bringen. Das Script reagiert auf jede Änderung im iCal-JSON und baut die List/Table erneut auf. Es würde dann auch jedesmal wieder alle Termine in deinen MessageHandler feuern. Denn das Script merkt sich ja nicht, für welchen Termin es das schon gemacht hat ...

                  Generell halte ich es auch für besser, wenn dein MessageHandler ohne ein weiteres Aufbereitungs-Script wie dieses hier funktioniert. Dann bist du unabhängiger und universeller. Insofern müsstest du lediglich einen MessageCreator schreiben, der iCal-JSON überwacht, die Serientermine zerpflückt, die Messages feuert und sich die auch merkt.

                  Das Zerpflücken kannst du aus meinem Script übernehmen, ist lediglich eine for-Schleife.

                  Oder habe ich "in einzelne Datenpunkte" nicht korrekt verstanden?

                  Uhula - Leise und Weise
                  Ex: ioBroker on Gigabyte NUC Proxmox

                  T 1 Antwort Letzte Antwort
                  0
                  • UhulaU Uhula

                    @Tirador sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                    @Uhula super, dass mit den Serienterminen. Würde es dir etwas ausmachen, dass Skript etwas zubauen, so dass die Termine von x Tagen auch in einzelne Datenpunkte geschrieben werden. Das könnte man dann auch direkt im MessageHandler anzapfen. Ich würde das Skript erweitern und dir die Version senden.

                    Das wäre im Script ja konfigurierbar "fireEvent4MessageHandler" oder so, stört mich nicht.
                    Aber.
                    Das wird nicht viel bringen. Das Script reagiert auf jede Änderung im iCal-JSON und baut die List/Table erneut auf. Es würde dann auch jedesmal wieder alle Termine in deinen MessageHandler feuern. Denn das Script merkt sich ja nicht, für welchen Termin es das schon gemacht hat ...

                    Generell halte ich es auch für besser, wenn dein MessageHandler ohne ein weiteres Aufbereitungs-Script wie dieses hier funktioniert. Dann bist du unabhängiger und universeller. Insofern müsstest du lediglich einen MessageCreator schreiben, der iCal-JSON überwacht, die Serientermine zerpflückt, die Messages feuert und sich die auch merkt.

                    Das Zerpflücken kannst du aus meinem Script übernehmen, ist lediglich eine for-Schleife.

                    Oder habe ich "in einzelne Datenpunkte" nicht korrekt verstanden?

                    T Offline
                    T Offline
                    Tirador
                    schrieb am zuletzt editiert von
                    #35

                    @Uhula den ical zu zerpflücken würde bedeuten, dass ich den MessageStateCreator erheblich aufrüsten müsste, um weiterhin generisch zu arbeiten. Selbst wenn ich Optionen integriere um json zu verarbeiten fehlt noch viel kalenderlogik. Die Logik aus deinem Skript zu übernehmen führt zur Redundanz. Daher die Idee die Aufbereitung aus deinem Skript direkt in Datenpunkten fortzuschreiben.die mehrfache Auslösung ist grundsätzlich nicht problematisch. Hier würde ich noch eine Option im MessageStateCreator vorsehen, dass der Datenpunkt nur bei Änderungen des Werts neu getriggert wird.

                    1 Antwort Letzte Antwort
                    0
                    • G Offline
                      G Offline
                      G4l4h4d
                      schrieb am zuletzt editiert von
                      #36

                      Ich habe das Script eingefügt und bekomme folgenden Fehler

                      javascript.0	2020-05-09 22:09:19.604	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                      javascript.0	2020-05-09 22:09:19.604	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                      javascript.0	2020-05-09 22:09:19.603	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                      javascript.0	2020-05-09 22:09:19.602	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                      javascript.0	2020-05-09 22:09:19.601	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                      

                      Es wird in den Objekten auch kein Eintag in Userdata angelegt?

                      UhulaU 1 Antwort Letzte Antwort
                      0
                      • G G4l4h4d

                        Ich habe das Script eingefügt und bekomme folgenden Fehler

                        javascript.0	2020-05-09 22:09:19.604	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                        javascript.0	2020-05-09 22:09:19.604	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                        javascript.0	2020-05-09 22:09:19.603	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                        javascript.0	2020-05-09 22:09:19.602	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                        javascript.0	2020-05-09 22:09:19.601	error	(973) Function "setObject" is not allowed. Use adapter settings to allow it.
                        

                        Es wird in den Objekten auch kein Eintag in Userdata angelegt?

                        UhulaU Offline
                        UhulaU Offline
                        Uhula
                        schrieb am zuletzt editiert von
                        #37

                        @G4l4h4d sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                        Es wird in den Objekten auch kein Eintag in Userdata angelegt?

                        Du musst im Admin in den Eigenschaften der Javascript-Instanz Erlaube das Kommando setObject einschalten.

                        010b0b2b-b443-4b35-95b5-6739ce7359dd-image.png

                        Uhula - Leise und Weise
                        Ex: ioBroker on Gigabyte NUC Proxmox

                        G 1 Antwort Letzte Antwort
                        0
                        • UhulaU Uhula

                          @G4l4h4d sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                          Es wird in den Objekten auch kein Eintag in Userdata angelegt?

                          Du musst im Admin in den Eigenschaften der Javascript-Instanz Erlaube das Kommando setObject einschalten.

                          010b0b2b-b443-4b35-95b5-6739ce7359dd-image.png

                          G Offline
                          G Offline
                          G4l4h4d
                          schrieb am zuletzt editiert von
                          #38

                          @Uhula Vielen Dank, wusste gar nicht das es aus ist :-)

                          1 Antwort Letzte Antwort
                          0
                          • ChaotC Offline
                            ChaotC Offline
                            Chaot
                            schrieb am zuletzt editiert von Chaot
                            #39

                            @Uhula
                            Ich bekomme beim zweiten Start die Fehlermeldung:

                            javascript.0	2020-06-12 21:39:26.740	warn	(460) at script.js.Kalender.MDUI-Ical:724:18
                            javascript.0	2020-06-12 21:39:26.740	warn	(460) at MduiShowIcal.start (script.js.Kalender.MDUI-Ical:112:26)
                            javascript.0	2020-06-12 21:39:26.739	warn	(460) at MduiShowIcal.doStart (script.js.Kalender.MDUI-Ical:366:14)
                            javascript.0	2020-06-12 21:39:26.739	warn	(460) at MduiShowIcal.onBuildHTML (script.js.Kalender.MDUI-Ical:608:73)
                            javascript.0	2020-06-12 21:39:26.739	warn	(460) at MduiShowIcal.getState (script.js.Kalender.MDUI-Ical:187:16)
                            javascript.0	2020-06-12 21:39:26.730	warn	(460) getState "0_userdata.0.mdui.showIcal.log0.filter" not found (3)
                            

                            Muss ich noch irgendwas einstellen das ich überlesen habe?

                            Edit:
                            Meldung ist jetzt plötzlich wieder weg.
                            Egal.
                            Danke für das Klasse Script!

                            ioBroker auf NUC unter Proxmox; VIS: 12" Touchscreen und 17" Touch; Lichtsteuerung, Thermometer und Sensoren: Tasmota (39); Ambiente Beleuchtung: WLED (9); Heizung: DECT Thermostate (9) an Fritz 6690; EMS-ESP; 1 Echo V2; 3 Echo DOT; 1 Echo Connect; 2 Echo Show 5; Unifi Ap-Ac Lite.

                            1 Antwort Letzte Antwort
                            0
                            • Lars WalpurgisL Offline
                              Lars WalpurgisL Offline
                              Lars Walpurgis
                              schrieb am zuletzt editiert von Lars Walpurgis
                              #40

                              @Uhula Vielen Dank erstmal für das tolle Script es läuft wunderbar!
                              Ich habe zwei kurze Fragen und zwar:

                              1. Wie kann man anstatt des Datums vorn direkt anzeigen lassen, dass der Termin heute, in 1 Tage... ist? Der ical Adapter liefert das ja schon....!Datum.JPG

                              2. Ich nutze noch die MDCSS V1.8 und vermute, dass deswegen das Kuchen icon (cake im oberen Bild) nicht angezeigt wird, liegt das daran? Kann ich das einfach nach implementieren, oder muss ich die MDCSS Verion hochrüsten? Und wenn ja, habe ich eine Kompatibilität?

                              Danke für eure Antworten!

                              1 Antwort Letzte Antwort
                              0
                              • Holger EtzelH Offline
                                Holger EtzelH Offline
                                Holger Etzel
                                schrieb am zuletzt editiert von Holger Etzel
                                #41

                                Hallo,

                                bekomme beim starten des Scripts folgende Meldung:

                                14:54:45.933 warn javascript.0 (22584) at MduiShowIcal.getState (script.js.Allgemein.Kalenderansicht:187:16)
                                14:54:45.934 warn javascript.0 (22584) at MduiShowIcal.onBuildHTML (script.js.Allgemein.Kalenderansicht:608:73)
                                14:54:45.934 warn javascript.0 (22584) at MduiShowIcal.doStart (script.js.Allgemein.Kalenderansicht:366:14)
                                14:54:45.935 warn javascript.0 (22584) at MduiShowIcal.start (script.js.Allgemein.Kalenderansicht:112:26)
                                14:54:45.936 warn javascript.0 (22584) at script.js.Allgemein.Kalenderansicht:724:18
                                14:54:46.027 info javascript.0 (22584) script.js.Allgemein.Kalenderansicht: [mduiShowIcal] script started
                                14:54:46.027 info javascript.0 (22584) script.js.Allgemein.Kalenderansicht: registered 3 subscriptions and 0 schedules

                                und im log:

                                javascript.0 2020-07-19 15:00:08.698 warn (22584) at process.topLevelDomainCallback (domain.js:126:23)
                                javascript.0 2020-07-19 15:00:08.697 warn (22584) at processImmediate (timers.js:658:5)
                                javascript.0 2020-07-19 15:00:08.696 warn (22584) at tryOnImmediate (timers.js:676:5)
                                javascript.0 2020-07-19 15:00:08.696 warn (22584) at runCallback (timers.js:705:18)
                                javascript.0 2020-07-19 15:00:08.695 warn (22584) at Immediate.setImmediate (/opt/iobroker/node_modules/iobroker.js-controller/lib/adapter.js:5384:37)
                                javascript.0 2020-07-19 15:00:08.695 warn (22584) at Object.stateChange (/opt/iobroker/node_modules/iobroker.javascript/main.js:451:25)
                                javascript.0 2020-07-19 15:00:08.694 warn (22584) at Object.callback (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1052:38)
                                javascript.0 2020-07-19 15:00:08.694 warn (22584) at Object.subscribe.obj (script.js.Allgemein.Kalenderansicht:364:60)
                                javascript.0 2020-07-19 15:00:08.694 warn (22584) at MduiShowIcal.onIcalTable (script.js.Allgemein.Kalenderansicht:391:12)
                                javascript.0 2020-07-19 15:00:08.693 warn (22584) at MduiShowIcal.onBuildHTML (script.js.Allgemein.Kalenderansicht:608:73)
                                javascript.0 2020-07-19 15:00:08.693 warn (22584) at MduiShowIcal.getState (script.js.Allgemein.Kalenderansicht:187:16)
                                javascript.0 2020-07-19 15:00:08.690 warn (22584) getState "0_userdata.0.mdui.showIcal.log0.filter" not found (3)

                                Habe ich noch was vergessen einzustellen?

                                Nach stundenlanger Wartezeit geht es auf einmal.

                                Gruß Holger

                                E 1 Antwort Letzte Antwort
                                0
                                • Holger EtzelH Holger Etzel

                                  Hallo,

                                  bekomme beim starten des Scripts folgende Meldung:

                                  14:54:45.933 warn javascript.0 (22584) at MduiShowIcal.getState (script.js.Allgemein.Kalenderansicht:187:16)
                                  14:54:45.934 warn javascript.0 (22584) at MduiShowIcal.onBuildHTML (script.js.Allgemein.Kalenderansicht:608:73)
                                  14:54:45.934 warn javascript.0 (22584) at MduiShowIcal.doStart (script.js.Allgemein.Kalenderansicht:366:14)
                                  14:54:45.935 warn javascript.0 (22584) at MduiShowIcal.start (script.js.Allgemein.Kalenderansicht:112:26)
                                  14:54:45.936 warn javascript.0 (22584) at script.js.Allgemein.Kalenderansicht:724:18
                                  14:54:46.027 info javascript.0 (22584) script.js.Allgemein.Kalenderansicht: [mduiShowIcal] script started
                                  14:54:46.027 info javascript.0 (22584) script.js.Allgemein.Kalenderansicht: registered 3 subscriptions and 0 schedules

                                  und im log:

                                  javascript.0 2020-07-19 15:00:08.698 warn (22584) at process.topLevelDomainCallback (domain.js:126:23)
                                  javascript.0 2020-07-19 15:00:08.697 warn (22584) at processImmediate (timers.js:658:5)
                                  javascript.0 2020-07-19 15:00:08.696 warn (22584) at tryOnImmediate (timers.js:676:5)
                                  javascript.0 2020-07-19 15:00:08.696 warn (22584) at runCallback (timers.js:705:18)
                                  javascript.0 2020-07-19 15:00:08.695 warn (22584) at Immediate.setImmediate (/opt/iobroker/node_modules/iobroker.js-controller/lib/adapter.js:5384:37)
                                  javascript.0 2020-07-19 15:00:08.695 warn (22584) at Object.stateChange (/opt/iobroker/node_modules/iobroker.javascript/main.js:451:25)
                                  javascript.0 2020-07-19 15:00:08.694 warn (22584) at Object.callback (/opt/iobroker/node_modules/iobroker.javascript/lib/sandbox.js:1052:38)
                                  javascript.0 2020-07-19 15:00:08.694 warn (22584) at Object.subscribe.obj (script.js.Allgemein.Kalenderansicht:364:60)
                                  javascript.0 2020-07-19 15:00:08.694 warn (22584) at MduiShowIcal.onIcalTable (script.js.Allgemein.Kalenderansicht:391:12)
                                  javascript.0 2020-07-19 15:00:08.693 warn (22584) at MduiShowIcal.onBuildHTML (script.js.Allgemein.Kalenderansicht:608:73)
                                  javascript.0 2020-07-19 15:00:08.693 warn (22584) at MduiShowIcal.getState (script.js.Allgemein.Kalenderansicht:187:16)
                                  javascript.0 2020-07-19 15:00:08.690 warn (22584) getState "0_userdata.0.mdui.showIcal.log0.filter" not found (3)

                                  Habe ich noch was vergessen einzustellen?

                                  Nach stundenlanger Wartezeit geht es auf einmal.

                                  Gruß Holger

                                  E Offline
                                  E Offline
                                  Ente
                                  schrieb am zuletzt editiert von
                                  #42

                                  @sigi234

                                  Hallo siggi234,

                                  danke für den Install-Screenshot. ;-)

                                  sigi234S 1 Antwort Letzte Antwort
                                  0
                                  • E Ente

                                    @sigi234

                                    Hallo siggi234,

                                    danke für den Install-Screenshot. ;-)

                                    sigi234S Online
                                    sigi234S Online
                                    sigi234
                                    Forum Testing Most Active
                                    schrieb am zuletzt editiert von sigi234
                                    #43

                                    @Ente sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                                    @sigi234

                                    Hallo siggi234,

                                    danke für den Install-Screenshot. ;-)

                                    Screenshot (674)_LI.jpg

                                    /*
                                    *** MduiShowIcal
                                    Dieses Script überwacht den ical-Adapter und erzeugt bei Änderungen an dessen States HTML-List States für die Anzeige als 
                                    Kalender in Listform mit
                                    * Tagesauflösung von "von/bis"-Terminen, inkl. Berücksichtigung der Uhrzeiten
                                    * Tagesdatum, Kalendarfarben aus ical oder optional aus dem Script
                                    * Termintext und -ort
                                    * optionalem Icon je Kalendar
                                    * optionalem "ignore", wenn ein Kalendar unberücksichtigt bleiben soll
                                    
                                    Es werden States für n-Logs erzeugt, jedem, Log kann ein Standardfilter mitgegeben werden. In jedem Log-Ordner 
                                    befindet sich ein list-HTML State, welcher direkt in der vis angezeigt werden kann (jeweils im basic-string (unescaped) Widget). 
                                    Über optionale Filter als string (Bsp:':Abfall:') oder als RegExp (Bsp:'/Feiertag|Geburtstag/') kann festgelegt werden, 
                                    welche Einträge beim Aufbau der list-HTML States berücksichtigt werden. 
                                    Bsp.: 
                                    log0 Filter: "abfall" oder ":Abfall:" (=Kalendarname) -> Zeigt nur Abfalltermine
                                    log0 Filter: "ferien" oder ":Ferien:" (=Kalendarname) -> Zeigt nur Ferientermine
                                    
                                    
                                    **** Installation
                                    Einfach als serverseitiges Script installieren und starten-5 Sek warten-stoppen-starten. Beim 1.Start werden 
                                    die notwendigen States unter STATE_PATH = '0_userdata.0.mdui.showIcal.' erzeugt. Erst beim 2.Start
                                    instanziiert das Script die Event-Handler und läuft dann.
                                    
                                    **** Konfiguration
                                    Eigentlich ist keine notwendig.
                                    Optional in der Funktion MduiShowIcal|doInit() eine Anpassung der KONFIGURATION vornehmen
                                    Optional Anpassung der tmpList.
                                      
                                    **** Dokumentation
                                    https://github.com/Uhula/ioBroker-Material-Design-Style/wiki/3.5-MduiShowIcal
                                    
                                    ###### Um ein Feedback zu erhalten wäre es schön, wenn jeder, der diese Vorlagen nutzt, den  1.Beitrag des Themas positiv bewertet (Pfeil nach oben oder unten ;-) ). Ich kann dann auch abschätzen, ob sich die weitere Pflege lohnt. Thx!
                                    
                                    ***** States
                                    Unter dem STATE_PATH werden die folgenden States erzeugt:
                                    version : Script-Version, wird verwendet um Script-Updates zu erkennen
                                    updatePressed : auf true setzen, wenn ein table/list update außerhalb des Intervals erfolgen soll
                                    
                                    Weiterhin werden MAX_LOG_FOLDER Unterordner im STATE_PATH erzeugt (N=0-9):
                                    
                                    * LogN.table        : enthält die table-HTML für ein basic-string (unescaped) Widget
                                    * LogN.list         : enthält die list-HTML für ein basic-string (unescaped) Widget
                                    * LogN.count        : Anzahl der Log-Zeilen (wenn das Log mit '/:error:|:warn:/' gefiltert ist, dann ist es die Anzahl der Fehler/Warnungen)
                                    * LogN.filter       : Filter, der auch die logCache angewendet wurde im .table/.list zu erzeugen (siehe Filter)
                                    * LogN.lastUpdate   : Timestamp des letzten Updates
                                    
                                    
                                    ***** Filter
                                    In den filter-States können sowohl strings (Bsp:'error') als auch RegExp-Strings (Bsp:'/warn|error/') 
                                    hinterlegt werden. RegExp-Strings werden an den einschließenden  '/' erkannt. Über den ':' kann der Anfang
                                    eines Feldes mit in den Filter einbezogen werden. 
                                    Beispiele: 
                                    '/Feiertag|Geburtstag/' (RegExp) zeigt alle Zeilen an, in denen 'Feiertag' oder 'Geburtstag' in irgendeinem Feld vorkommen
                                    ':Abfall:' (string) zeigt alle Zeilen an, welche derKalendar 'Abfall' lautet
                                    'Arzt' (string) zeigt alle Zeilen an, in denen 'Arzt' in irgendeinem Feld vorkommt
                                    
                                    **** Lizenz
                                    (c) 2020 by UH, MIT License, no warranty, use on your own risc
                                    
                                    *** Changelog
                                    2020.05.01 UH 
                                    * Anpassung an neues MduiBase (intern)
                                    * Anpassung an MDCSS 2.5
                                    * Serientermine vor dem aktuellem Datum werden nicht mehr angezeigt
                                    
                                    */
                                    
                                    // ------------------------------------------------------------------------------------- 
                                    // MduiBase
                                    // ------------------------------------------------------------------------------------- 
                                    
                                    class MduiBase {
                                    
                                        constructor() {
                                          this.init();
                                        }
                                        
                                        //
                                        init() {
                                            // const
                                            this.DEBUG      = false;
                                            this.VERSION    = '1.0/2020-01-01';
                                            this.NAME       = 'mduiBase';
                                            this.STATE_PATH = '0_userdata.0.mdui.base.';
                                            this.STATE_UNKNOWN    =  0;
                                            this.STATE_INSTALLING = 10;
                                            this.STATE_INSTALLED  = 11;
                                            this.STATE_STARTING   = 20;
                                            this.STATE_STARTED    = 21;
                                            this.STATE_STOPPING   = 30;
                                            this.STATE_STOPPED    = 31;
                                        
                                            // var
                                            this.state = this.STATE_UNKNOWN;
                                            this.states = [];
                                            this.subscribers = [];
                                            this.schedulers = [];
                                        
                                            this.doInit();
                                        
                                            // init der states
                                            this.states.push( { id:'version',     common:{name:'installed script-version', write:false, def:this.VERSION} } );
                                        }
                                        
                                        //
                                        // start the script/class
                                        //
                                        start() {
                                            // beim 1.Start nur die States erzeugen
                                            if ( !this.existState("version") || (this.getState('version').val!=this.VERSION) ) {
                                                for (let s=0; s<this.states.length; s++) { this.createState( this.states[s].id ); }
                                                this.logWarn('first script start, creating states for version '+this.VERSION+', automatic restarting script again in 10 sec ...');
                                                setStateDelayed(this.STATE_PATH + 'version', this.VERSION, 3000);
                                                setTimeout( this.start.bind(this), 10000 );
                                                this.state = this.STATE_INSTALLED; 
                                                return;
                                            }
                                            switch (this.state) {
                                                case this.STATE_UNKNOWN : ;
                                                case this.STATE_INSTALLING : ;
                                                case this.STATE_INSTALLED : ;
                                                case this.STATE_STOPPED : {
                                                    this.state = this.STATE_STARTING; 
                                                    if (this.doStart()) {
                                                        this.log('script started');
                                                        this.state = this.STATE_STARTED;
                                                    }
                                                    break;    
                                                }
                                                case this.STATE_STARTING : ;
                                                case this.STATE_STARTED : {
                                                    this.logWarn('script already starting/started');
                                                    break;    
                                                }
                                                case this.STATE_STOPPING : {
                                                    this.logWarn('script is stopping, pls start later again');
                                                    break;    
                                                }
                                          
                                            } 
                                        }
                                        
                                        //
                                        // stop the script/class
                                        //
                                        stop() {
                                            switch (this.state) {
                                                case this.STATE_STARTED : {
                                                    this.state = this.STATE_STOPPING; 
                                                    if (this.doStop()) {
                                                        for (let i=0; i<this.subscribers.length; i++) if (this.subscribers[i] !== undefined) unsubscribe( this.subscribers[i] );
                                                        this.subscribers = [];
                                                        for (let i=0; i<this.schedulers.length; i++) if (this.schedulers[i] !== undefined) clearSchedule( this.schedulers[i] );
                                                        this.schedulers = [];
                                                        this.state = this.STATE_STOPPED; 
                                                        this.log('script stopped');
                                                    }
                                                    break;    
                                                }
                                                default : {
                                                    this.log('cant stopp script, because not startet');
                                                }
                                            } 
                                        }
                                        
                                        //
                                        // virtual functions, overwrite it 
                                        //
                                        doInit() { return true; }
                                        doStart() { return true; }
                                        doStop() { return true; }
                                        
                                        // einen on-Handler registrieren
                                        subscribe( handler ) {
                                            this.subscribers.push( handler );
                                        }
                                        
                                        // einen timer registrieren
                                        schedule( handler ) {
                                            this.schedulers.push( handler );
                                        }
                                        
                                        //
                                        // tool functions 
                                        //
                                        logDebug(msg) { if (this.DEBUG) console.log('['+this.NAME+'] '+msg); }
                                        log(msg) { console.log('['+this.NAME+'] '+msg); }
                                        logWarn(msg) { console.warn('['+this.NAME+'] '+msg); }
                                        logError(msg) { console.error('['+this.NAME+'] '+msg); }
                                        
                                        // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                                        // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                                        existState(id) {
                                            return ( $(this.STATE_PATH+id).length==0?false:true);
                                        }
                                        
                                        // wrapper, adds statepath to state-ID
                                        getState(id) {
                                            return getState(this.STATE_PATH + id);
                                        }
                                        
                                        // like setState(), but adds statepath to state_ID and checks if state exists, when not, creates it
                                        setState(id,value) {
                                            if ( !this.existState(id) ) this.createState(id,value,undefined);
                                            else setState( this.STATE_PATH + id, value);
                                        }
                                        
                                        // like cresteState(), but adds statepath to state_ID and checks if state exists, when not, creates it
                                        createState(id,value,common) {
                                            if ( !this.existState(id) ) {
                                                if (common===undefined) {
                                                    // id im states-Array suchen
                                                    for (var i=0; i<this.states.length; i++) { 
                                                        if (this.states[i].id==id) {
                                                            if (this.states[i].hasOwnProperty('common'))
                                                                common = this.states[i].common;
                                                           break;
                                                        }   
                                                    }
                                                }
                                                if ( (typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                                // unter "0_userdata.0"
                                                let obj = {};
                                                obj.type = 'state';
                                                obj.native = {};
                                                obj.common = common;
                                                setObject(this.STATE_PATH + id, obj, (err) => {
                                                        if (err) {
                                                            this.log('cant write object for state "' + this.STATE_PATH + id + '": ' + err);
                                                        } else { 
                                                            this.log('state "' + this.STATE_PATH + id + '" created');
                                                        }
                                                });
                                        
                                                setTimeout( setState, 3000, this.STATE_PATH + id, value );
                                            }
                                        }
                                        
                                        // true, if str contains filter string or regexp 
                                        fitsFilter(str, filter) {
                                            if ( (filter===undefined) || !filter || (filter=='') )
                                                return true;
                                            if ( filter instanceof RegExp )  {
                                                if (str.match( filter ) != null) return true;
                                            } else if (typeof filter == 'string') {
                                                if(str.includes(filter)) return true;
                                            }
                                            return false;        
                                        }
                                        
                                        //
                                        escapeRegExp(str) {
                                            return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 
                                        }
                                        
                                        // wandelt eine Farbe im hex-Format (#000000) in ein RGB-Array[2] um
                                        hexToRGB(hex) {
                                            var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                                            return result 
                                                ? [parseInt(result[1],16),parseInt(result[2],16),parseInt(result[3],16)]
                                                : [0,0,0];
                                        };
                                        
                                        // Helligkeit berechnen
                                        getLuminance(r, g, b) {
                                            var a = [r, g, b].map(function (v) {
                                                v /= 255;
                                                return v <= 0.03928
                                                    ? v / 12.92
                                                    : Math.pow( (v + 0.055) / 1.055, 2.4 );
                                            });
                                            return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
                                        }
                                        
                                        // Kontrats berechnen
                                        getContrast(rgb1, rgb2) {
                                            var l1 = this.getLuminance(rgb1[0], rgb1[1], rgb1[2]) + 0.05;
                                            var l2 = this.getLuminance(rgb2[0], rgb2[1], rgb2[2]) + 0.05;
                                            if ( l1 > l2 ) return l1 / l2 
                                            else return l2 / l1;
                                        }
                                        
                                        // liefert die fontColor auf Basis der backgroundColor durch Berechnung
                                        // des Kontrasts
                                        getFontColor(backgroundColor) {
                                            if ( this.getContrast(this.hexToRGB(backgroundColor),this.hexToRGB("#000000")) < 6 ) 
                                                return "#ffffff";
                                            else
                                                return "#000000";
                                        }
                                        
                                        }
                                        
                                        // ------------------------------------------------------------------------------------- 
                                        // MduiLogHMDevices
                                        // ------------------------------------------------------------------------------------- 
                                        
                                        class MduiShowIcal extends MduiBase {
                                        
                                        constructor() {
                                            super();
                                        }
                                        
                                        doInit() {
                                          super.doInit();
                                        
                                          // const
                                          this.DEBUG = false;
                                          this.VERSION = '1.0/2020-03-22';
                                          this.NAME = 'mduiShowIcal';
                                          this.DAY_MILLISECONDS = 60 * 60 * 24 * 1000;
                                        
                                          // -----------------------  
                                          // optional: KONFIGURATION
                                          // -----------------------  
                                                               // state-Pfad unter dem die States angelegt werden  
                                          this.STATE_PATH      = '0_userdata.0.mdui.showIcal.'; 
                                                               // Anzahl der Table/List Ordner mit eigenem Filter/View
                                          this.MAX_LOG_FOLDER  = 3;   
                                                               // max.Anzahl der Zeilen für die Table/List Ausgabe
                                          this.MAX_TABLE_ROWS  = 200; 
                                                               // Objekt-Pfad zum ical.x.data.table
                                          this.ICAL_TABLE      = 'ical.0.data.table';
                                                               // siehe: https://github.com/ioBroker/ioBroker.javascript/blob/master/docs/en/javascript.md#formatdate
                                          this.DATE_FORMAT     = 'W DD.MM.YYYY hh:mm';
                                                               // optional können dem Kalendar noch Icons und abweichende
                                                               // Farben angegeben werden (diese überschreiben jene aus iCal)
                                                               // 'icon'     : 'Icon-Name' (MDCSS/Google WebFont)
                                                               // 'calColor' : *#rrggbb' abweichende Kalendarfarbe 
                                                               // 'ignore'   : true|false Kalendar komplett ignorieren
                                          this.CALENDAR        = {'mdui-Abfall'      : {'icon':'delete_outline', 'calcolor':'orange' },
                                                                  'mdui-Geburtstage' : {'icon':'cake'},
                                                                  'Familie'          : {'ignore':true}
                                                                   
                                                                 };
                                        
                                          // -----------------------  
                                          // ENDE KONFIGURATION
                                          // -----------------------  
                                        
                                          // var
                                          this.logs = [];
                                          for (let i=0; i<=this.MAX_LOG_FOLDER && i<10; i++) 
                                              this.logs.push({filter:'' });
                                          
                                        
                                          // init der states
                                          this.states.push( { id:'version',     common:{name:'installed script-version', write:false, def:this.VERSION} } );
                                          this.states.push( { id:'updatePressed',common:{name:'update button pressed', write:true, type:'boolean', def:'false', role:'button' }} );
                                          
                                          let defFilter;
                                          for (let i=0; i<=this.MAX_LOG_FOLDER && i<10; i++) {
                                              switch (i) {
                                                  case 1 : defFilter = ':Abfall:'; break;   
                                                  case 2 : defFilter = 'Geburtstag'; break;   
                                                  case 3 : defFilter = ''; break;   
                                                  default: defFilter = undefined;
                                              }
                                              this.states.push( { id:'log'+i+'.table',      common:{name:'ioBroker-log as table', write:false, role:'html' }} );
                                              this.states.push( { id:'log'+i+'.list',       common:{name:'ioBroker-log as list', write:false, role:'html' }} );
                                              this.states.push( { id:'log'+i+'.count',      common:{name:'ioBroker-log count', write:false, type:'number', def:'0' }} );
                                              this.states.push( { id:'log'+i+'.filter',     common:{name:'ioBroker-log filter', write:true, def:defFilter}} );
                                              this.states.push( { id:'log'+i+'.lastUpdate', common:{name:'ioBroker-log last update', write:false, def:'0' }} );
                                          }
                                        
                                          return true;  
                                        }
                                        
                                        // start the script/class
                                        doStart() {
                                            super.doStart();
                                            
                                            // subscriber erzeugen
                                            this.subscribe( on( this.STATE_PATH+'updatePressed', obj => { this.onUpdate(obj) } ));
                                            this.subscribe( on( new RegExp( this.STATE_PATH+'*.filter' ), obj => { this.onFilter(obj) } ));
                                            this.subscribe( on( this.ICAL_TABLE, obj => { this.onIcalTable(obj) } ));
                                        
                                            this.onBuildHTML();
                                            return true;
                                        }
                                        
                                        // stop the script/class
                                        doStop() {
                                            super.doStop();
                                            return true;
                                        }
                                        
                                        // 
                                        onUpdate(obj) {
                                            if (obj.state.val===true) {
                                                this.onBuildHTML();
                                            }
                                            this.setState('updatePressed', false);
                                        }
                                        
                                        // filter, sort events
                                        onFilter(obj) {
                                          this.onBuildHTML();
                                        }
                                        
                                        // Ical table hat sich geändert
                                        onIcalTable(obj) {
                                          this.onBuildHTML();
                                        }
                                        
                                        
                                        
                                        // creates the HTML states for every log
                                        /*
                                        'date' => "→ 16.10.2017" 
                                        'event' => "9.30 - 16.30 Uhr Stopka " 
                                        '_class' => "ical_Kerstin ical_today" 
                                        '_date' => "2017-10-16T07:30:00.000Z" 
                                        '_end' => "2017-10-16T14:30:00.000Z" 
                                        '_section' => "" 
                                        '_IDID' => "040000008200E00074C5B7101A82E008000000007645DC55A6DFC44FB2EC6FEA9EFEA33C100000007688316058136F42AD128D91103543C9" 
                                        '_allDay' => "false" 
                                        '_rule' => " " 
                                        '_calName' => "Kerstin" 
                                        
                                        Optionen der ical-Instanz:
                                            'daysPreview' => "7"
                                            'colorize' => "false"
                                            'defColor' => "white"
                                            'fulltime' => " 00:00"
                                            'dataPaddingWithZeros' => "true"
                                            'replaceDates' => "false"
                                            'language' => "de"
                                            'everyCalOneColor' => "true"
                                            'calendars' (array) 
                                                '0' (array) 
                                                    'name' => "Abfall"
                                                    'url' => "https://calendar.google.com/calendar/ical/ruo0ddgalu03qq2ehpm8imqnk4%40group.calendar.google.com/private-a30aac0367f8b50cfd7373f6222d29b4/basic.ics"
                                                    'user' => ""
                                                    'pass' => ""
                                                    'sslignore' => ""
                                                    'color' => ""
                                        */
                                        
                                        isSameDay(d1,d2) {
                                            return (d1.getDate()==d2.getDate()) && (d1.getMonth()==d2.getMonth()) && (d1.getFullYear()==d2.getFullYear());
                                        }
                                        
                                        getWeekNumber( date ) {
                                            let d = new Date(date);
                                            d.setHours(0,0,0);
                                            d.setDate(d.getDate()+4-(d.getDay()||7));
                                            return Math.ceil((((d-new Date(d.getFullYear(),0,1))/8.64e7)+1)/7);
                                        };            
                                        
                                        //
                                        buildEntry( entry ) {
                                            const WEEKDAY_NAMES = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
                                            const MONTH_NAMES = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
                                        
                                            let beginDate = new Date( entry.beginDateISO );
                                            entry.beginTime = beginDate.getTime();
                                            entry.beginWeekDay = beginDate.getDay(); // 0=Sonntag 6=Samstag
                                            entry.beginWeekDayName = WEEKDAY_NAMES[entry.beginWeekDay]; 
                                            entry.beginDay = beginDate.getDate();
                                            entry.beginMonth = beginDate.getMonth();
                                            entry.beginMonthName = MONTH_NAMES[entry.beginMonth]; 
                                            entry.beginYear = beginDate.getFullYear();
                                            entry.beginHour = beginDate.getHours();
                                            entry.beginMinute = beginDate.getMinutes();
                                            entry.beginDate = formatDate(beginDate,  this.DATE_FORMAT);
                                        
                                            let currDate = new Date( entry.currDateISO );
                                            entry.currTime = currDate.getTime();
                                            entry.currWeekDay = currDate.getDay(); // 0=Sonntag 6=Samstag
                                            entry.currWeekDayName = WEEKDAY_NAMES[entry.currWeekDay]; 
                                            entry.currDay = currDate.getDate();
                                            entry.currMonth = currDate.getMonth();
                                            entry.currMonthName = MONTH_NAMES[entry.currMonth]; 
                                            entry.currYear = currDate.getFullYear();
                                            entry.currHour = currDate.getHours();
                                            entry.currMinute = currDate.getMinutes();
                                            entry.currDate = formatDate(currDate,  this.DATE_FORMAT);
                                            entry.currDateOnly = entry.currYear*10000+entry.currMonth*100+entry.currDay;
                                        
                                        
                                            let endDate = new Date( entry.endDateISO );
                                            //allDay Korrektur: liefert immer einen Tag zu viel
                                            if (entry.allDay && endDate.getHours()==0) endDate.setTime( endDate.getTime() - this.DAY_MILLISECONDS);
                                            entry.endTime = endDate.getTime();
                                            entry.endWeekDay = endDate.getDay(); // 0=Sonntag 6=Samstag
                                            entry.endDay = endDate.getDate();
                                            entry.endMonth = endDate.getMonth();
                                            entry.endYear = endDate.getFullYear();
                                            entry.endHour = endDate.getHours();
                                            entry.endMinute = endDate.getMinutes();
                                            entry.endDate = formatDate(endDate, this.DATE_FORMAT);
                                        
                                            // 
                                            if (entry.currWeekDay==0 || entry.currWeekDay==6) entry.dayColor = '#f44336'
                                            else entry.dayColor = 'inherited';
                                        
                                            // 
                                            if (entry.currWeekDay==1) entry.week = this.getWeekNumber(currDate)+'.W';
                                            else entry.week = '';
                                        
                                            // 
                                            if (entry.currTime < Date.now() ) entry.opacity = '.66'
                                            else entry.opacity = '1';
                                        
                                            // Zeitraumangabe berechnen
                                            entry.hint = '';
                                            if ( !this.isSameDay(beginDate, endDate) ) {
                                                    // mehrtägig
                                                    if (entry.allDay)
                                                        entry.timeSpan = 'ganztägig ';
                                                    else if ( this.isSameDay(beginDate, currDate) ) 
                                                            entry.timeSpan = 'ab ' + formatDate(beginDate,'hh:mm');
                                                         else if ( this.isSameDay(endDate, currDate) ) 
                                                                  entry.timeSpan = 'bis ' + formatDate(endDate,'hh:mm');
                                                              else 
                                                                   entry.timeSpan = 'ganztägig ';
                                                } else {
                                                    // an einem Tag
                                                    if (entry.allDay)
                                                        entry.timeSpan = 'ganztägig';
                                                    else if ( (entry.beginHour!=entry.endHour) || (entry.beginMinute!=entry.endMinute)  ) 
                                                             entry.timeSpan = formatDate(beginDate,'hh:mm') + '-' + formatDate(endDate,'hh:mm');
                                                         else   
                                                             entry.timeSpan = formatDate(beginDate,'hh:mm');
                                                }
                                            return entry;        
                                        };
                                        
                                        
                                        onBuildHTML() { try {
                                        
                                            let json = [];  
                                            let calTable = getState(this.ICAL_TABLE);
                                            let inst     = getObject("system.adapter."+this.ICAL_TABLE.substr(0,6));
                                            let instopt  = inst.native;
                                            let calendar = {};
                                            let cal      = {};
                                            let calOptions = {};
                                            let todayDate = new Date();
                                            let today = todayDate.getFullYear()*10000+todayDate.getMonth()*100+todayDate.getDate();
                                        
                                        
                                            for (var i = 0; i < calTable.val.length; i++) { 
                                              cal = calTable.val[i];
                                              let entry = {};
                                              if ( cal._calName != calendar.name ) {
                                                    calendar = {};
                                                    // color suchen
                                                    for (let c = 0; c < instopt.calendars.length; c++ ) {
                                                        if (cal._calName == instopt.calendars[c].name ) {
                                                            calendar = instopt.calendars[c];
                                                            break;
                                                        }
                                                    }
                                                    // options suchen
                                                    if (this.CALENDAR.hasOwnProperty(cal._calName)) calOptions = this.CALENDAR[cal._calName];
                                                    else calOptions={};
                                                }    
                                                if (calOptions.hasOwnProperty('ignore') && calOptions.ignore ) continue;
                                        
                                                if (calOptions.hasOwnProperty('calcolor')) entry.calColor = calOptions.calcolor;
                                                else if (calendar.color !== "") entry.calColor = calendar.color;
                                                     else entry.calColor = '#000000';
                                                entry.color = this.getFontColor( entry.calColor ); 
                                                if (calOptions.hasOwnProperty('icon')) entry.icon = calOptions.icon;
                                                else entry.icon='';
                                        
                                                entry.date = cal.date;
                                                entry.event = cal.event;
                                                entry.calName = cal._calName;
                                                entry.beginDateISO = cal._date;
                                                entry.currDateISO = cal._date;
                                                entry.endDateISO  = cal._end;
                                                entry.allDay  = cal._allDay; 
                                                entry.location = cal.location;
                                        
                                                entry = this.buildEntry( entry);
                                        
                                                if (entry.currDateOnly >= today)
                                                    json.push( entry );
                                        
                                                // Listenansicht
                                                // in calTable steht nur ein Eintrag für jeden Beginn, für die List-Darstellung
                                                // sind diese auch je Tag notwendig - hier jetzt bilden 
                                                if ( (entry.beginDay!=entry.endDay) || (entry.beginMonth!=entry.endMonth) || (entry.beginYear!=entry.endYear) ) {
                                                    let currTime = new Date(entry.beginDateISO).getTime() + this.DAY_MILLISECONDS;
                                                    let endTime = new Date(entry.endDateISO).getTime();
                                                    if (entry.allDay) endTime -= + this.DAY_MILLISECONDS;
                                                    let dayMax=Math.trunc( 2 + (endTime - currTime) /this. DAY_MILLISECONDS); 
                                                    entry.hint += ' (Tag 1/'+dayMax+')';
                                                    let dayCount=2; 
                                                    while (currTime <= endTime && dayCount<100) {
                                                        let newEntry = {};
                                                        Object.assign(newEntry, entry);
                                                        newEntry.currDateISO = new Date(currTime).toISOString();
                                                        newEntry = this.buildEntry( newEntry );
                                                        newEntry.hint += ' (Tag '+dayCount+'/'+dayMax+')';
                                                        if (newEntry.currDateOnly >= today)
                                                            json.push( newEntry );
                                                        currTime = currTime + this.DAY_MILLISECONDS;
                                                        dayCount++;
                                                    }
                                        
                                                }
                                            }
                                        
                                            // sortieren
                                            json.sort( (l,r) => {
                                                      let lv=l['currTime'],rv=r['currTime'];
                                                      return ((lv < rv) ? -1 : (lv > rv) ? 1 : 0);
                                                  } );
                                                
                                          // build table/list HTML
                                          for (let i=0; i<=this.MAX_LOG_FOLDER && i<10; i++) {
                                              let log = this.logs[i];
                                              log.filter = '';
                                              log.ts = 0;
                                              log.idState = 'log'+i;
                                              if (this.existState(log.idState+'.filter')) log.filter = this.getState(log.idState+'.filter').val;
                                              if (this.existState(log.idState+'.lastClear')) log.ts = this.getState(log.idState+'.lastClear').val;
                                        
                                              this.convertJSON2HTML(json, log);
                                          }
                                        } catch(err) { this.logError( 'onBuildHTML: '+err.message ); }  }
                                        
                                        
                                        // color date event calName beginDate endDate allDay
                                        convertJSON2HTML(json, log) {
                                        const tmpTable = {
                                        header : 
                                        `<tr>
                                        <th style="text-align:right;"></th>
                                        <th style="text-align:left;"></th>
                                        <th style="text-align:left;"></th>
                                        <th style="text-align:left;"></th>
                                        <th style="text-align:left;"></th>
                                        <th style="text-align:left; min-width:12em;">Betreff</th>
                                        <th style="text-align:left;">Zeit</th>
                                        <th style="text-align:left;">Ort</th>
                                        <th style="text-align:left;">Kalendar</th>
                                        <th style="text-align:left;"></th>
                                        </tr>`,
                                        row : 
                                        `<tr>
                                        <td style="text-align:right;">
                                           <span style="display:{showDay}; color:{dayColor}; font-size:1.5em; opacity:1; font-weight:bold;">{currDay}</span>
                                        </td>
                                        <td>
                                           <span style="display:{showDay}; font-size:0.8em; margin-top:0.3em; margin-left:4px; opacity:.8;">{currMonthName}</span>
                                        </td>
                                        <td>
                                           <span style="display:{showDay}; font-size:0.8em; margin-top:0.3em; opacity:.8;">{currWeekDayName}</span>
                                        </td>
                                        <td>
                                          <span style="display:inline-block; width:.8em; height:.8em; margin-top:0.3em; background:{calColor}; border-radius:50%;">&nbsp;</span>
                                        </td>
                                        <td>
                                          <i class='material-icons mdui-center {color}' style='font-size:1.2em;'>{icon}</i>
                                        </td>
                                        <td>{event}</td>
                                        <td><span style="font-size:1.0em; opacity:.8;">{timeSpan} {hint}</span></td>
                                        <td>{location}</td>
                                        <td><span style="font-size:0.8em; opacity:.8;color:{calColor};">{calName}</span></td>
                                        <td></td>
                                        </tr>`
                                        }
                                        
                                        const tmpList = {
                                        row : 
                                        `<div class="mdui-listitem" style="font-size:1em; width:100%; display:flex; opacity:{opacity};">
                                          <div style="min-width:3.5em;">
                                            <div style="display:{showDay}; color:{dayColor};">
                                              <span style="font-size:1.5em; opacity:1; font-weight:bold;">{currDay}</span>
                                              <span style="font-size:0.8em; margin:4px; opacity:.8;">{currMonthName}<br/>
                                              {currWeekDayName}</span>
                                            </div>
                                            <div style="display:{showDay}; text-align:right; font-size:0.6em; opacity:.6;">{week}</div>
                                          </div>
                                          <div style="min-width:1.2em;">
                                            <div style="width:.8em; height:.8em;  margin:.1em; text-align:center; background:{calColor}; border-radius:50%;">&nbsp;</div>
                                            <div class="mdui-icon" style="font-size:1.1em; text-align:center; margin-top:0.33em;">{icon}</div>
                                          </div>
                                          <div style="width:100%; margin-left:.25em;">
                                            <div style="">
                                              <div style="font-size:1.1em;">{event}</div>
                                            </div>
                                            <div style="width:100%; display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between;">
                                              <div style="font-size:1.0em; opacity:.8;">
                                                {timeSpan}
                                                <span style="font-size:0.8em;">{hint} {location}</span>
                                              </div>
                                              <div style="font-size:0.8em; opacity:.8;color:{calColor};">{calName}</div>
                                            </div>
                                          </div>
                                        </div>`}
                                            // build htmlTable and htmlList
                                            let htmlTable  = "<table><thead>"+tmpTable.header+"</thead><tbody>";
                                            for (let [key, value] of Object.entries(log)) htmlTable = htmlTable.replace(new RegExp('{'+key+'}','g'),value);
                                           
                                            let htmlList  = "";
                                            let entry, tr;
                                            let count = 0;
                                            // filter as regex?
                                            if ( log.filter!==undefined && typeof log.filter == 'string' && log.filter.startsWith('/') && log.filter.endsWith('/') && (log.filter.length>=2) )  {
                                                log.filter = new RegExp(log.filter.substr(1,log.filter.length-2), 'i');
                                            }
                                        
                                            let lastEntry = {};
                                            for (var i = 0; i < json.length && count<this.MAX_TABLE_ROWS; i++) { 
                                                entry = json[i];
                                                if (this.fitsFilter(':' + entry.currDate + ':' + entry.event +':'+entry.calName + ':' + entry.location + ':',log.filter)) {
                                                    entry.showDay = (lastEntry=={}) || (entry.currDay!=lastEntry.currDay) || (entry.currMonth!=lastEntry.currMonth) || (entry.currYear!=lastEntry.currYear)?'flex':'none';
                                                    lastEntry = entry;
                                                    tr = tmpTable.row;    
                                                    for (let [key, value] of Object.entries(entry)) tr = tr.replace(new RegExp('{'+key+'}','g'),value);
                                                    htmlTable+=tr;
                                                    tr = tmpList.row;    
                                                    for (let [key, value] of Object.entries(entry)) tr = tr.replace(new RegExp('{'+key+'}','g'),value);
                                                    htmlList+=tr;
                                                    count++;
                                                }
                                            }
                                            htmlTable+="</body></table>";    
                                            this.setState(log.idState+'.table', htmlTable);  
                                            this.setState(log.idState+'.list', htmlList);  
                                            this.setState(log.idState+'.count', count);  
                                            this.setState(log.idState+'.lastUpdate', +new Date());  
                                        }
                                        
                                        }
                                        
                                        
                                        // create instance and start
                                        var mduiShowIcal = new MduiShowIcal( );
                                        mduiShowIcal.start();
                                        
                                        // on script stop, stop instance too
                                        onStop(function () { 
                                            mduiShowIcal.stop(); 
                                        }, 1000 );
                                    

                                    Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                                    Immer Daten sichern!

                                    E 1 Antwort Letzte Antwort
                                    0
                                    • sigi234S sigi234

                                      @Ente sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                                      @sigi234

                                      Hallo siggi234,

                                      danke für den Install-Screenshot. ;-)

                                      Screenshot (674)_LI.jpg

                                      /*
                                      *** MduiShowIcal
                                      Dieses Script überwacht den ical-Adapter und erzeugt bei Änderungen an dessen States HTML-List States für die Anzeige als 
                                      Kalender in Listform mit
                                      * Tagesauflösung von "von/bis"-Terminen, inkl. Berücksichtigung der Uhrzeiten
                                      * Tagesdatum, Kalendarfarben aus ical oder optional aus dem Script
                                      * Termintext und -ort
                                      * optionalem Icon je Kalendar
                                      * optionalem "ignore", wenn ein Kalendar unberücksichtigt bleiben soll
                                      
                                      Es werden States für n-Logs erzeugt, jedem, Log kann ein Standardfilter mitgegeben werden. In jedem Log-Ordner 
                                      befindet sich ein list-HTML State, welcher direkt in der vis angezeigt werden kann (jeweils im basic-string (unescaped) Widget). 
                                      Über optionale Filter als string (Bsp:':Abfall:') oder als RegExp (Bsp:'/Feiertag|Geburtstag/') kann festgelegt werden, 
                                      welche Einträge beim Aufbau der list-HTML States berücksichtigt werden. 
                                      Bsp.: 
                                      log0 Filter: "abfall" oder ":Abfall:" (=Kalendarname) -> Zeigt nur Abfalltermine
                                      log0 Filter: "ferien" oder ":Ferien:" (=Kalendarname) -> Zeigt nur Ferientermine
                                      
                                      
                                      **** Installation
                                      Einfach als serverseitiges Script installieren und starten-5 Sek warten-stoppen-starten. Beim 1.Start werden 
                                      die notwendigen States unter STATE_PATH = '0_userdata.0.mdui.showIcal.' erzeugt. Erst beim 2.Start
                                      instanziiert das Script die Event-Handler und läuft dann.
                                      
                                      **** Konfiguration
                                      Eigentlich ist keine notwendig.
                                      Optional in der Funktion MduiShowIcal|doInit() eine Anpassung der KONFIGURATION vornehmen
                                      Optional Anpassung der tmpList.
                                        
                                      **** Dokumentation
                                      https://github.com/Uhula/ioBroker-Material-Design-Style/wiki/3.5-MduiShowIcal
                                      
                                      ###### Um ein Feedback zu erhalten wäre es schön, wenn jeder, der diese Vorlagen nutzt, den  1.Beitrag des Themas positiv bewertet (Pfeil nach oben oder unten ;-) ). Ich kann dann auch abschätzen, ob sich die weitere Pflege lohnt. Thx!
                                      
                                      ***** States
                                      Unter dem STATE_PATH werden die folgenden States erzeugt:
                                      version : Script-Version, wird verwendet um Script-Updates zu erkennen
                                      updatePressed : auf true setzen, wenn ein table/list update außerhalb des Intervals erfolgen soll
                                      
                                      Weiterhin werden MAX_LOG_FOLDER Unterordner im STATE_PATH erzeugt (N=0-9):
                                      
                                      * LogN.table        : enthält die table-HTML für ein basic-string (unescaped) Widget
                                      * LogN.list         : enthält die list-HTML für ein basic-string (unescaped) Widget
                                      * LogN.count        : Anzahl der Log-Zeilen (wenn das Log mit '/:error:|:warn:/' gefiltert ist, dann ist es die Anzahl der Fehler/Warnungen)
                                      * LogN.filter       : Filter, der auch die logCache angewendet wurde im .table/.list zu erzeugen (siehe Filter)
                                      * LogN.lastUpdate   : Timestamp des letzten Updates
                                      
                                      
                                      ***** Filter
                                      In den filter-States können sowohl strings (Bsp:'error') als auch RegExp-Strings (Bsp:'/warn|error/') 
                                      hinterlegt werden. RegExp-Strings werden an den einschließenden  '/' erkannt. Über den ':' kann der Anfang
                                      eines Feldes mit in den Filter einbezogen werden. 
                                      Beispiele: 
                                      '/Feiertag|Geburtstag/' (RegExp) zeigt alle Zeilen an, in denen 'Feiertag' oder 'Geburtstag' in irgendeinem Feld vorkommen
                                      ':Abfall:' (string) zeigt alle Zeilen an, welche derKalendar 'Abfall' lautet
                                      'Arzt' (string) zeigt alle Zeilen an, in denen 'Arzt' in irgendeinem Feld vorkommt
                                      
                                      **** Lizenz
                                      (c) 2020 by UH, MIT License, no warranty, use on your own risc
                                      
                                      *** Changelog
                                      2020.05.01 UH 
                                      * Anpassung an neues MduiBase (intern)
                                      * Anpassung an MDCSS 2.5
                                      * Serientermine vor dem aktuellem Datum werden nicht mehr angezeigt
                                      
                                      */
                                      
                                      // ------------------------------------------------------------------------------------- 
                                      // MduiBase
                                      // ------------------------------------------------------------------------------------- 
                                      
                                      class MduiBase {
                                      
                                          constructor() {
                                            this.init();
                                          }
                                          
                                          //
                                          init() {
                                              // const
                                              this.DEBUG      = false;
                                              this.VERSION    = '1.0/2020-01-01';
                                              this.NAME       = 'mduiBase';
                                              this.STATE_PATH = '0_userdata.0.mdui.base.';
                                              this.STATE_UNKNOWN    =  0;
                                              this.STATE_INSTALLING = 10;
                                              this.STATE_INSTALLED  = 11;
                                              this.STATE_STARTING   = 20;
                                              this.STATE_STARTED    = 21;
                                              this.STATE_STOPPING   = 30;
                                              this.STATE_STOPPED    = 31;
                                          
                                              // var
                                              this.state = this.STATE_UNKNOWN;
                                              this.states = [];
                                              this.subscribers = [];
                                              this.schedulers = [];
                                          
                                              this.doInit();
                                          
                                              // init der states
                                              this.states.push( { id:'version',     common:{name:'installed script-version', write:false, def:this.VERSION} } );
                                          }
                                          
                                          //
                                          // start the script/class
                                          //
                                          start() {
                                              // beim 1.Start nur die States erzeugen
                                              if ( !this.existState("version") || (this.getState('version').val!=this.VERSION) ) {
                                                  for (let s=0; s<this.states.length; s++) { this.createState( this.states[s].id ); }
                                                  this.logWarn('first script start, creating states for version '+this.VERSION+', automatic restarting script again in 10 sec ...');
                                                  setStateDelayed(this.STATE_PATH + 'version', this.VERSION, 3000);
                                                  setTimeout( this.start.bind(this), 10000 );
                                                  this.state = this.STATE_INSTALLED; 
                                                  return;
                                              }
                                              switch (this.state) {
                                                  case this.STATE_UNKNOWN : ;
                                                  case this.STATE_INSTALLING : ;
                                                  case this.STATE_INSTALLED : ;
                                                  case this.STATE_STOPPED : {
                                                      this.state = this.STATE_STARTING; 
                                                      if (this.doStart()) {
                                                          this.log('script started');
                                                          this.state = this.STATE_STARTED;
                                                      }
                                                      break;    
                                                  }
                                                  case this.STATE_STARTING : ;
                                                  case this.STATE_STARTED : {
                                                      this.logWarn('script already starting/started');
                                                      break;    
                                                  }
                                                  case this.STATE_STOPPING : {
                                                      this.logWarn('script is stopping, pls start later again');
                                                      break;    
                                                  }
                                            
                                              } 
                                          }
                                          
                                          //
                                          // stop the script/class
                                          //
                                          stop() {
                                              switch (this.state) {
                                                  case this.STATE_STARTED : {
                                                      this.state = this.STATE_STOPPING; 
                                                      if (this.doStop()) {
                                                          for (let i=0; i<this.subscribers.length; i++) if (this.subscribers[i] !== undefined) unsubscribe( this.subscribers[i] );
                                                          this.subscribers = [];
                                                          for (let i=0; i<this.schedulers.length; i++) if (this.schedulers[i] !== undefined) clearSchedule( this.schedulers[i] );
                                                          this.schedulers = [];
                                                          this.state = this.STATE_STOPPED; 
                                                          this.log('script stopped');
                                                      }
                                                      break;    
                                                  }
                                                  default : {
                                                      this.log('cant stopp script, because not startet');
                                                  }
                                              } 
                                          }
                                          
                                          //
                                          // virtual functions, overwrite it 
                                          //
                                          doInit() { return true; }
                                          doStart() { return true; }
                                          doStop() { return true; }
                                          
                                          // einen on-Handler registrieren
                                          subscribe( handler ) {
                                              this.subscribers.push( handler );
                                          }
                                          
                                          // einen timer registrieren
                                          schedule( handler ) {
                                              this.schedulers.push( handler );
                                          }
                                          
                                          //
                                          // tool functions 
                                          //
                                          logDebug(msg) { if (this.DEBUG) console.log('['+this.NAME+'] '+msg); }
                                          log(msg) { console.log('['+this.NAME+'] '+msg); }
                                          logWarn(msg) { console.warn('['+this.NAME+'] '+msg); }
                                          logError(msg) { console.error('['+this.NAME+'] '+msg); }
                                          
                                          // über den $-Operator nachsehen, ob der state bereits vorhanden ist
                                          // getState().notExists geht auch, erzeugt aber Warnmeldungen!
                                          existState(id) {
                                              return ( $(this.STATE_PATH+id).length==0?false:true);
                                          }
                                          
                                          // wrapper, adds statepath to state-ID
                                          getState(id) {
                                              return getState(this.STATE_PATH + id);
                                          }
                                          
                                          // like setState(), but adds statepath to state_ID and checks if state exists, when not, creates it
                                          setState(id,value) {
                                              if ( !this.existState(id) ) this.createState(id,value,undefined);
                                              else setState( this.STATE_PATH + id, value);
                                          }
                                          
                                          // like cresteState(), but adds statepath to state_ID and checks if state exists, when not, creates it
                                          createState(id,value,common) {
                                              if ( !this.existState(id) ) {
                                                  if (common===undefined) {
                                                      // id im states-Array suchen
                                                      for (var i=0; i<this.states.length; i++) { 
                                                          if (this.states[i].id==id) {
                                                              if (this.states[i].hasOwnProperty('common'))
                                                                  common = this.states[i].common;
                                                             break;
                                                          }   
                                                      }
                                                  }
                                                  if ( (typeof value === 'undefined') && (common.hasOwnProperty('def'))) value = common.def;
                                                  // unter "0_userdata.0"
                                                  let obj = {};
                                                  obj.type = 'state';
                                                  obj.native = {};
                                                  obj.common = common;
                                                  setObject(this.STATE_PATH + id, obj, (err) => {
                                                          if (err) {
                                                              this.log('cant write object for state "' + this.STATE_PATH + id + '": ' + err);
                                                          } else { 
                                                              this.log('state "' + this.STATE_PATH + id + '" created');
                                                          }
                                                  });
                                          
                                                  setTimeout( setState, 3000, this.STATE_PATH + id, value );
                                              }
                                          }
                                          
                                          // true, if str contains filter string or regexp 
                                          fitsFilter(str, filter) {
                                              if ( (filter===undefined) || !filter || (filter=='') )
                                                  return true;
                                              if ( filter instanceof RegExp )  {
                                                  if (str.match( filter ) != null) return true;
                                              } else if (typeof filter == 'string') {
                                                  if(str.includes(filter)) return true;
                                              }
                                              return false;        
                                          }
                                          
                                          //
                                          escapeRegExp(str) {
                                              return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 
                                          }
                                          
                                          // wandelt eine Farbe im hex-Format (#000000) in ein RGB-Array[2] um
                                          hexToRGB(hex) {
                                              var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
                                              return result 
                                                  ? [parseInt(result[1],16),parseInt(result[2],16),parseInt(result[3],16)]
                                                  : [0,0,0];
                                          };
                                          
                                          // Helligkeit berechnen
                                          getLuminance(r, g, b) {
                                              var a = [r, g, b].map(function (v) {
                                                  v /= 255;
                                                  return v <= 0.03928
                                                      ? v / 12.92
                                                      : Math.pow( (v + 0.055) / 1.055, 2.4 );
                                              });
                                              return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
                                          }
                                          
                                          // Kontrats berechnen
                                          getContrast(rgb1, rgb2) {
                                              var l1 = this.getLuminance(rgb1[0], rgb1[1], rgb1[2]) + 0.05;
                                              var l2 = this.getLuminance(rgb2[0], rgb2[1], rgb2[2]) + 0.05;
                                              if ( l1 > l2 ) return l1 / l2 
                                              else return l2 / l1;
                                          }
                                          
                                          // liefert die fontColor auf Basis der backgroundColor durch Berechnung
                                          // des Kontrasts
                                          getFontColor(backgroundColor) {
                                              if ( this.getContrast(this.hexToRGB(backgroundColor),this.hexToRGB("#000000")) < 6 ) 
                                                  return "#ffffff";
                                              else
                                                  return "#000000";
                                          }
                                          
                                          }
                                          
                                          // ------------------------------------------------------------------------------------- 
                                          // MduiLogHMDevices
                                          // ------------------------------------------------------------------------------------- 
                                          
                                          class MduiShowIcal extends MduiBase {
                                          
                                          constructor() {
                                              super();
                                          }
                                          
                                          doInit() {
                                            super.doInit();
                                          
                                            // const
                                            this.DEBUG = false;
                                            this.VERSION = '1.0/2020-03-22';
                                            this.NAME = 'mduiShowIcal';
                                            this.DAY_MILLISECONDS = 60 * 60 * 24 * 1000;
                                          
                                            // -----------------------  
                                            // optional: KONFIGURATION
                                            // -----------------------  
                                                                 // state-Pfad unter dem die States angelegt werden  
                                            this.STATE_PATH      = '0_userdata.0.mdui.showIcal.'; 
                                                                 // Anzahl der Table/List Ordner mit eigenem Filter/View
                                            this.MAX_LOG_FOLDER  = 3;   
                                                                 // max.Anzahl der Zeilen für die Table/List Ausgabe
                                            this.MAX_TABLE_ROWS  = 200; 
                                                                 // Objekt-Pfad zum ical.x.data.table
                                            this.ICAL_TABLE      = 'ical.0.data.table';
                                                                 // siehe: https://github.com/ioBroker/ioBroker.javascript/blob/master/docs/en/javascript.md#formatdate
                                            this.DATE_FORMAT     = 'W DD.MM.YYYY hh:mm';
                                                                 // optional können dem Kalendar noch Icons und abweichende
                                                                 // Farben angegeben werden (diese überschreiben jene aus iCal)
                                                                 // 'icon'     : 'Icon-Name' (MDCSS/Google WebFont)
                                                                 // 'calColor' : *#rrggbb' abweichende Kalendarfarbe 
                                                                 // 'ignore'   : true|false Kalendar komplett ignorieren
                                            this.CALENDAR        = {'mdui-Abfall'      : {'icon':'delete_outline', 'calcolor':'orange' },
                                                                    'mdui-Geburtstage' : {'icon':'cake'},
                                                                    'Familie'          : {'ignore':true}
                                                                     
                                                                   };
                                          
                                            // -----------------------  
                                            // ENDE KONFIGURATION
                                            // -----------------------  
                                          
                                            // var
                                            this.logs = [];
                                            for (let i=0; i<=this.MAX_LOG_FOLDER && i<10; i++) 
                                                this.logs.push({filter:'' });
                                            
                                          
                                            // init der states
                                            this.states.push( { id:'version',     common:{name:'installed script-version', write:false, def:this.VERSION} } );
                                            this.states.push( { id:'updatePressed',common:{name:'update button pressed', write:true, type:'boolean', def:'false', role:'button' }} );
                                            
                                            let defFilter;
                                            for (let i=0; i<=this.MAX_LOG_FOLDER && i<10; i++) {
                                                switch (i) {
                                                    case 1 : defFilter = ':Abfall:'; break;   
                                                    case 2 : defFilter = 'Geburtstag'; break;   
                                                    case 3 : defFilter = ''; break;   
                                                    default: defFilter = undefined;
                                                }
                                                this.states.push( { id:'log'+i+'.table',      common:{name:'ioBroker-log as table', write:false, role:'html' }} );
                                                this.states.push( { id:'log'+i+'.list',       common:{name:'ioBroker-log as list', write:false, role:'html' }} );
                                                this.states.push( { id:'log'+i+'.count',      common:{name:'ioBroker-log count', write:false, type:'number', def:'0' }} );
                                                this.states.push( { id:'log'+i+'.filter',     common:{name:'ioBroker-log filter', write:true, def:defFilter}} );
                                                this.states.push( { id:'log'+i+'.lastUpdate', common:{name:'ioBroker-log last update', write:false, def:'0' }} );
                                            }
                                          
                                            return true;  
                                          }
                                          
                                          // start the script/class
                                          doStart() {
                                              super.doStart();
                                              
                                              // subscriber erzeugen
                                              this.subscribe( on( this.STATE_PATH+'updatePressed', obj => { this.onUpdate(obj) } ));
                                              this.subscribe( on( new RegExp( this.STATE_PATH+'*.filter' ), obj => { this.onFilter(obj) } ));
                                              this.subscribe( on( this.ICAL_TABLE, obj => { this.onIcalTable(obj) } ));
                                          
                                              this.onBuildHTML();
                                              return true;
                                          }
                                          
                                          // stop the script/class
                                          doStop() {
                                              super.doStop();
                                              return true;
                                          }
                                          
                                          // 
                                          onUpdate(obj) {
                                              if (obj.state.val===true) {
                                                  this.onBuildHTML();
                                              }
                                              this.setState('updatePressed', false);
                                          }
                                          
                                          // filter, sort events
                                          onFilter(obj) {
                                            this.onBuildHTML();
                                          }
                                          
                                          // Ical table hat sich geändert
                                          onIcalTable(obj) {
                                            this.onBuildHTML();
                                          }
                                          
                                          
                                          
                                          // creates the HTML states for every log
                                          /*
                                          'date' => "→ 16.10.2017" 
                                          'event' => "9.30 - 16.30 Uhr Stopka " 
                                          '_class' => "ical_Kerstin ical_today" 
                                          '_date' => "2017-10-16T07:30:00.000Z" 
                                          '_end' => "2017-10-16T14:30:00.000Z" 
                                          '_section' => "" 
                                          '_IDID' => "040000008200E00074C5B7101A82E008000000007645DC55A6DFC44FB2EC6FEA9EFEA33C100000007688316058136F42AD128D91103543C9" 
                                          '_allDay' => "false" 
                                          '_rule' => " " 
                                          '_calName' => "Kerstin" 
                                          
                                          Optionen der ical-Instanz:
                                              'daysPreview' => "7"
                                              'colorize' => "false"
                                              'defColor' => "white"
                                              'fulltime' => " 00:00"
                                              'dataPaddingWithZeros' => "true"
                                              'replaceDates' => "false"
                                              'language' => "de"
                                              'everyCalOneColor' => "true"
                                              'calendars' (array) 
                                                  '0' (array) 
                                                      'name' => "Abfall"
                                                      'url' => "https://calendar.google.com/calendar/ical/ruo0ddgalu03qq2ehpm8imqnk4%40group.calendar.google.com/private-a30aac0367f8b50cfd7373f6222d29b4/basic.ics"
                                                      'user' => ""
                                                      'pass' => ""
                                                      'sslignore' => ""
                                                      'color' => ""
                                          */
                                          
                                          isSameDay(d1,d2) {
                                              return (d1.getDate()==d2.getDate()) && (d1.getMonth()==d2.getMonth()) && (d1.getFullYear()==d2.getFullYear());
                                          }
                                          
                                          getWeekNumber( date ) {
                                              let d = new Date(date);
                                              d.setHours(0,0,0);
                                              d.setDate(d.getDate()+4-(d.getDay()||7));
                                              return Math.ceil((((d-new Date(d.getFullYear(),0,1))/8.64e7)+1)/7);
                                          };            
                                          
                                          //
                                          buildEntry( entry ) {
                                              const WEEKDAY_NAMES = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
                                              const MONTH_NAMES = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
                                          
                                              let beginDate = new Date( entry.beginDateISO );
                                              entry.beginTime = beginDate.getTime();
                                              entry.beginWeekDay = beginDate.getDay(); // 0=Sonntag 6=Samstag
                                              entry.beginWeekDayName = WEEKDAY_NAMES[entry.beginWeekDay]; 
                                              entry.beginDay = beginDate.getDate();
                                              entry.beginMonth = beginDate.getMonth();
                                              entry.beginMonthName = MONTH_NAMES[entry.beginMonth]; 
                                              entry.beginYear = beginDate.getFullYear();
                                              entry.beginHour = beginDate.getHours();
                                              entry.beginMinute = beginDate.getMinutes();
                                              entry.beginDate = formatDate(beginDate,  this.DATE_FORMAT);
                                          
                                              let currDate = new Date( entry.currDateISO );
                                              entry.currTime = currDate.getTime();
                                              entry.currWeekDay = currDate.getDay(); // 0=Sonntag 6=Samstag
                                              entry.currWeekDayName = WEEKDAY_NAMES[entry.currWeekDay]; 
                                              entry.currDay = currDate.getDate();
                                              entry.currMonth = currDate.getMonth();
                                              entry.currMonthName = MONTH_NAMES[entry.currMonth]; 
                                              entry.currYear = currDate.getFullYear();
                                              entry.currHour = currDate.getHours();
                                              entry.currMinute = currDate.getMinutes();
                                              entry.currDate = formatDate(currDate,  this.DATE_FORMAT);
                                              entry.currDateOnly = entry.currYear*10000+entry.currMonth*100+entry.currDay;
                                          
                                          
                                              let endDate = new Date( entry.endDateISO );
                                              //allDay Korrektur: liefert immer einen Tag zu viel
                                              if (entry.allDay && endDate.getHours()==0) endDate.setTime( endDate.getTime() - this.DAY_MILLISECONDS);
                                              entry.endTime = endDate.getTime();
                                              entry.endWeekDay = endDate.getDay(); // 0=Sonntag 6=Samstag
                                              entry.endDay = endDate.getDate();
                                              entry.endMonth = endDate.getMonth();
                                              entry.endYear = endDate.getFullYear();
                                              entry.endHour = endDate.getHours();
                                              entry.endMinute = endDate.getMinutes();
                                              entry.endDate = formatDate(endDate, this.DATE_FORMAT);
                                          
                                              // 
                                              if (entry.currWeekDay==0 || entry.currWeekDay==6) entry.dayColor = '#f44336'
                                              else entry.dayColor = 'inherited';
                                          
                                              // 
                                              if (entry.currWeekDay==1) entry.week = this.getWeekNumber(currDate)+'.W';
                                              else entry.week = '';
                                          
                                              // 
                                              if (entry.currTime < Date.now() ) entry.opacity = '.66'
                                              else entry.opacity = '1';
                                          
                                              // Zeitraumangabe berechnen
                                              entry.hint = '';
                                              if ( !this.isSameDay(beginDate, endDate) ) {
                                                      // mehrtägig
                                                      if (entry.allDay)
                                                          entry.timeSpan = 'ganztägig ';
                                                      else if ( this.isSameDay(beginDate, currDate) ) 
                                                              entry.timeSpan = 'ab ' + formatDate(beginDate,'hh:mm');
                                                           else if ( this.isSameDay(endDate, currDate) ) 
                                                                    entry.timeSpan = 'bis ' + formatDate(endDate,'hh:mm');
                                                                else 
                                                                     entry.timeSpan = 'ganztägig ';
                                                  } else {
                                                      // an einem Tag
                                                      if (entry.allDay)
                                                          entry.timeSpan = 'ganztägig';
                                                      else if ( (entry.beginHour!=entry.endHour) || (entry.beginMinute!=entry.endMinute)  ) 
                                                               entry.timeSpan = formatDate(beginDate,'hh:mm') + '-' + formatDate(endDate,'hh:mm');
                                                           else   
                                                               entry.timeSpan = formatDate(beginDate,'hh:mm');
                                                  }
                                              return entry;        
                                          };
                                          
                                          
                                          onBuildHTML() { try {
                                          
                                              let json = [];  
                                              let calTable = getState(this.ICAL_TABLE);
                                              let inst     = getObject("system.adapter."+this.ICAL_TABLE.substr(0,6));
                                              let instopt  = inst.native;
                                              let calendar = {};
                                              let cal      = {};
                                              let calOptions = {};
                                              let todayDate = new Date();
                                              let today = todayDate.getFullYear()*10000+todayDate.getMonth()*100+todayDate.getDate();
                                          
                                          
                                              for (var i = 0; i < calTable.val.length; i++) { 
                                                cal = calTable.val[i];
                                                let entry = {};
                                                if ( cal._calName != calendar.name ) {
                                                      calendar = {};
                                                      // color suchen
                                                      for (let c = 0; c < instopt.calendars.length; c++ ) {
                                                          if (cal._calName == instopt.calendars[c].name ) {
                                                              calendar = instopt.calendars[c];
                                                              break;
                                                          }
                                                      }
                                                      // options suchen
                                                      if (this.CALENDAR.hasOwnProperty(cal._calName)) calOptions = this.CALENDAR[cal._calName];
                                                      else calOptions={};
                                                  }    
                                                  if (calOptions.hasOwnProperty('ignore') && calOptions.ignore ) continue;
                                          
                                                  if (calOptions.hasOwnProperty('calcolor')) entry.calColor = calOptions.calcolor;
                                                  else if (calendar.color !== "") entry.calColor = calendar.color;
                                                       else entry.calColor = '#000000';
                                                  entry.color = this.getFontColor( entry.calColor ); 
                                                  if (calOptions.hasOwnProperty('icon')) entry.icon = calOptions.icon;
                                                  else entry.icon='';
                                          
                                                  entry.date = cal.date;
                                                  entry.event = cal.event;
                                                  entry.calName = cal._calName;
                                                  entry.beginDateISO = cal._date;
                                                  entry.currDateISO = cal._date;
                                                  entry.endDateISO  = cal._end;
                                                  entry.allDay  = cal._allDay; 
                                                  entry.location = cal.location;
                                          
                                                  entry = this.buildEntry( entry);
                                          
                                                  if (entry.currDateOnly >= today)
                                                      json.push( entry );
                                          
                                                  // Listenansicht
                                                  // in calTable steht nur ein Eintrag für jeden Beginn, für die List-Darstellung
                                                  // sind diese auch je Tag notwendig - hier jetzt bilden 
                                                  if ( (entry.beginDay!=entry.endDay) || (entry.beginMonth!=entry.endMonth) || (entry.beginYear!=entry.endYear) ) {
                                                      let currTime = new Date(entry.beginDateISO).getTime() + this.DAY_MILLISECONDS;
                                                      let endTime = new Date(entry.endDateISO).getTime();
                                                      if (entry.allDay) endTime -= + this.DAY_MILLISECONDS;
                                                      let dayMax=Math.trunc( 2 + (endTime - currTime) /this. DAY_MILLISECONDS); 
                                                      entry.hint += ' (Tag 1/'+dayMax+')';
                                                      let dayCount=2; 
                                                      while (currTime <= endTime && dayCount<100) {
                                                          let newEntry = {};
                                                          Object.assign(newEntry, entry);
                                                          newEntry.currDateISO = new Date(currTime).toISOString();
                                                          newEntry = this.buildEntry( newEntry );
                                                          newEntry.hint += ' (Tag '+dayCount+'/'+dayMax+')';
                                                          if (newEntry.currDateOnly >= today)
                                                              json.push( newEntry );
                                                          currTime = currTime + this.DAY_MILLISECONDS;
                                                          dayCount++;
                                                      }
                                          
                                                  }
                                              }
                                          
                                              // sortieren
                                              json.sort( (l,r) => {
                                                        let lv=l['currTime'],rv=r['currTime'];
                                                        return ((lv < rv) ? -1 : (lv > rv) ? 1 : 0);
                                                    } );
                                                  
                                            // build table/list HTML
                                            for (let i=0; i<=this.MAX_LOG_FOLDER && i<10; i++) {
                                                let log = this.logs[i];
                                                log.filter = '';
                                                log.ts = 0;
                                                log.idState = 'log'+i;
                                                if (this.existState(log.idState+'.filter')) log.filter = this.getState(log.idState+'.filter').val;
                                                if (this.existState(log.idState+'.lastClear')) log.ts = this.getState(log.idState+'.lastClear').val;
                                          
                                                this.convertJSON2HTML(json, log);
                                            }
                                          } catch(err) { this.logError( 'onBuildHTML: '+err.message ); }  }
                                          
                                          
                                          // color date event calName beginDate endDate allDay
                                          convertJSON2HTML(json, log) {
                                          const tmpTable = {
                                          header : 
                                          `<tr>
                                          <th style="text-align:right;"></th>
                                          <th style="text-align:left;"></th>
                                          <th style="text-align:left;"></th>
                                          <th style="text-align:left;"></th>
                                          <th style="text-align:left;"></th>
                                          <th style="text-align:left; min-width:12em;">Betreff</th>
                                          <th style="text-align:left;">Zeit</th>
                                          <th style="text-align:left;">Ort</th>
                                          <th style="text-align:left;">Kalendar</th>
                                          <th style="text-align:left;"></th>
                                          </tr>`,
                                          row : 
                                          `<tr>
                                          <td style="text-align:right;">
                                             <span style="display:{showDay}; color:{dayColor}; font-size:1.5em; opacity:1; font-weight:bold;">{currDay}</span>
                                          </td>
                                          <td>
                                             <span style="display:{showDay}; font-size:0.8em; margin-top:0.3em; margin-left:4px; opacity:.8;">{currMonthName}</span>
                                          </td>
                                          <td>
                                             <span style="display:{showDay}; font-size:0.8em; margin-top:0.3em; opacity:.8;">{currWeekDayName}</span>
                                          </td>
                                          <td>
                                            <span style="display:inline-block; width:.8em; height:.8em; margin-top:0.3em; background:{calColor}; border-radius:50%;">&nbsp;</span>
                                          </td>
                                          <td>
                                            <i class='material-icons mdui-center {color}' style='font-size:1.2em;'>{icon}</i>
                                          </td>
                                          <td>{event}</td>
                                          <td><span style="font-size:1.0em; opacity:.8;">{timeSpan} {hint}</span></td>
                                          <td>{location}</td>
                                          <td><span style="font-size:0.8em; opacity:.8;color:{calColor};">{calName}</span></td>
                                          <td></td>
                                          </tr>`
                                          }
                                          
                                          const tmpList = {
                                          row : 
                                          `<div class="mdui-listitem" style="font-size:1em; width:100%; display:flex; opacity:{opacity};">
                                            <div style="min-width:3.5em;">
                                              <div style="display:{showDay}; color:{dayColor};">
                                                <span style="font-size:1.5em; opacity:1; font-weight:bold;">{currDay}</span>
                                                <span style="font-size:0.8em; margin:4px; opacity:.8;">{currMonthName}<br/>
                                                {currWeekDayName}</span>
                                              </div>
                                              <div style="display:{showDay}; text-align:right; font-size:0.6em; opacity:.6;">{week}</div>
                                            </div>
                                            <div style="min-width:1.2em;">
                                              <div style="width:.8em; height:.8em;  margin:.1em; text-align:center; background:{calColor}; border-radius:50%;">&nbsp;</div>
                                              <div class="mdui-icon" style="font-size:1.1em; text-align:center; margin-top:0.33em;">{icon}</div>
                                            </div>
                                            <div style="width:100%; margin-left:.25em;">
                                              <div style="">
                                                <div style="font-size:1.1em;">{event}</div>
                                              </div>
                                              <div style="width:100%; display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between;">
                                                <div style="font-size:1.0em; opacity:.8;">
                                                  {timeSpan}
                                                  <span style="font-size:0.8em;">{hint} {location}</span>
                                                </div>
                                                <div style="font-size:0.8em; opacity:.8;color:{calColor};">{calName}</div>
                                              </div>
                                            </div>
                                          </div>`}
                                              // build htmlTable and htmlList
                                              let htmlTable  = "<table><thead>"+tmpTable.header+"</thead><tbody>";
                                              for (let [key, value] of Object.entries(log)) htmlTable = htmlTable.replace(new RegExp('{'+key+'}','g'),value);
                                             
                                              let htmlList  = "";
                                              let entry, tr;
                                              let count = 0;
                                              // filter as regex?
                                              if ( log.filter!==undefined && typeof log.filter == 'string' && log.filter.startsWith('/') && log.filter.endsWith('/') && (log.filter.length>=2) )  {
                                                  log.filter = new RegExp(log.filter.substr(1,log.filter.length-2), 'i');
                                              }
                                          
                                              let lastEntry = {};
                                              for (var i = 0; i < json.length && count<this.MAX_TABLE_ROWS; i++) { 
                                                  entry = json[i];
                                                  if (this.fitsFilter(':' + entry.currDate + ':' + entry.event +':'+entry.calName + ':' + entry.location + ':',log.filter)) {
                                                      entry.showDay = (lastEntry=={}) || (entry.currDay!=lastEntry.currDay) || (entry.currMonth!=lastEntry.currMonth) || (entry.currYear!=lastEntry.currYear)?'flex':'none';
                                                      lastEntry = entry;
                                                      tr = tmpTable.row;    
                                                      for (let [key, value] of Object.entries(entry)) tr = tr.replace(new RegExp('{'+key+'}','g'),value);
                                                      htmlTable+=tr;
                                                      tr = tmpList.row;    
                                                      for (let [key, value] of Object.entries(entry)) tr = tr.replace(new RegExp('{'+key+'}','g'),value);
                                                      htmlList+=tr;
                                                      count++;
                                                  }
                                              }
                                              htmlTable+="</body></table>";    
                                              this.setState(log.idState+'.table', htmlTable);  
                                              this.setState(log.idState+'.list', htmlList);  
                                              this.setState(log.idState+'.count', count);  
                                              this.setState(log.idState+'.lastUpdate', +new Date());  
                                          }
                                          
                                          }
                                          
                                          
                                          // create instance and start
                                          var mduiShowIcal = new MduiShowIcal( );
                                          mduiShowIcal.start();
                                          
                                          // on script stop, stop instance too
                                          onStop(function () { 
                                              mduiShowIcal.stop(); 
                                          }, 1000 );
                                      
                                      E Offline
                                      E Offline
                                      Ente
                                      schrieb am zuletzt editiert von Ente
                                      #44

                                      @sigi234

                                      Skript läuft. Vielen Dank.

                                      Tut mir leid sigi. Wie bekomme ich das nun in meine vis integriert. Entschuldige bitte die Dummy-Frage.

                                      sigi234S 1 Antwort Letzte Antwort
                                      0
                                      • E Ente

                                        @sigi234

                                        Skript läuft. Vielen Dank.

                                        Tut mir leid sigi. Wie bekomme ich das nun in meine vis integriert. Entschuldige bitte die Dummy-Frage.

                                        sigi234S Online
                                        sigi234S Online
                                        sigi234
                                        Forum Testing Most Active
                                        schrieb am zuletzt editiert von
                                        #45

                                        @Ente sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                                        @sigi234

                                        Skript läuft. Vielen Dank.

                                        Tut mir leid sigi. Wie bekomme ich das nun in meine vis integriert. Entschuldige bitte die Dummy-Frage.

                                        Es werden States für n-Logs erzeugt, jedem, Log kann ein Standardfilter mitgegeben werden. In jedem Log-Ordner
                                        befindet sich ein list-HTML State, welcher direkt in der vis angezeigt werden kann (jeweils im basic-string (unescaped) Widget).

                                        Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                                        Immer Daten sichern!

                                        E 1 Antwort Letzte Antwort
                                        0
                                        • sigi234S sigi234

                                          @Ente sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                                          @sigi234

                                          Skript läuft. Vielen Dank.

                                          Tut mir leid sigi. Wie bekomme ich das nun in meine vis integriert. Entschuldige bitte die Dummy-Frage.

                                          Es werden States für n-Logs erzeugt, jedem, Log kann ein Standardfilter mitgegeben werden. In jedem Log-Ordner
                                          befindet sich ein list-HTML State, welcher direkt in der vis angezeigt werden kann (jeweils im basic-string (unescaped) Widget).

                                          E Offline
                                          E Offline
                                          Ente
                                          schrieb am zuletzt editiert von Ente
                                          #46

                                          @sigi234 said in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                                          @Ente sagte in [Vorlage] MDCSS v2: ical Kalendar anzeigen:

                                          @sigi234

                                          Skript läuft. Vielen Dank.

                                          Tut mir leid sigi. Wie bekomme ich das nun in meine vis integriert. Entschuldige bitte die Dummy-Frage.

                                          Es werden States für n-Logs erzeugt, jedem, Log kann ein Standardfilter mitgegeben werden. In jedem Log-Ordner
                                          befindet sich ein list-HTML State, welcher direkt in der vis angezeigt werden kann (jeweils im basic-string (unescaped) Widget).

                                          Genau das habe ich gerade gefunden. Es zeigt auch die Termine an,

                                          feb6d00b-f448-415c-a618-733025c42ac5-image.png

                                          leider nicht so wie in dem Bild im 1. Post. bzw. wie hier:

                                          https://forum.iobroker.net/topic/31635/vorlage-mdcss-v2-ical-kalendar-anzeigen/15
                                          1ad94760-95e1-46fb-ac36-f89d3a9b77ad-image.png

                                          Ich vermute mal, dass ich das im Skript selbst ändern muss. Mal sehen.

                                          Edit: Das Datum erscheint schon einmal. Jetzt will ich noch die Farben ändern. Den Filter habe ich auch nicht.

                                          sigi234S 1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          925

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe