VN Mailer

Anwendungen für Webseiten. Künstliche Intelligenz verwenden.
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Moin Tommy,

die Daten gelten pro Projekt !! Wenn also ein neues Projekt angelegt wird muss auch wieder die SMTP Maske ausgefüllt werden. Die Daten liegen dann immer in asstes/vn-shared/config.php

Damit nicht SMTP Daten von Projekt A in Projekt B landen ;)

Jede meiner Anwendungen legt einen eigenen Ordner an:
ordner.png
ordner.png (11.16 KiB) 305 mal betrachtet
PS: Wer also diese Daten per Hand ändern möchte muss das in der config.php machen ;)
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Moin,

hmmm - habe kein solches Verzeichnis "vn-shared" :eek:


Verzeichnis vn-shared ist nicht vorhanden.jpg
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Ah - sorry - das ist natürlich dann nur online am Server. Dort habe ich es auch :verlegen:
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Nur Online ;)

Dummerweise filtert Mobirise den raus beim lokalen Veröffentlichen.

Sollte man noch erwähnen Tommy ;)

Hab ich vergessen :D Man bin ich alt :eek:


Da bin ich auch noch nicht hinter gestiegen wie ich das hin bekommen soll :confused: Aber finde ich jetzt auch nicht so schlimm. ALso nochmal Deutlich: Immer mit Mobirise die Seite veröffentlichen !! Wenn per FTP muss der Ordner von Hand angelegt werden und auch die config.php
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Nee - ist doch vollkommen OK. Wenn man es nicht weiß, sucht man dennoch am Computer und findet nix. Und ja - auf meiner Tutorialseite steht das längst.

↗️ https://www.mobirise-tutorials.com/VN-B ... ailer.html


Falls jemand den "VN Mailer" in Deutsch/Englisch haben wollte, hier der Code:

Code: Alles auswählen

<section data-bs-version="5.1" class="vn-mailer" group="Content" id="{{mbr-id}}" data-addcopycheckbox="{{addCopyCheckbox}}" data-copycheckboxlabel="{{copyCheckboxLabel}}" data-addcaptcha="{{addCaptcha}}" data-thankyouurl="{{thankYouUrl}}" data-toastmessage="{{toastMessage}}" data-textcolor="{{textColor}}" data-captchatransparent="{{captchaTransparent}}" data-captchabgcolor="{{captchaBgColor}}" data-captchabordercolor="{{captchaBorderColor}}" data-accentcolor="{{accentColor}}" data-fieldfontsize="{{fieldFontSize}}" data-scrolltoform="{{scrollToForm}}">
    <style>
        .vn-mailer {
            display: none;
        }

        html.is-builder .vn-mailer {
            display: block !important;
            padding: 20px;
            text-align: center;
            border: 2px dashed #e63946;
            border-radius: 8px;
            background: #fff5f5;
            color: #999;
            font-size: 13px;
        }

        .vn-mailer .vnm-editor-only {
            display: none;
        }

        html.is-builder .vn-mailer .vnm-editor-only {
            display: block;
        }

        .vnm-toast {
            position: fixed;
            bottom: 24px;
            left: 50%;
            transform: translateX(-50%) translateY(20px);
            max-width: calc(100vw - 40px);
            background: #1d9e75;
            color: #fff;
            padding: 14px 24px;
            border-radius: 8px;
            box-shadow: 0 8px 24px rgba(0,0,0,0.2);
            font-family: system-ui, sans-serif;
            font-size: 14px;
            line-height: 1.5;
            text-align: center;
            white-space: pre-line;
            z-index: 999995;
            opacity: 0;
            transition: opacity 0.3s ease, transform 0.3s ease;
            pointer-events: none;
        }

        .vnm-toast.vnm-toast-show {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
        }

        .vnm-bilingual-text {
            display: inline-block;
            line-height: 1.45;
        }

        /* EDIT Tommy */
        #myLabel {
            padding-top: 20px;
        }
    </style>

    <mbr-parameters>
        <header>Formular-Erweiterung</header>

        <input type="checkbox" title="'Kopie an mich'-Checkbox einfügen" name="addCopyCheckbox" checked>

        <input type="text" title="Checkbox-Text – Deutsch" name="copyCheckboxLabel" value="Kopie an mich senden">

        <input type="checkbox" title="Captcha einfügen (Rechenaufgabe)" name="addCaptcha" checked>

        <header>Optik der eingefügten Felder</header>

        <input type="color" title="Textfarbe (Label/Checkbox-Text)" name="textColor" value="#ffffff" selected>

        <input type="checkbox" title="Captcha-Box transparent (kein eigener Hintergrund)" name="captchaTransparent" checked>

        <input type="color" title="Captcha-Box Hintergrund" name="captchaBgColor" value="#f0f4f8" condition="!captchaTransparent" selected>

        <input type="color" title="Captcha-Box Rahmen" name="captchaBorderColor" value="#b8c4d0" selected>

        <input type="color" title="Akzentfarbe (Pflichtfeld-Sterne)" name="accentColor" value="#ffffff" selected>

        <input type="range" inline title="Schriftgröße Label/Captcha (px)" name="fieldFontSize" min="10" max="24" step="1" value="16">

        <header>Nach dem Absenden</header>

        <input type="text" title="Danke-Seite (URL, leer = Toast-Meldung statt Weiterleitung)" name="thankYouUrl" value placeholder="/danke.html oder https://...">

        <input type="text" title="Toast-Meldung – Deutsch (falls keine Danke-Seite)" name="toastMessage" value="Danke für deine Nachricht!">

        <input type="checkbox" title="Zum Formular scrollen statt Seitenanfang (nach Absenden/Fehler)" name="scrollToForm" checked>
    </mbr-parameters>

    <div class="vnm-editor-only">
        🔧 VN Mailer<br>
        <small>
            Unsichtbar auf der Live-Seite.<br>
            Invisible on the live website.<br><br>

            Rüstet das Formular direkt darüber oder darunter mit
            Kopie-Checkbox, Captcha und Danke-Seite nach.<br>

            Adds a copy checkbox, captcha and thank-you page to the
            form directly above or below.
        </small>
    </div>

    <script>
    (function() {
        if (document.documentElement.classList.contains('is-builder')) return;

        var sec = document.currentScript
            ? document.currentScript.closest('section.vn-mailer')
            : document.querySelector('section.vn-mailer');

        if (!sec) return;

        function attr(name) {
            var value = sec.getAttribute(name);

            if (!value || value.indexOf('{{') !== -1) {
                return '';
            }

            return value.trim();
        }

        function truthy(value) {
            return value === 'true' ||
                   value === 'on' ||
                   value === '1';
        }

        var addCopyCheckbox = truthy(
            attr('data-addcopycheckbox')
        );

        var copyCheckboxLabel =
            attr('data-copycheckboxlabel') ||
            'Kopie an mich senden';

        var addCaptcha = truthy(
            attr('data-addcaptcha')
        );

        var thankYouUrl =
            attr('data-thankyouurl');

        var toastMessageGerman =
            attr('data-toastmessage') ||
            'Danke für deine Nachricht!';

        var toastMessageEnglish =
            'Thank you for your message!';

        var textColor =
            attr('data-textcolor') ||
            '#222222';

        var captchaTransparent = truthy(
            attr('data-captchatransparent')
        );

        var captchaBgColor =
            attr('data-captchabgcolor') ||
            '#f0f4f8';

        var captchaBorderColor =
            attr('data-captchabordercolor') ||
            '#b8c4d0';

        var accentColor =
            attr('data-accentcolor') ||
            '#e63946';

        var fieldFontSize =
            attr('data-fieldfontsize') ||
            '14';

        var scrollToForm = truthy(
            attr('data-scrolltoform')
        );

        var labelColorStyle =
            'color:' + textColor + ' !important;' +
            'font-size:' + fieldFontSize + 'px !important;';

        var captchaBgStyle = captchaTransparent
            ? 'background:transparent;'
            : 'background:' + captchaBgColor + ';';

        /*
         * Natives Formular in der Nachbarschaft finden.
         *
         * Es werden bis zu fünf Geschwister-Elemente in beide
         * Richtungen durchsucht. Dadurch darf sich beispielsweise
         * noch ein anderer Mobirise-Block zwischen Formular und
         * VN-Mailer befinden.
         */
        function findNeighborForm() {
            var maxSteps = 5;
            var prevEl = sec;

            for (var i = 0; i < maxSteps; i++) {
                prevEl = prevEl.previousElementSibling;

                if (!prevEl) {
                    break;
                }

                var formInPrev = prevEl.tagName === 'FORM'
                    ? prevEl
                    : prevEl.querySelector('form');

                if (formInPrev) {
                    return formInPrev;
                }
            }

            var nextEl = sec;

            for (var j = 0; j < maxSteps; j++) {
                nextEl = nextEl.nextElementSibling;

                if (!nextEl) {
                    break;
                }

                var formInNext = nextEl.tagName === 'FORM'
                    ? nextEl
                    : nextEl.querySelector('form');

                if (formInNext) {
                    return formInNext;
                }
            }

            return null;
        }

        /*
         * Steht der VN-Mailer vor dem Formular im HTML, existiert
         * das Formular beim Ausführen dieses Script-Tags eventuell
         * noch nicht im DOM.
         *
         * Deshalb erfolgt die Suche erst nach dem vollständigen
         * Aufbau des HTML-Dokuments.
         */
        function initFormEnhancements() {
            var form = findNeighborForm();

            if (!form) {
                /*
                 * Kein Formular gefunden.
                 *
                 * Eine Rückmeldung nach einer Weiterleitung kann
                 * dennoch angezeigt werden, lediglich ohne ein
                 * Formular als Scroll-Ziel.
                 */
                handleRedirectResult(null);
                return;
            }

            runEnhancements(form);
            handleRedirectResult(form);
        }

        if (document.readyState === 'loading') {
            document.addEventListener(
                'DOMContentLoaded',
                initFormEnhancements
            );
        } else {
            initFormEnhancements();
        }

        function runEnhancements(form) {
            function getAssetsBasePath() {
                var ref = document.querySelector(
                    'link[href*="assets/"], script[src*="assets/"]'
                );

                if (ref) {
                    var url =
                        ref.getAttribute('href') ||
                        ref.getAttribute('src');

                    var index = url.indexOf('assets/');

                    if (index !== -1) {
                        return url.substring(
                            0,
                            index + 'assets/'.length
                        );
                    }
                }

                return 'assets/';
            }

            var base =
                getAssetsBasePath() +
                'vn-mailer/';

            /*
             * Container, in den die zusätzlichen Felder
             * eingefügt werden.
             */
            var dragArea =
                form.querySelector('.dragArea') ||
                form;

            var submitBtnWrap =
                dragArea.querySelector('.mbr-section-btn');

            function insertField(html) {
                var wrap =
                    document.createElement('div');

                wrap.className =
                    'col-lg-12 col-md-12 col-sm-12 form-group mb-3';

                wrap.innerHTML = html;

                if (submitBtnWrap) {
                    dragArea.insertBefore(
                        wrap,
                        submitBtnWrap
                    );
                } else {
                    dragArea.appendChild(wrap);
                }

                return wrap;
            }

            /*
             * Verstecktes Feld mit der ID des VN-Mailer-Blocks.
             *
             * Dadurch kann bei mehreren Formularen auf derselben
             * Seite festgestellt werden, welcher VN-Mailer die
             * Rückmeldung anzeigen soll.
             */
            var idField =
                document.createElement('input');

            idField.type = 'hidden';
            idField.name = 'vnmailer_id';
            idField.value = sec.id || '';

            form.appendChild(idField);

            /*
             * Checkbox:
             * Kopie an mich senden
             * Send me a copy
             */
            if (addCopyCheckbox) {
                insertField(
                    '<label style="' +
                        'display:flex;' +
                        'align-items:center;' +
                        'gap:8px;' +
                        'font-weight:400;' +
                        labelColorStyle +
                    '">' +

                        '<input ' +
                            'type="checkbox" ' +
                            'name="copy_to_sender" ' +
                            'style="width:auto;"' +
                        '> ' +

                        '<span class="vnm-bilingual-text">' +
                            escapeHtmlLocal(copyCheckboxLabel) +
                            '/ Send me a copy' +
                        '</span>' +

                    '</label>'
                );
            }

            /*
             * Captcha einfügen.
             */
            if (addCaptcha) {
                var captchaWrap = insertField(
                    '<label id="myLabel" style="' +
                        labelColorStyle +
                    '">' +

                        '<span class="vnm-bilingual-text">' +
                            'Sicherheitsabfrage' +
                            ' / Security check' +
                        '</span> ' +

                        '<span style="color:' +
                            accentColor +
                        ';">*</span>' +

                    '</label>' +

                    '<div style="' +
                        'display:flex;' +
                        'align-items:center;' +
                        'gap:10px;' +
                        captchaBgStyle +
                        'border:1px dashed ' +
                        captchaBorderColor +
                        ';border-radius:6px;' +
                        'padding:10px 12px;' +
                    '">' +

                        '<span data-vnm-captcha-q style="' +
                            labelColorStyle +
                        '">' +
                            'Lade… / Loading…' +
                        '</span>' +

                        '<input ' +
                            'type="text" ' +
                            'name="captcha_answer" ' +
                            'required ' +
                            'aria-label="Antwort / Answer" ' +
                            'style="' +
                                'width:70px;' +
                                'padding:6px;' +
                                'border:1px solid #ccc;' +
                                'border-radius:4px;' +
                                'font-size:' +
                                fieldFontSize +
                                'px !important;' +
                            '"' +
                        '>' +

                        '<input ' +
                            'type="hidden" ' +
                            'name="captcha_token" ' +
                            'value=""' +
                        '>' +

                    '</div>'
                );

                fetch(
                    base + 'captcha.php?t=' + Date.now(),
                    {
                        cache: 'no-store'
                    }
                )
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'Captcha konnte nicht geladen werden.'
                        );
                    }

                    return response.json();
                })
                .then(function(data) {
                    var questionElement =
                        captchaWrap.querySelector(
                            '[data-vnm-captcha-q]'
                        );

                    var tokenElement =
                        captchaWrap.querySelector(
                            'input[name="captcha_token"]'
                        );

                    if (questionElement) {
                        questionElement.textContent =
                            data.question || '?';
                    }

                    if (tokenElement) {
                        tokenElement.value =
                            data.token || '';
                    }
                })
                .catch(function() {
                    var questionElement =
                        captchaWrap.querySelector(
                            '[data-vnm-captcha-q]'
                        );

                    if (questionElement) {
                        questionElement.textContent =
                            'Captcha konnte nicht geladen werden.' +
                            '\n' +
                            'The captcha could not be loaded.';

                        questionElement.style.whiteSpace =
                            'pre-line';
                    }
                });
            }

            function escapeHtmlLocal(value) {
                var div =
                    document.createElement('div');

                div.textContent = value || '';

                return div.innerHTML;
            }
        }

        /*
         * Erfolg oder Fehler nach einem echten Seiten-Reload
         * erkennen.
         *
         * native-form-handler.php leitet nach dem Absenden zurück
         * auf diese Seite und hängt beispielsweise folgende
         * Parameter an:
         *
         * ?vnmailer=success
         * ?vnmailer=error
         */
        function showToast(message, type) {
            var existingToast =
                document.querySelector('.vnm-toast');

            if (existingToast) {
                existingToast.remove();
            }

            var toast =
                document.createElement('div');

            toast.className = 'vnm-toast';

            if (type === 'error') {
                toast.style.background = '#c0392b';
            }

            toast.textContent = message;

            document.body.appendChild(toast);

            requestAnimationFrame(function() {
                toast.classList.add(
                    'vnm-toast-show'
                );
            });

            setTimeout(function() {
                toast.classList.remove(
                    'vnm-toast-show'
                );

                setTimeout(function() {
                    toast.remove();
                }, 400);
            }, 5000);
        }

        function onFormSuccess() {
            if (thankYouUrl) {
                window.location.href =
                    thankYouUrl;
            } else {
                showToast(
                    toastMessageGerman +
                    '\n' +
                    toastMessageEnglish,
                    'success'
                );
            }
        }

        function handleRedirectResult(form) {
            var urlParams =
                new URLSearchParams(
                    window.location.search
                );

            var vnmStatus =
                urlParams.get('vnmailer');

            if (
                vnmStatus !== 'success' &&
                vnmStatus !== 'error'
            ) {
                return;
            }

            /*
             * Bei mehreren VN-Mailer-Blöcken auf einer Seite nur
             * reagieren, wenn diese Instanz tatsächlich gemeint
             * ist.
             */
            var targetId =
                urlParams.get('vnmailer_id') ||
                '';

            if (
                targetId &&
                sec.id &&
                targetId !== sec.id
            ) {
                return;
            }

            /*
             * Zum Formular scrollen statt am Seitenanfang zu
             * bleiben.
             *
             * Bei erfolgreicher Weiterleitung auf eine Danke-Seite
             * wird nicht mehr zum Formular gescrollt.
             */
            if (
                scrollToForm &&
                form &&
                !(
                    vnmStatus === 'success' &&
                    thankYouUrl
                )
            ) {
                var targetSection =
                    form.closest('section') ||
                    form;

                targetSection.scrollIntoView({
                    behavior: 'auto',
                    block: 'start'
                });
            }

            if (vnmStatus === 'success') {
                onFormSuccess();
            } else {
                var serverErrorMessage =
                    urlParams.get('vnmailer_msg') ||
                    'Es ist ein Fehler aufgetreten.';

                var bilingualErrorMessage =
                    serverErrorMessage +
                    '\n' +
                    'An error has occurred.';

                showToast(
                    bilingualErrorMessage,
                    'error'
                );
            }

            cleanUrlParams();
        }

        function cleanUrlParams() {
            /*
             * URL-Parameter entfernen, damit ein erneutes Laden
             * der Seite die Meldung nicht noch einmal anzeigt.
             */
            var url =
                new URL(window.location.href);

            url.searchParams.delete(
                'vnmailer'
            );

            url.searchParams.delete(
                'vnmailer_msg'
            );

            url.searchParams.delete(
                'vnmailer_id'
            );

            window.history.replaceState(
                {},
                '',
                url.toString()
            );
        }
    })();
    </script>
</section>
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Soll ich eine englische Version anbieten Tommy ?

Hab ich nun auch noch gemacht Tommy, aber die SMTP Maske muss deutsch bleiben wegen den anderen Anwendungen, da kann ich nix übersetzen ;)

https://www.niederastroth.de/mobirisebl ... t=VN+Tools

Technisch komplett unabhängig von vn-mailer – eigener Alias (vn-mailer-en), eigene Deploy-Pfade (assets/vn-mailer-en/), eigene IDs – kannst du parallel zur deutschen Version installieren, falls du mal eine mehrsprachige Seite baust Tommy ;)
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Das ist nur eine Übersetzung oder? Muss ich die also nicht installieren.

Ja - Englisch ist gut bei dieser Anwendung, denn niemand der das auf Englisch braucht, würde es selbst übersetzen.

Jetzt hat aber der originale "VN Mailer" wieder kein Logo mehr - das sieht scheiße aus. Den musst Du nochmals neu generieren, damit man den auch gleich finden kann.
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Wie kein Logo mehr ? Ist doch in beiden da

Ah ich sehe es wenn beide installiert sind
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Gefixt die heißen intern components/vn-mailer das ist natürlich doof :D

Jetzt hat der englische eigenen Namen ;)

hach gleiche Bilder :crying: man man man

Gefixt :D
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

So,

Thema für mich abgeschlossen :D
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Ja so ist schön :kiss:
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Tommy,

gab noch einen kleinen bug im handler.
Beide Erweiterungen sind gefixt und aktuell im download.

Wenn beide aktiv waren hat immer die erste installierte die Sprache in den Mails gehabt, also entweder nur deutsch oder nur englisch. Ist jetzt weg der Fehler.

Wer nur eine Version installiert hat merkt das natürlich nicht :D
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Huhu :D

Kleine Verbesserung : Ich habe in der deutschen Version ( exklusiv) hcaptcha eingebaut.
https://dashboard.hcaptcha.com/welcome Da kann man die kostenlose Version nehmen ( wie ich )
Einfacher als Google und 100% Kostenfrei ;)

Man kann im Zahnrad jetzt wählen Mathe Captcha oder hCaptcha.
Dazu muss man seinen secret key im Sidemenü eintrage:
hcaptcha.png
Und seinen Site Key dann im Zahnrad:
captchatyp.png
Hab ich hier eingebaut: https://www.niederastroth.de/vntabs/

Ist jetzt aktuell im Download ;)
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Moin,

das ist eine super Idee, ich kannte "hCaptcha" noch gar nicht. Ich gehe mal davon aus, dass das auch Wirkung hat. Weißt Du darüber mehr?

Ich habe mir erlaubt da noch das optionale, dunkle Thema mit in die Blockwerkzeuge einzubinden. Ich kann jetzt also zwischen hellem und dunklen Thema für das "hCaptcha" umschalten. Das lässt sich nämlich nicht über CSS von außen
ändern:

https://www.mobirise-tutorials.com/VN-B ... ailer.html

Falls Du das noch übernehmen willst, hier der Code vom "VN Mailer" Block:

Code: Alles auswählen

<section data-bs-version="5.1" class="vn-mailer" group="Content" id="{{mbr-id}}" data-addcopycheckbox="{{addCopyCheckbox}}" data-copycheckboxlabel="{{copyCheckboxLabel}}" data-addcaptcha="{{addCaptcha}}" data-captchatype="{{captchaType}}" data-hcaptchasitekey="{{hcaptchaSiteKey}}" data-hcaptchatheme="{{hcaptchaTheme}}" data-thankyouurl="{{thankYouUrl}}" data-toastmessage="{{toastMessage}}" data-textcolor="{{textColor}}" data-captchatransparent="{{captchaTransparent}}" data-captchabgcolor="{{captchaBgColor}}" data-captchabordercolor="{{captchaBorderColor}}" data-accentcolor="{{accentColor}}" data-fieldfontsize="{{fieldFontSize}}" data-scrolltoform="{{scrollToForm}}">
    <style>
        .vn-mailer {
            display: none;
        }

        html.is-builder .vn-mailer {
            display: block !important;
            padding: 20px;
            text-align: center;
            border: 2px dashed #e63946;
            border-radius: 8px;
            background: #fff5f5;
            color: #999;
            font-size: 13px;
        }

        .vn-mailer .vnm-editor-only {
            display: none;
        }

        html.is-builder .vn-mailer .vnm-editor-only {
            display: block;
        }

        .vnm-toast {
            position: fixed;
            bottom: 24px;
            left: 50%;
            transform: translateX(-50%) translateY(20px);
            background: #1d9e75;
            color: #fff;
            padding: 14px 24px;
            border-radius: 8px;
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
            font-family: system-ui, sans-serif;
            font-size: 14px;
            z-index: 999995;
            opacity: 0;
            transition: opacity 0.3s ease, transform 0.3s ease;
            pointer-events: none;
        }

        .vnm-toast.vnm-toast-show {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
        }
        /* EDIT Tommy – Beschriftung immer oberhalb des Captchas */
        #myLabel {
            display: block !important;
            width: 100%;
            padding-top: 20px;
            padding-bottom: 10px;
        }

        /*
         * hCaptcha immer unterhalb der Beschriftung anzeigen.
         * Gilt für das helle und das dunkle hCaptcha-Design.
         */
        .vnm-hcaptcha-wrap {
            display: block !important;
            width: 100%;
            max-width: 100%;
            clear: both;
            line-height: 0;
            overflow: visible !important;
            padding-bottom: 2px;
        }

        .vnm-hcaptcha-wrap .h-captcha {
            display: inline-block !important;
            line-height: 0;
            overflow: visible !important;
        }

        .vnm-hcaptcha-wrap iframe {
            display: block !important;
            overflow: visible !important;
        }
    </style>

    <mbr-parameters>
        <header>Formular-Erweiterung</header>

        <input type="checkbox" title="'Kopie an mich'-Checkbox einfügen" name="addCopyCheckbox" checked>

        <input type="text" title="Checkbox-Text" name="copyCheckboxLabel" value="Kopie an mich senden">

        <input type="checkbox" title="Captcha einfügen" name="addCaptcha" checked>

        <select title="Captcha-Typ" name="captchaType">
            <option value="math">Mathe-Aufgabe (eigenes System)</option>
            <option value="hcaptcha" selected>hCaptcha</option>
        </select>

        <input type="text" title="hCaptcha Site-Key (öffentlich, von hcaptcha.com)" name="hcaptchaSiteKey" value="856eaa44-aec0-4fb6-9034-fd4a2dceacec" placeholder="z.B. 10000000-ffff-ffff-ffff-000000000001">

        <!--
            hCaptcha-Design:

            "dark"  = dunkles hCaptcha-Design
            "light" = helles Standarddesign und ursprünglicher Zustand

            Zum Wiederherstellen des ursprünglichen hellen Designs
            in den Blockwerkzeugen „Hell – Originalzustand“ auswählen.
        -->
        <select title="hCaptcha-Design" name="hcaptchaTheme" condition="captchaType == 'hcaptcha'">
            <option value="light" selected>Hell – Originalzustand</option>
            <option value="dark">Dunkel</option>
        </select>

        <header>Optik der eingefügten Felder</header>

        <input type="color" title="Textfarbe (Label/Checkbox-Text)" name="textColor" value="#ffffff" selected>

        <input type="checkbox" title="Captcha-Box transparent (kein eigener Hintergrund)" name="captchaTransparent" checked>

        <input type="color" title="Captcha-Box Hintergrund" name="captchaBgColor" value="#f0f4f8" condition="!captchaTransparent" selected>

        <input type="color" title="Captcha-Box Rahmen" name="captchaBorderColor" value="#b8c4d0" selected>

        <input type="color" title="Akzentfarbe (Pflichtfeld-Sterne)" name="accentColor" value="#ffffff" selected>

        <input type="range" inline title="Schriftgröße Label/Captcha (px)" name="fieldFontSize" min="10" max="24" step="1" value="18">

        <header>Nach dem Absenden</header>

        <input type="text" title="Danke-Seite (URL, leer = Toast-Meldung statt Weiterleitung)" name="thankYouUrl" value placeholder="/danke.html oder https://...">

        <input type="text" title="Toast-Meldung (falls keine Danke-Seite)" name="toastMessage" value="Danke für deine Nachricht!">

        <input type="checkbox" title="Zum Formular scrollen statt Seitenanfang (nach Absenden/Fehler)" name="scrollToForm" checked>
    </mbr-parameters>

    <div class="vnm-editor-only">
        🔧 VN Mailer<br>
        <small>Unsichtbar auf der Live-Seite. Rüstet das Formular direkt darüber oder darunter mit Kopie-Checkbox/Captcha/Danke-Seite nach.</small>
    </div>

    <script>
    (function() {
        if (document.documentElement.classList.contains('is-builder')) return;

        var sec = document.currentScript
            ? document.currentScript.closest('section.vn-mailer')
            : document.querySelector('section.vn-mailer');

        if (!sec) return;

        function attr(name) {
            var v = sec.getAttribute(name);

            if (!v || v.indexOf('{{') !== -1) {
                return '';
            }

            return v.trim();
        }

        function truthy(v) {
            return v === 'true' || v === 'on' || v === '1';
        }

        var addCopyCheckbox    = truthy(attr('data-addcopycheckbox'));
        var copyCheckboxLabel  = attr('data-copycheckboxlabel') || 'Kopie an mich senden';
        var addCaptcha         = truthy(attr('data-addcaptcha'));
        var captchaType        = attr('data-captchatype') || 'math';
        var hcaptchaSiteKey    = attr('data-hcaptchasitekey');

        /*
         * hCaptcha-Design:
         *
         * "dark"  = dunkles Design
         * "light" = helles Standarddesign / Originalzustand
         *
         * Wenn ein älterer Block noch keinen Wert übergibt,
         * wird automatisch das helle Standarddesign verwendet.
         */
        var hcaptchaTheme = attr('data-hcaptchatheme') || 'light';

        var thankYouUrl        = attr('data-thankyouurl');
        var toastMessage       = attr('data-toastmessage') || 'Danke für deine Nachricht!';
        var textColor          = attr('data-textcolor') || '#222222';
        var captchaTransparent = truthy(attr('data-captchatransparent'));
        var captchaBgColor     = attr('data-captchabgcolor') || '#f0f4f8';
        var captchaBorderColor = attr('data-captchabordercolor') || '#b8c4d0';
        var accentColor        = attr('data-accentcolor') || '#e63946';
        var fieldFontSize      = attr('data-fieldfontsize') || '14';
        var scrollToForm       = truthy(attr('data-scrolltoform'));

        /*
         * Sicherheitshalber nur die beiden offiziellen Standardwerte
         * für das hCaptcha-Design zulassen.
         */
        if (hcaptchaTheme !== 'dark' && hcaptchaTheme !== 'light') {
            hcaptchaTheme = 'light';
        }

        var labelColorStyle =
            'color:' + textColor + ' !important;' +
            'font-size:' + fieldFontSize + 'px !important;';

        var captchaBgStyle = captchaTransparent
            ? 'background:transparent;'
            : 'background:' + captchaBgColor + ';';

        // ── Natives Formular in der Nachbarschaft finden ──────────────
        // Sucht bis zu 5 Geschwister-Elemente in jede Richtung ab,
        // falls beispielsweise noch ein Trenner-Block dazwischen liegt.
        function findNeighborForm() {
            var maxSteps = 5;

            var prevEl = sec;

            for (var i = 0; i < maxSteps; i++) {
                prevEl = prevEl.previousElementSibling;

                if (!prevEl) break;

                var formInPrev = prevEl.tagName === 'FORM'
                    ? prevEl
                    : prevEl.querySelector('form');

                if (formInPrev) {
                    return formInPrev;
                }
            }

            var nextEl = sec;

            for (var j = 0; j < maxSteps; j++) {
                nextEl = nextEl.nextElementSibling;

                if (!nextEl) break;

                var formInNext = nextEl.tagName === 'FORM'
                    ? nextEl
                    : nextEl.querySelector('form');

                if (formInNext) {
                    return formInNext;
                }
            }

            return null;
        }

        /*
         * Steht der VN-Mailer-Block vor dem Formular, kann das Formular
         * beim Ausführen dieses Script-Tags noch nicht vollständig im DOM
         * vorhanden sein. Daher erst nach dem Laden der Seite suchen.
         */
        function initFormEnhancements() {
            var form = findNeighborForm();

            if (!form) {
                handleRedirectResult(null);
                return;
            }

            runEnhancements(form);
            handleRedirectResult(form);
        }

        if (document.readyState === 'loading') {
            document.addEventListener(
                'DOMContentLoaded',
                initFormEnhancements
            );
        } else {
            initFormEnhancements();
        }

        function runEnhancements(form) {
            function getAssetsBasePath() {
                var ref = document.querySelector(
                    'link[href*="assets/"], script[src*="assets/"]'
                );

                if (ref) {
                    var url =
                        ref.getAttribute('href') ||
                        ref.getAttribute('src');

                    var idx = url.indexOf('assets/');

                    if (idx !== -1) {
                        return url.substring(
                            0,
                            idx + 'assets/'.length
                        );
                    }
                }

                return 'assets/';
            }

            var base = getAssetsBasePath() + 'vn-mailer/';

            var dragArea =
                form.querySelector('.dragArea') ||
                form;

            var submitBtnWrap =
                dragArea.querySelector('.mbr-section-btn');

            function insertField(html) {
                var wrap = document.createElement('div');

                wrap.className =
                    'col-lg-12 col-md-12 col-sm-12 form-group mb-3';

                wrap.innerHTML = html;

                if (submitBtnWrap) {
                    dragArea.insertBefore(
                        wrap,
                        submitBtnWrap
                    );
                } else {
                    dragArea.appendChild(wrap);
                }

                return wrap;
            }

            var idField = document.createElement('input');

            idField.type = 'hidden';
            idField.name = 'vnmailer_id';
            idField.value = sec.id || '';

            form.appendChild(idField);

            // ── „Kopie an mich“-Checkbox einfügen ─────────────────────
            if (addCopyCheckbox) {
                insertField(
                    '<label style="' +
                        'display:flex;' +
                        'align-items:center;' +
                        'gap:8px;' +
                        'font-weight:400;' +
                        labelColorStyle +
                    '">' +
                        '<input type="checkbox" ' +
                            'name="copy_to_sender" ' +
                            'style="width:auto;"> ' +
                        escapeHtmlLocal(copyCheckboxLabel) +
                    '</label>'
                );
            }

            // ── hCaptcha einfügen ──────────────────────────────────────
            if (
                addCaptcha &&
                captchaType === 'hcaptcha' &&
                hcaptchaSiteKey
            ) {
                /*
                 * Das hCaptcha-Design wird über data-theme gesteuert:
                 *
                 * data-theme="dark"  = dunkles Design
                 * data-theme="light" = helles Originaldesign
                 *
                 * Das zusätzliche DIV .vnm-hcaptcha-wrap verändert
                 * weder Breite noch Skalierung des hCaptcha. Es verhindert
                 * nur das Abschneiden der unteren Border.
                 */
                var hcaptchaField = insertField(
                    '<label id="myLabel" style="' +
                        labelColorStyle +
                    '">' +
                        'Sicherheitsabfrage ' +
                        '<span style="color:' +
                            accentColor +
                        ';">*</span>' +
                    '</label>' +

                    '<div class="vnm-hcaptcha-wrap">' +
                        '<div class="h-captcha" ' +
                            'data-sitekey="' +
                                escapeHtmlLocal(hcaptchaSiteKey) +
                            '" ' +
                            'data-theme="' +
                                escapeHtmlLocal(hcaptchaTheme) +
                            '">' +
                        '</div>' +
                    '</div>' +

                    '<input type="hidden" ' +
                        'name="vnmailer_hcaptcha_sitekey" ' +
                        'value="' +
                            escapeHtmlLocal(hcaptchaSiteKey) +
                        '">'
                );

                var hcaptchaWrap =
                    hcaptchaField.querySelector('.vnm-hcaptcha-wrap');

                /*
                 * EDIT Tommy – untere hCaptcha-Border sichtbar machen
                 *
                 * hCaptcha erzeugt den eigentlichen iFrame erst nachträglich.
                 * Ein MutationObserver wartet auf diesen iFrame und gibt ihm
                 * zwei Pixel mehr Höhe. Der Inhalt wird dabei nicht skaliert.
                 *
                 * ORIGINALZUSTAND WIEDERHERSTELLEN:
                 * Den Aufruf fixHcaptchaFrame(hcaptchaWrap) weiter unten
                 * löschen oder auskommentieren.
                 */
                function fixHcaptchaFrame(wrapper) {
                    if (!wrapper) return;

                    var attempts = 0;
                    var maxAttempts = 100;

                    function applyFix() {
                        attempts++;

                        var iframe = wrapper.querySelector('iframe');

                        if (iframe) {
                            iframe.style.setProperty(
                                'display',
                                'block',
                                'important'
                            );

                            iframe.style.setProperty(
                                'height',
                                '80px',
                                'important'
                            );

                            iframe.style.setProperty(
                                'overflow',
                                'visible',
                                'important'
                            );

                            wrapper.style.setProperty(
                                'overflow',
                                'visible',
                                'important'
                            );

                            return;
                        }

                        if (attempts < maxAttempts) {
                            window.setTimeout(applyFix, 100);
                        }
                    }

                    applyFix();
                }

                /*
                 * Zum Wiederherstellen des Originalzustands diese Zeile
                 * löschen oder mit // auskommentieren:
                 */
                fixHcaptchaFrame(hcaptchaWrap);

                if (!document.querySelector(
                    'script[data-vnm-hcaptcha]'
                )) {
                    var hcScript =
                        document.createElement('script');

                    hcScript.src =
                        'https://js.hcaptcha.com/1/api.js';

                    hcScript.async = true;
                    hcScript.defer = true;

                    hcScript.setAttribute(
                        'data-vnm-hcaptcha',
                        '1'
                    );

                    document.head.appendChild(hcScript);
                }
            } else if (addCaptcha) {
                var captchaWrap = insertField(
                    '<label id="myLabel" style="' +
                        labelColorStyle +
                    '">' +
                        'Sicherheitsabfrage ' +
                        '<span style="color:' +
                            accentColor +
                        ';">*</span>' +
                    '</label>' +

                    '<div style="' +
                        'display:flex;' +
                        'align-items:center;' +
                        'gap:10px;' +
                        captchaBgStyle +
                        'border:1px dashed ' +
                            captchaBorderColor +
                        ';' +
                        'border-radius:6px;' +
                        'padding:10px 12px;' +
                    '">' +

                        '<span data-vnm-captcha-q style="' +
                            labelColorStyle +
                        '">' +
                            'Lade…' +
                        '</span>' +

                        '<input type="text" ' +
                            'name="captcha_answer" ' +
                            'required ' +
                            'style="' +
                                'width:70px;' +
                                'padding:6px;' +
                                'border:1px solid #ccc;' +
                                'border-radius:4px;' +
                                'font-size:' +
                                    fieldFontSize +
                                'px !important;' +
                            '">' +

                        '<input type="hidden" ' +
                            'name="captcha_token" ' +
                            'value="">' +
                    '</div>'
                );

                fetch(
                    base + 'captcha.php?t=' + Date.now(),
                    {
                        cache: 'no-store'
                    }
                )
                .then(function(r) {
                    return r.json();
                })
                .then(function(d) {
                    var qEl = captchaWrap.querySelector(
                        '[data-vnm-captcha-q]'
                    );

                    var tokenEl = captchaWrap.querySelector(
                        'input[name="captcha_token"]'
                    );

                    if (qEl) {
                        qEl.textContent =
                            d.question || '?';
                    }

                    if (tokenEl) {
                        tokenEl.value =
                            d.token || '';
                    }
                })
                .catch(function() {});
            }

            function escapeHtmlLocal(str) {
                var div = document.createElement('div');

                div.textContent = str || '';

                return div.innerHTML;
            }
        }

        // ── Erfolg/Fehler nach Seiten-Reload erkennen ─────────────────
        function showToast(message) {
            var toast = document.createElement('div');

            toast.className = 'vnm-toast';
            toast.textContent = message;

            document.body.appendChild(toast);

            requestAnimationFrame(function() {
                toast.classList.add('vnm-toast-show');
            });

            setTimeout(function() {
                toast.classList.remove('vnm-toast-show');

                setTimeout(function() {
                    toast.remove();
                }, 400);
            }, 4000);
        }

        function onFormSuccess() {
            if (thankYouUrl) {
                window.location.href = thankYouUrl;
            } else {
                showToast(toastMessage);
            }
        }

        function handleRedirectResult(form) {
            var urlParams =
                new URLSearchParams(window.location.search);

            var vnmStatus =
                urlParams.get('vnmailer');

            if (
                vnmStatus !== 'success' &&
                vnmStatus !== 'error'
            ) {
                return;
            }

            var targetId =
                urlParams.get('vnmailer_id') || '';

            if (
                targetId &&
                sec.id &&
                targetId !== sec.id
            ) {
                return;
            }

            if (
                scrollToForm &&
                form &&
                !(
                    vnmStatus === 'success' &&
                    thankYouUrl
                )
            ) {
                var targetSection =
                    form.closest('section') ||
                    form;

                targetSection.scrollIntoView({
                    behavior: 'auto',
                    block: 'start'
                });
            }

            if (vnmStatus === 'success') {
                onFormSuccess();
            } else {
                var errMsg =
                    urlParams.get('vnmailer_msg') ||
                    'Es ist ein Fehler aufgetreten.';

                showToast(errMsg);
            }

            cleanUrlParams();
        }

        function cleanUrlParams() {
            var url = new URL(window.location.href);

            url.searchParams.delete('vnmailer');
            url.searchParams.delete('vnmailer_msg');
            url.searchParams.delete('vnmailer_id');

            window.history.replaceState(
                {},
                '',
                url.toString()
            );
        }
    })();
    </script>
</section>
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Moin Tommy,

hab ich eingebaut, gute Idee, aber ich habe anstatt id=mylabel die CSS Klasse vnm-captcha-label genommen.
Mit einer id würde es bei mehreren Formularen auf der selben Seite Probleme geben ;)

Ist jetzt im Download aktuell ;)
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Ja prima - dann baue ich die nach dem Duschen auch nochmals neu ein.

Ja mit dieser Class (ID) wollte ich den Abstand ein wenig erhöhen. Die Beschriftung "Sicherheitsabfrage" klebte mir zu sehr am Captcha (vorher auch schon).
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Ja - schön und gut aber die ID hatte schon ihren Sinn (natürlich kann man dann keine zwei Formulare auf der gleichen Seite haben).

So wie Deine KI es umgeschrieben hat ist es falsch und wirkt gar nicht, da diese Klassen im JavaScript umgesetzt werden.

Unten nochmals der nun fertige und korrekte Code. Bitte lasse doch auch die jetzt korrekten Einrückungen vom Style und vom JavaScript so wie sie sind, Du rückst die nie ein und für einen Programmierer ist das sehr unschön und tut schon fast weh :eek:

Code: Alles auswählen

<section data-bs-version="5.1" class="vn-mailer" group="Content" id="{{mbr-id}}" data-addcopycheckbox="{{addCopyCheckbox}}" data-copycheckboxlabel="{{copyCheckboxLabel}}" data-addcaptcha="{{addCaptcha}}" data-captchatype="{{captchaType}}" data-hcaptchasitekey="{{hcaptchaSiteKey}}" data-hcaptchatheme="{{hcaptchaTheme}}" data-thankyouurl="{{thankYouUrl}}" data-toastmessage="{{toastMessage}}" data-textcolor="{{textColor}}" data-captchatransparent="{{captchaTransparent}}" data-captchabgcolor="{{captchaBgColor}}" data-captchabordercolor="{{captchaBorderColor}}" data-accentcolor="{{accentColor}}" data-fieldfontsize="{{fieldFontSize}}" data-scrolltoform="{{scrollToForm}}">
    <style>
        .vn-mailer {
            display: none;
        }

        html.is-builder .vn-mailer {
            display: block !important;
            padding: 20px;
            text-align: center;
            border: 2px dashed #e63946;
            border-radius: 8px;
            background: #fff5f5;
            color: #999;
            font-size: 13px;
        }

        .vn-mailer .vnm-editor-only {
            display: none;
        }

        html.is-builder .vn-mailer .vnm-editor-only {
            display: block;
        }

        .vnm-toast {
            position: fixed;
            bottom: 24px;
            left: 50%;
            transform: translateX(-50%) translateY(20px);
            background: #1d9e75;
            color: #fff;
            padding: 14px 24px;
            border-radius: 8px;
            box-shadow: 0 8px 24px rgba(0,0,0,0.2);
            font-family: system-ui, sans-serif;
            font-size: 14px;
            z-index: 999995;
            opacity: 0;
            transition: opacity 0.3s ease, transform 0.3s ease;
            pointer-events: none;
        }

        .vnm-toast.vnm-toast-show {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
        }

        /*
         * Von Tommy:
         * Beschriftung immer oberhalb des Captchas anzeigen.
         *
         * WICHTIG:
         * Hier darf NICHT ".vn-mailer .vnm-captcha-label" stehen,
         * weil das Label per JavaScript in das benachbarte Formular
         * eingefügt wird und deshalb nicht innerhalb von .vn-mailer liegt.
         *
         * Die Klasse darf auf einer Seite mehrfach verwendet werden.
         */
        .vnm-captcha-label {
            display: block !important;
            width: 100% !important;
            box-sizing: border-box;
            padding-top: 20px !important;
            padding-bottom: 10px !important;
            margin: 0 !important;
        }

        /*
         * Von Tommy:
         * hCaptcha immer unterhalb der Beschriftung anzeigen.
         * Gilt für das helle und das dunkle hCaptcha-Design.
         *
         * Auch dieser Wrapper liegt im benachbarten Formular und
         * darf deshalb nicht mit ".vn-mailer" davor angesprochen werden.
         */
        .vnm-hcaptcha-wrap {
            display: block !important;
            width: 100% !important;
            max-width: 100%;
            clear: both;
            box-sizing: border-box;
            line-height: 0;
            overflow: visible !important;
            padding-bottom: 2px;
        }

        .vnm-hcaptcha-wrap .h-captcha {
            display: inline-block !important;
            line-height: 0;
            overflow: visible !important;
        }

        .vnm-hcaptcha-wrap iframe {
            display: block !important;
            overflow: visible !important;
        }
    </style>

    <mbr-parameters>
        <header>Formular-Erweiterung</header>

        <input type="checkbox" title="'Kopie an mich'-Checkbox einfügen" name="addCopyCheckbox" checked>

        <input type="text" title="Checkbox-Text" name="copyCheckboxLabel" value="Kopie an mich senden">

        <input type="checkbox" title="Captcha einfügen" name="addCaptcha" checked>

        <select title="Captcha-Typ" name="captchaType">
            <option value="math">Mathe-Aufgabe (eigenes System)</option>
            <option value="hcaptcha" selected>hCaptcha</option>
        </select>

        <input type="text" title="hCaptcha Site-Key (öffentlich, von hcaptcha.com)" name="hcaptchaSiteKey" value="856eaa44-aec0-4fb6-9034-fd4a2dceacec" placeholder="z.B. 10000000-ffff-ffff-ffff-000000000001">

        <select title="hCaptcha-Design" name="hcaptchaTheme" condition="captchaType == 'hcaptcha'">
            <option value="light">Hell</option>
            <option value="dark" selected>Dunkel</option>
        </select>

        <header>Optik der eingefügten Felder</header>

        <input type="color" title="Textfarbe (Label/Checkbox-Text)" name="textColor" value="#ffffff" selected>

        <input type="checkbox" title="Captcha-Box transparent (kein eigener Hintergrund)" name="captchaTransparent" checked>

        <input type="color" title="Captcha-Box Hintergrund" name="captchaBgColor" value="#f0f4f8" condition="!captchaTransparent" selected>

        <input type="color" title="Captcha-Box Rahmen" name="captchaBorderColor" value="#b8c4d0" selected>

        <input type="color" title="Akzentfarbe (Pflichtfeld-Sterne)" name="accentColor" value="#ffffff" selected>

        <input type="range" inline title="Schriftgröße Label/Captcha (px)" name="fieldFontSize" min="10" max="24" step="1" value="18">

        <header>Nach dem Absenden</header>

        <input type="text" title="Danke-Seite (URL, leer = Toast-Meldung statt Weiterleitung)" name="thankYouUrl" value placeholder="/danke.html oder https://...">

        <input type="text" title="Toast-Meldung (falls keine Danke-Seite)" name="toastMessage" value="Danke für deine Nachricht!">

        <input type="checkbox" title="Zum Formular scrollen statt Seitenanfang (nach Absenden/Fehler)" name="scrollToForm" checked>
    </mbr-parameters>

    <div class="vnm-editor-only">
        🔧 VN Mailer<br>
        <small>Unsichtbar auf der Live-Seite. Rüstet das Formular direkt darüber oder darunter mit Kopie-Checkbox/Captcha/Danke-Seite nach.</small>
    </div>

    <script>
    (function() {
        if (document.documentElement.classList.contains('is-builder')) return;

        var sec = document.currentScript
            ? document.currentScript.closest('section.vn-mailer')
            : document.querySelector('section.vn-mailer');

        if (!sec) return;

        function attr(name) {
            var v = sec.getAttribute(name);

            if (!v || v.indexOf('{{') !== -1) {
                return '';
            }

            return v.trim();
        }

        function truthy(v) {
            return v === 'true' || v === 'on' || v === '1';
        }

        var addCopyCheckbox    = truthy(attr('data-addcopycheckbox'));
        var copyCheckboxLabel  = attr('data-copycheckboxlabel') || 'Kopie an mich senden';
        var addCaptcha         = truthy(attr('data-addcaptcha'));
        var captchaType        = attr('data-captchatype') || 'math';
        var hcaptchaSiteKey    = attr('data-hcaptchasitekey');
        var hcaptchaTheme      = attr('data-hcaptchatheme') || 'light';
        var thankYouUrl        = attr('data-thankyouurl');
        var toastMessage       = attr('data-toastmessage') || 'Danke für deine Nachricht!';
        var textColor          = attr('data-textcolor') || '#222222';
        var captchaTransparent = truthy(attr('data-captchatransparent'));
        var captchaBgColor     = attr('data-captchabgcolor') || '#f0f4f8';
        var captchaBorderColor = attr('data-captchabordercolor') || '#b8c4d0';
        var accentColor        = attr('data-accentcolor') || '#e63946';
        var fieldFontSize      = attr('data-fieldfontsize') || '14';
        var scrollToForm       = truthy(attr('data-scrolltoform'));

        /*
         * Nur die zwei offiziellen hCaptcha-Designwerte zulassen.
         */
        if (
            hcaptchaTheme !== 'dark' &&
            hcaptchaTheme !== 'light'
        ) {
            hcaptchaTheme = 'light';
        }

        var labelColorStyle =
            'color:' + textColor + ' !important;' +
            'font-size:' + fieldFontSize + 'px !important;';

        var captchaBgStyle = captchaTransparent
            ? 'background:transparent;'
            : 'background:' + captchaBgColor + ';';

        // ── Natives Formular in der Nachbarschaft finden ──────────────
        // Sucht bis zu 5 Geschwister-Elemente in jede Richtung ab,
        // falls beispielsweise noch ein Trenner-Block dazwischen liegt.
        function findNeighborForm() {
            var maxSteps = 5;

            var prevEl = sec;

            for (var i = 0; i < maxSteps; i++) {
                prevEl = prevEl.previousElementSibling;

                if (!prevEl) break;

                var formInPrev = prevEl.tagName === 'FORM'
                    ? prevEl
                    : prevEl.querySelector('form');

                if (formInPrev) {
                    return formInPrev;
                }
            }

            var nextEl = sec;

            for (var j = 0; j < maxSteps; j++) {
                nextEl = nextEl.nextElementSibling;

                if (!nextEl) break;

                var formInNext = nextEl.tagName === 'FORM'
                    ? nextEl
                    : nextEl.querySelector('form');

                if (formInNext) {
                    return formInNext;
                }
            }

            return null;
        }

        /*
         * Steht der VN-Mailer-Block vor dem Formular, existiert das
         * Formular beim Ausführen dieses Script-Tags möglicherweise
         * noch nicht im DOM. Deshalb erst nach vollständigem Laden suchen.
         */
        function initFormEnhancements() {
            var form = findNeighborForm();

            if (!form) {
                handleRedirectResult(null);
                return;
            }

            runEnhancements(form);
            handleRedirectResult(form);
        }

        if (document.readyState === 'loading') {
            document.addEventListener(
                'DOMContentLoaded',
                initFormEnhancements
            );
        } else {
            initFormEnhancements();
        }

        function runEnhancements(form) {

            function getAssetsBasePath() {
                var ref = document.querySelector(
                    'link[href*="assets/"], script[src*="assets/"]'
                );

                if (ref) {
                    var url =
                        ref.getAttribute('href') ||
                        ref.getAttribute('src');

                    var idx = url.indexOf('assets/');

                    if (idx !== -1) {
                        return url.substring(
                            0,
                            idx + 'assets/'.length
                        );
                    }
                }

                return 'assets/';
            }

            var base = getAssetsBasePath() + 'vn-mailer/';

            // Container, in den neue Felder eingefügt werden.
            var dragArea =
                form.querySelector('.dragArea') ||
                form;

            var submitBtnWrap =
                dragArea.querySelector('.mbr-section-btn');

            function insertField(html) {
                var wrap = document.createElement('div');

                wrap.className =
                    'col-lg-12 col-md-12 col-sm-12 form-group mb-3';

                wrap.innerHTML = html;

                if (submitBtnWrap) {
                    dragArea.insertBefore(
                        wrap,
                        submitBtnWrap
                    );
                } else {
                    dragArea.appendChild(wrap);
                }

                return wrap;
            }

            /*
             * Verstecktes Feld mit der Block-ID.
             *
             * Damit kann bei mehreren VN-Mailer-Instanzen auf einer
             * Seite zugeordnet werden, welches Formular abgesendet wurde.
             */
            var idField = document.createElement('input');

            idField.type = 'hidden';
            idField.name = 'vnmailer_id';
            idField.value = sec.id || '';

            form.appendChild(idField);

            // ── „Kopie an mich“-Checkbox einfügen ─────────────────────
            if (addCopyCheckbox) {
                insertField(
                    '<label style="' +
                        'display:flex;' +
                        'align-items:center;' +
                        'gap:8px;' +
                        'font-weight:400;' +
                        labelColorStyle +
                    '">' +
                        '<input type="checkbox" ' +
                            'name="copy_to_sender" ' +
                            'style="width:auto;"> ' +
                        escapeHtmlLocal(copyCheckboxLabel) +
                    '</label>'
                );
            }

            // ── Captcha einfügen ───────────────────────────────────────
            if (
                addCaptcha &&
                captchaType === 'hcaptcha' &&
                hcaptchaSiteKey
            ) {
                var hcaptchaField = insertField(
                    '<label class="vnm-captcha-label" style="' +
                        labelColorStyle +
                    '">' +
                        'Sicherheitsabfrage ' +
                        '<span style="color:' +
                            accentColor +
                        ';">*</span>' +
                    '</label>' +

                    '<div class="vnm-hcaptcha-wrap">' +
                        '<div class="h-captcha" ' +
                            'data-sitekey="' +
                                escapeHtmlLocal(hcaptchaSiteKey) +
                            '" ' +
                            'data-theme="' +
                                escapeHtmlLocal(hcaptchaTheme) +
                            '">' +
                        '</div>' +
                    '</div>' +

                    '<input type="hidden" ' +
                        'name="vnmailer_hcaptcha_sitekey" ' +
                        'value="' +
                            escapeHtmlLocal(hcaptchaSiteKey) +
                        '">'
                );

                if (!document.querySelector(
                    'script[data-vnm-hcaptcha]'
                )) {
                    var hcScript =
                        document.createElement('script');

                    hcScript.src =
                        'https://js.hcaptcha.com/1/api.js';

                    hcScript.async = true;
                    hcScript.defer = true;

                    hcScript.setAttribute(
                        'data-vnm-hcaptcha',
                        '1'
                    );

                    document.head.appendChild(hcScript);
                }

                /*
                 * hCaptcha erzeugt seinen iFrame erst nachträglich.
                 * Kurz warten und dann etwas mehr Höhe geben, damit
                 * der untere Rahmen nicht abgeschnitten wird.
                 *
                 * Der Inhalt wird dabei nicht skaliert.
                 */
                (function fixHcaptchaFrame(wrapper, attemptsLeft) {
                    if (!wrapper || attemptsLeft <= 0) return;

                    var iframe = wrapper.querySelector('iframe');

                    if (iframe) {
                        iframe.style.setProperty(
                            'display',
                            'block',
                            'important'
                        );

                        iframe.style.setProperty(
                            'height',
                            '80px',
                            'important'
                        );

                        iframe.style.setProperty(
                            'overflow',
                            'visible',
                            'important'
                        );

                        wrapper.style.setProperty(
                            'overflow',
                            'visible',
                            'important'
                        );

                        return;
                    }

                    setTimeout(function() {
                        fixHcaptchaFrame(
                            wrapper,
                            attemptsLeft - 1
                        );
                    }, 100);

                })(
                    hcaptchaField.querySelector(
                        '.vnm-hcaptcha-wrap'
                    ),
                    100
                );

            } else if (addCaptcha) {

                var captchaWrap = insertField(
                    '<label class="vnm-captcha-label" style="' +
                        labelColorStyle +
                    '">' +
                        'Sicherheitsabfrage ' +
                        '<span style="color:' +
                            accentColor +
                        ';">*</span>' +
                    '</label>' +

                    '<div style="' +
                        'display:flex;' +
                        'align-items:center;' +
                        'gap:10px;' +
                        captchaBgStyle +
                        'border:1px dashed ' +
                            captchaBorderColor +
                        ';' +
                        'border-radius:6px;' +
                        'padding:10px 12px;' +
                    '">' +

                        '<span data-vnm-captcha-q style="' +
                            labelColorStyle +
                        '">' +
                            'Lade…' +
                        '</span>' +

                        '<input type="text" ' +
                            'name="captcha_answer" ' +
                            'required ' +
                            'style="' +
                                'width:70px;' +
                                'padding:6px;' +
                                'border:1px solid #ccc;' +
                                'border-radius:4px;' +
                                'font-size:' +
                                    fieldFontSize +
                                'px !important;' +
                            '">' +

                        '<input type="hidden" ' +
                            'name="captcha_token" ' +
                            'value="">' +
                    '</div>'
                );

                fetch(
                    base + 'captcha.php?t=' + Date.now(),
                    {
                        cache: 'no-store'
                    }
                )
                .then(function(r) {
                    return r.json();
                })
                .then(function(d) {
                    var qEl = captchaWrap.querySelector(
                        '[data-vnm-captcha-q]'
                    );

                    var tokenEl = captchaWrap.querySelector(
                        'input[name="captcha_token"]'
                    );

                    if (qEl) {
                        qEl.textContent =
                            d.question || '?';
                    }

                    if (tokenEl) {
                        tokenEl.value =
                            d.token || '';
                    }
                })
                .catch(function() {});
            }

            function escapeHtmlLocal(str) {
                var div = document.createElement('div');

                div.textContent = str || '';

                return div.innerHTML;
            }
        }

        // ── Erfolg/Fehler nach echtem Seiten-Reload erkennen ─────────
        /*
         * native-form-handler.php leitet nach dem Absenden per Redirect
         * zurück auf diese Seite und hängt ?vnmailer=success/error an.
         */
        function showToast(message) {
            var toast = document.createElement('div');

            toast.className = 'vnm-toast';
            toast.textContent = message;

            document.body.appendChild(toast);

            requestAnimationFrame(function() {
                toast.classList.add('vnm-toast-show');
            });

            setTimeout(function() {
                toast.classList.remove('vnm-toast-show');

                setTimeout(function() {
                    toast.remove();
                }, 400);
            }, 4000);
        }

        function onFormSuccess() {
            if (thankYouUrl) {
                window.location.href = thankYouUrl;
            } else {
                showToast(toastMessage);
            }
        }

        function handleRedirectResult(form) {
            var urlParams =
                new URLSearchParams(window.location.search);

            var vnmStatus =
                urlParams.get('vnmailer');

            if (
                vnmStatus !== 'success' &&
                vnmStatus !== 'error'
            ) {
                return;
            }

            /*
             * Bei mehreren VN-Mailer-Blöcken auf einer Seite nur
             * reagieren, wenn diese Instanz tatsächlich gemeint ist.
             */
            var targetId =
                urlParams.get('vnmailer_id') || '';

            if (
                targetId &&
                sec.id &&
                targetId !== sec.id
            ) {
                return;
            }

            /*
             * Zum Formular-Block scrollen statt zum Seitenanfang.
             * Nicht bei Erfolg mit einer eigenen Danke-Seite.
             */
            if (
                scrollToForm &&
                form &&
                !(
                    vnmStatus === 'success' &&
                    thankYouUrl
                )
            ) {
                var targetSection =
                    form.closest('section') ||
                    form;

                targetSection.scrollIntoView({
                    behavior: 'auto',
                    block: 'start'
                });
            }

            if (vnmStatus === 'success') {
                onFormSuccess();
            } else {
                var errMsg =
                    urlParams.get('vnmailer_msg') ||
                    'Es ist ein Fehler aufgetreten.';

                showToast(errMsg);
            }

            cleanUrlParams();
        }

        function cleanUrlParams() {
            /*
             * URL-Parameter entfernen, damit beim Neuladen
             * die Meldung nicht erneut erscheint.
             */
            var url = new URL(window.location.href);

            url.searchParams.delete('vnmailer');
            url.searchParams.delete('vnmailer_msg');
            url.searchParams.delete('vnmailer_id');

            window.history.replaceState(
                {},
                '',
                url.toString()
            );
        }
    })();
    </script>
</section>

Die ganzen Kommentare "Von Tommy" kannst du natürlich gerne entfernen lassen - die sind nur für mein Verständnis gedacht.

Weiterhin ist mir aufgefallen das der geheime "Secret Code" vom hCaptcha nach der Neuinstallation bereits im Site-Menü eingetragen war - wie ist das möglich, bzw. das dürfte ja eigentlich nicht sein.
Benutzeravatar
Tommy Herrmann
Site Admin
Site Admin
Beiträge: 9257
Registriert: So 6. Dez 2020, 07:37
Wohnort: Berlin
Kontaktdaten:

Re: VN Mailer

Ungelesener Beitrag von Tommy Herrmann »

Volker,

habe da eine Frage, die Du mir wahrscheinlich beantworten kannst.

Ich möchte - für mich jetzt ganz individuell - den Text im Header von meinem Formular etwas anpassen.


Neue Nachricht.jpg


Ich möchte also z.B. in Zeile 212 vom Script (Screenshot oben): "native-form-handler.php" den Text ändern in:

Code: Alles auswählen

$mail->Subject = 'Neue Nachricht über das Kontaktformular VN Mailer';
OK, wenn ich das in diesem Script ändere und auf den Server kopiere geht das auch, jedoch wird das nach jedem Publizieren wieder überschrieben, obwohl ich auch die Datei im Verzeichnis der Erweiterungen "vn-mailer" geändert habe.

Wo zum Teufel kommt das denn sonst noch her :angst: :confused:
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

Tommy Herrmann hat geschrieben: So 26. Jul 2026, 09:37

Wo zum Teufel kommt das denn sonst noch her :angst: :confused:
Aus der app.js - ich ändere das jetzt alles ;)

Warte...


Warum das so ist: Alle PHP Scripte werden aus der app.js generiert. Da steht dann natürlich auch wieder der alte Betreff. Aber in der aktuellen Version ja jetzt durch Eintrag im Zahnrad geändert ;)
Volker
Moderator
Moderator
Beiträge: 3253
Registriert: Sa 12. Dez 2020, 22:35

Re: VN Mailer

Ungelesener Beitrag von Volker »

So,

nun umgebaut :

Der Captcha Schlüssel bleibt stehen. Wenn Du ein neues Projekt startest musst auch wieder neuen Schlüssel eingeben.
Der wird in der Projektdatei gespeichert - ist ungefährlich - da kann niemand was mit anfangen ;)
Du kannst den Betreff jetzt im Zahnrad einstellen
Es gibt eine Leerzeile zwischen Sicherheitsabfrage und Captcha
Code aufgeräumt samt deiner Kommentare ;)

Jetzt aber Feierabend :prost:

PS: Wer die Leerzeile nicht will, kann das <br> auch löschen im Code.
Auch den Text Sicherheitsabfrage kann man da ändern - hier Spamschutz ohne die Leezeile:
https://www.niederastroth.de/vntabs/

Also es kann jeder, jederzeit auch den Code des Blocks ändern- aber vorsicht bei den Java Befehlen und Funktionen ;)
Antworten

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 5 Gäste