Remove the rest of the innerHTML text after ellipsis

The text is truncated by CSS rules text-overflow: ellipsis; and -webkit-line-clamp:3; but when the text is longer and goes to next row, sometimes the next row is showing few pixels. element.innerHTML doesn’t show the three dots from ellipsis, so I can’t detect it with regex and remove it.

How can I remove the remaining text from html element after ellipsis is added?

In the example the height of the div should stay the same

let ele = document.getElementById('header-inner');

ele.innerHTML = 'There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which  ';
#header-inner {
  height: 80px;
  display: -webkit-box;
  font-size: 15px;
  font-weight: normal;
  font-style: normal;
  text-decoration: none;
  border-width: 1px;
  max-height: 62px;
  text-overflow: ellipsis;
  overflow: hidden;
  -moz-box-orient: vertical;
  white-space: unset;
  -webkit-line-clamp: 3;
  padding-top: 0px;
}
<div id='header-inner'></div>

The GET method is not supported for route crea/attività/professionale. Supported methods: PATCH trustproxies error

I’m reporting this problem which only occurs with the Chrome browser while it works correctly on all other browsers. and I don’t understand why the complete code is below.

This error occurs only in the chrome browser while with all other browsers everything works as it should. The problem only occurs when I want to save the recorded audio. The error is highlighted in the trustproxies.php file at the code return $next($request). I don’t understand why this problem only occurs with Chrome while for example Mozilla everything works normally. I hope someone can help me understand where the problem is, thanks

Route::middleware('auth', 'admin')->group(function () {
    Route::patch('/salva/attivita/specifica', [CreaAttivitàController::class, 'store'])->middleware(['auth', 'verified'])->name('salva.attività');
    Route::patch('/crea/attività/professionale', [CreaAttivitàController::class, 'indexProfessionale'])->middleware(['auth', 'verified'])->name('update.manutenzione.professionale');
    Route::post('/salva/attività/professionale', [CreaAttivitàController::class, 'storeProfessionale'])->middleware(['auth', 'verified'])->name('salva.attività.professionale');
    Route::patch('/modifica/attività/professionale', [CreaAttivitàController::class, 'modificaProfessionale'])->middleware(['auth', 'verified'])->name('modifica.attività.professionale');
});

<form action="{{ route('salva.attività.professionale') }}" method="POST"
enctype="multipart/form-data">
@csrf
 @method('POST')
<input type="button" id="recordButton" />
 <input type="file" id="hiddenAudioInput" name="audios[]"
 multiple hidden />
</form>

public function storeProfessionale(Request $request)
    {
        $area = $request->area;
        $qr_linea = $request->qr_linea;
        $linea = Linee::where('qr_code', $qr_linea)->value('nome_linea');
        $qr_blocco = $request->qr_blocco;
        $blocco = BloccoLinee::where('qr_code', $qr_blocco)->value('nome_blocco');
        $qr_macchina = $request->qr_macchina;
        $macchina = $request->macchina;
        $frequenza = "personalizzata";
        $ciclo = $request->ciclo;
        $start = $request->start;
        $durata = $request->durata;
        $stato = "Da fare";
        $step_id_value = $request->step_id;
        $step = $request->step;
        $permessi_1 = $request->checked;
        $permessi = array($permessi_1);
            $attività_id = Attivita::create([
                'area' => $area,
                'linea' => $linea,
                'blocco_linea' => $blocco,
                'macchina' => $macchina,
                'frequenza' => $frequenza,
                'ciclo' => $ciclo,
                'start' => $start,
                'stato' => $stato,
                'qr_code' => $qr_macchina,
                'qr_macchina' => $qr_macchina,
                'qr_blocco' => $qr_blocco,
                'qr_linea' => $qr_linea,
                'durata' => $durata
            ])->id;
            Attivita::where('id', $attività_id)->update(['permessi' => $permessi]);
            $step_id = Step::create([
                'attivita_id' => $attività_id,
                'step_id' => $step_id_value,
                'step' => $step
            ])->id;
            $request->validate([
                'audios.*' => 'nullable|file|mimes:ogg,mp3,m4a,mp4,mpeg,mpga,wav,aac',
            ]);         
            if ($request->hasFile('audios')) {
                foreach ($request->file('audios') as $audio) {
                    $audioName = $audio->hashName();
                    $audio->storeAs('audio', $audioName, 'private');
                    Audio::create([
                        'attivita_id' => $attività_id,
                        'step_id' => $step_id,
                        'audio' => htmlspecialchars($audioName, ENT_QUOTES, 'UTF-8')
                    ]);
                }
            }
            $notifiche = Aree::sum('attività');
            $item_step = Attivita::with(['step'])
                ->where('id', $attività_id)
                ->first();
            $step_count = Step::where('attivita_id', $attività_id)->get()->last()->step_id + 1;
            $macchina_item = Macchine::where('qr_code', $qr_macchina)->get();
            return $this->indexProfessionale($request)->with(['notifiche' => $notifiche, 'macchina' => $macchina_item, 'data_frequenza' => $start, 'ciclo' => $ciclo, 'step' => $step_count, 'item_step' => $item_step])
                ->withErrors(['msgSuccess' => "Step creato correttamente"]);
    }

let mediaRecorder;
let audioChunks = [];
const recordButton = document.getElementById('recordButton');
const audioGallery = document.getElementById('audioGallery');
recordButton.addEventListener('click', async () => {
    if (!mediaRecorder) {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            mediaRecorder = new MediaRecorder(stream);
            mediaRecorder.ondataavailable = event => {
                audioChunks.push(event.data);
            };
            mediaRecorder.onstop = () => {
                const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
                const audioUrl = URL.createObjectURL(audioBlob);
                audioChunks = [];
                addAudioToGallery(audioUrl);
            };
            mediaRecorder.start();
            recordButton.classList.add('recording');
            recordButton.textContent = 'Ferma Registrazione';
        } catch (error) {
            console.error('Errore durante la registrazione:', error);
            alert('Error: ' + error.message);
        }
    } else {
        mediaRecorder.stop();
        recordButton.classList.remove('recording');
        recordButton.textContent = 'Avvia Registrazione';
        mediaRecorder = null;
    }
});
function addAudioToGallery(audioUrl) {
    const audioElement = document.createElement('audio');
    audioElement.controls = true;
    audioElement.src = audioUrl;
    audioGallery.appendChild(audioElement);
}

function addAudioToGallery(audioUrl) {
    const audioItem = document.createElement('div');
    audioItem.classList.add('audio-item');
    const audioElement = document.createElement('audio');
    audioElement.controls = true;
    audioElement.src = audioUrl;
    audioElement.name = "audios[]";
    const removeBtn = document.createElement('button');
    removeBtn.classList.add('remove-btn-record');
    const imgDelete = document.createElement('img');
    imgDelete.src = "/css/external/Icone/Delete.svg";
    imgDelete.id = "delete";
    imgDelete.alt = "Rimuovi";
    removeBtn.appendChild(imgDelete);
    removeBtn.onclick = () => {
        audioGallery.removeChild(audioItem);
        // Rimuovi anche il file dall'input nascosto
        removeAudioFromInput(audioUrl);
    };
    audioItem.appendChild(audioElement);
    audioItem.appendChild(removeBtn);
    audioGallery.appendChild(audioItem);
    addAudioToInput(audioUrl);
}

function addAudioToInput(audioUrl) {
    const hiddenInput = document.getElementById('hiddenAudioInput');
    fetch(audioUrl)
    .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok: ' + response.statusText);
        }
        return response.blob();
    })
    .then(blob => {
        const file = new File([blob], 'audio_' + Date.now() + '.wav', { type: 'audio/wav' });
        const dataTransfer = new DataTransfer();
        for (const file of hiddenInput.files) {
            dataTransfer.items.add(file);
        }
        dataTransfer.items.add(file);
        hiddenInput.files = dataTransfer.files;
    })
    .catch(error => {
        console.error('Error fetch dell'audio:', error);
    });
}

function removeAudioFromInput(audioUrl) {
    const hiddenInput = document.getElementById('hiddenAudioInput');
    const dataTransfer = new DataTransfer();
    for (const file of hiddenInput.files) {
        if (!file.name.includes(audioUrl)) { // Assicurati di avere un modo per identificare il file
            dataTransfer.items.add(file);
        }
    }
    hiddenInput.files = dataTransfer.files;
}

/**
     * The trusted proxies for this application.
     *
     * @var array<int, string>|string|null
     */
    protected $proxies;

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers =
    Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB;

    /**
     * Add Content Security Policy headers to accept everything already present in your Laravel app.
     *
     * @param IlluminateHttpRequest $request
     * @param Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $response->headers->set('Content-Security-Policy', "default-src'self'");

        $response->headers->add([
            'Content-Security-Policy' => "default-src 'self'; object-src 'self'; base-uri 'self'; report-uri 'self';",
        ]);

        $response->headers->set('Permissions-Policy', "geolocation=()");

        $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');

        return $response;
    }

The GET method is not supported for route crea/attività/professionale. Supported methods: PATCH trustproxies error

I’m reporting this problem which only occurs with the Chrome browser while it works correctly on all other browsers. and I don’t understand why the complete code is below.

This error occurs only in the chrome browser while with all other browsers everything works as it should. The problem only occurs when I want to save the recorded audio. The error is highlighted in the trustproxies.php file at the code return $next($request). I don’t understand why this problem only occurs with Chrome while for example Mozilla everything works normally. I hope someone can help me understand where the problem is, thanks

Route::middleware('auth', 'admin')->group(function () {
    Route::patch('/salva/attivita/specifica', [CreaAttivitàController::class, 'store'])->middleware(['auth', 'verified'])->name('salva.attività');
    Route::patch('/crea/attività/professionale', [CreaAttivitàController::class, 'indexProfessionale'])->middleware(['auth', 'verified'])->name('update.manutenzione.professionale');
    Route::post('/salva/attività/professionale', [CreaAttivitàController::class, 'storeProfessionale'])->middleware(['auth', 'verified'])->name('salva.attività.professionale');
    Route::patch('/modifica/attività/professionale', [CreaAttivitàController::class, 'modificaProfessionale'])->middleware(['auth', 'verified'])->name('modifica.attività.professionale');
});

<form action="{{ route('salva.attività.professionale') }}" method="POST"
enctype="multipart/form-data">
@csrf
 @method('POST')
<input type="button" id="recordButton" />
 <input type="file" id="hiddenAudioInput" name="audios[]"
 multiple hidden />
</form>

public function storeProfessionale(Request $request)
    {
        $area = $request->area;
        $qr_linea = $request->qr_linea;
        $linea = Linee::where('qr_code', $qr_linea)->value('nome_linea');
        $qr_blocco = $request->qr_blocco;
        $blocco = BloccoLinee::where('qr_code', $qr_blocco)->value('nome_blocco');
        $qr_macchina = $request->qr_macchina;
        $macchina = $request->macchina;
        $frequenza = "personalizzata";
        $ciclo = $request->ciclo;
        $start = $request->start;
        $durata = $request->durata;
        $stato = "Da fare";
        $step_id_value = $request->step_id;
        $step = $request->step;
        $permessi_1 = $request->checked;
        $permessi = array($permessi_1);
            $attività_id = Attivita::create([
                'area' => $area,
                'linea' => $linea,
                'blocco_linea' => $blocco,
                'macchina' => $macchina,
                'frequenza' => $frequenza,
                'ciclo' => $ciclo,
                'start' => $start,
                'stato' => $stato,
                'qr_code' => $qr_macchina,
                'qr_macchina' => $qr_macchina,
                'qr_blocco' => $qr_blocco,
                'qr_linea' => $qr_linea,
                'durata' => $durata
            ])->id;
            Attivita::where('id', $attività_id)->update(['permessi' => $permessi]);
            $step_id = Step::create([
                'attivita_id' => $attività_id,
                'step_id' => $step_id_value,
                'step' => $step
            ])->id;
            $request->validate([
                'audios.*' => 'nullable|file|mimes:ogg,mp3,m4a,mp4,mpeg,mpga,wav,aac',
            ]);         
            if ($request->hasFile('audios')) {
                foreach ($request->file('audios') as $audio) {
                    $audioName = $audio->hashName();
                    $audio->storeAs('audio', $audioName, 'private');
                    Audio::create([
                        'attivita_id' => $attività_id,
                        'step_id' => $step_id,
                        'audio' => htmlspecialchars($audioName, ENT_QUOTES, 'UTF-8')
                    ]);
                }
            }
            $notifiche = Aree::sum('attività');
            $item_step = Attivita::with(['step'])
                ->where('id', $attività_id)
                ->first();
            $step_count = Step::where('attivita_id', $attività_id)->get()->last()->step_id + 1;
            $macchina_item = Macchine::where('qr_code', $qr_macchina)->get();
            return $this->indexProfessionale($request)->with(['notifiche' => $notifiche, 'macchina' => $macchina_item, 'data_frequenza' => $start, 'ciclo' => $ciclo, 'step' => $step_count, 'item_step' => $item_step])
                ->withErrors(['msgSuccess' => "Step creato correttamente"]);
    }

let mediaRecorder;
let audioChunks = [];
const recordButton = document.getElementById('recordButton');
const audioGallery = document.getElementById('audioGallery');
recordButton.addEventListener('click', async () => {
    if (!mediaRecorder) {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            mediaRecorder = new MediaRecorder(stream);
            mediaRecorder.ondataavailable = event => {
                audioChunks.push(event.data);
            };
            mediaRecorder.onstop = () => {
                const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
                const audioUrl = URL.createObjectURL(audioBlob);
                audioChunks = [];
                addAudioToGallery(audioUrl);
            };
            mediaRecorder.start();
            recordButton.classList.add('recording');
            recordButton.textContent = 'Ferma Registrazione';
        } catch (error) {
            console.error('Errore durante la registrazione:', error);
            alert('Error: ' + error.message);
        }
    } else {
        mediaRecorder.stop();
        recordButton.classList.remove('recording');
        recordButton.textContent = 'Avvia Registrazione';
        mediaRecorder = null;
    }
});
function addAudioToGallery(audioUrl) {
    const audioElement = document.createElement('audio');
    audioElement.controls = true;
    audioElement.src = audioUrl;
    audioGallery.appendChild(audioElement);
}

function addAudioToGallery(audioUrl) {
    const audioItem = document.createElement('div');
    audioItem.classList.add('audio-item');
    const audioElement = document.createElement('audio');
    audioElement.controls = true;
    audioElement.src = audioUrl;
    audioElement.name = "audios[]";
    const removeBtn = document.createElement('button');
    removeBtn.classList.add('remove-btn-record');
    const imgDelete = document.createElement('img');
    imgDelete.src = "/css/external/Icone/Delete.svg";
    imgDelete.id = "delete";
    imgDelete.alt = "Rimuovi";
    removeBtn.appendChild(imgDelete);
    removeBtn.onclick = () => {
        audioGallery.removeChild(audioItem);
        // Rimuovi anche il file dall'input nascosto
        removeAudioFromInput(audioUrl);
    };
    audioItem.appendChild(audioElement);
    audioItem.appendChild(removeBtn);
    audioGallery.appendChild(audioItem);
    addAudioToInput(audioUrl);
}

function addAudioToInput(audioUrl) {
    const hiddenInput = document.getElementById('hiddenAudioInput');
    fetch(audioUrl)
    .then(response => {
        if (!response.ok) {
            throw new Error('Network response was not ok: ' + response.statusText);
        }
        return response.blob();
    })
    .then(blob => {
        const file = new File([blob], 'audio_' + Date.now() + '.wav', { type: 'audio/wav' });
        const dataTransfer = new DataTransfer();
        for (const file of hiddenInput.files) {
            dataTransfer.items.add(file);
        }
        dataTransfer.items.add(file);
        hiddenInput.files = dataTransfer.files;
    })
    .catch(error => {
        console.error('Error fetch dell'audio:', error);
    });
}

function removeAudioFromInput(audioUrl) {
    const hiddenInput = document.getElementById('hiddenAudioInput');
    const dataTransfer = new DataTransfer();
    for (const file of hiddenInput.files) {
        if (!file.name.includes(audioUrl)) { // Assicurati di avere un modo per identificare il file
            dataTransfer.items.add(file);
        }
    }
    hiddenInput.files = dataTransfer.files;
}

/**
     * The trusted proxies for this application.
     *
     * @var array<int, string>|string|null
     */
    protected $proxies;

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers =
    Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB;

    /**
     * Add Content Security Policy headers to accept everything already present in your Laravel app.
     *
     * @param IlluminateHttpRequest $request
     * @param Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $response->headers->set('Content-Security-Policy', "default-src'self'");

        $response->headers->add([
            'Content-Security-Policy' => "default-src 'self'; object-src 'self'; base-uri 'self'; report-uri 'self';",
        ]);

        $response->headers->set('Permissions-Policy', "geolocation=()");

        $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');

        return $response;
    }

Getting Missing biometric data as specified in “Uses” error with finguer print authntication

I’m taking finguerprint data with JS code and pass to UIDI for validation but it’s sending back below error

Missing biometric data as specified in “Uses”

How to resolved this error ?

var XML = ' <PidOptions ver="1.0"> <Opts fCount="1" fType="0" iCount="0" pCount="0" format="0" pidVer="2.0" timeout="10000" posh="UNKNOWN" env="P" /> ' + DemoFinalString + ' </PidOptions>';

var verb = "CAPTURE";
var err = "";

var res;
$.support.cors = true;
var httpStaus = false;
var jsonstr = "";
$.ajax({

    type: "CAPTURE",
    async: false,
    crossDomain: true,
    url: finalUrl + MethodCapture,
    data: XML,
    contentType: "text/xml; charset=utf-8",
    processData: false,
    success: function (data) {}

Is there a way to hide the url info box that is show by browsers while hovering over a hyperlink? [duplicate]

In our project we want to show some hyperlinks, On hovering we can see a little box with the url for that link(chrome and firefox), But since it give ups lots of details we want to disable that. How can we do that? I understand it’s for user safety to know what they are clicking on, but as a genuine website with authentication we want to have this feature.

Any way that works. We are using html, tailwind css, next js

Getting Missing biometric data as specified in “Uses” error with finguer print authntication

I’m taking finguerprint data with JS code and pass to UIDI for validation but it’s sending back below error

Missing biometric data as specified in “Uses”

How to resolved this error ?

var XML = ' <PidOptions ver="1.0"> <Opts fCount="1" fType="0" iCount="0" pCount="0" format="0" pidVer="2.0" timeout="10000" posh="UNKNOWN" env="P" /> ' + DemoFinalString + ' </PidOptions>';

var verb = "CAPTURE";
var err = "";

var res;
$.support.cors = true;
var httpStaus = false;
var jsonstr = "";
$.ajax({

    type: "CAPTURE",
    async: false,
    crossDomain: true,
    url: finalUrl + MethodCapture,
    data: XML,
    contentType: "text/xml; charset=utf-8",
    processData: false,
    success: function (data) {}

How to catch failed to fech errors in javascript? [closed]

I was trying to fetch data from my backend then I notice whenever my server was down it keeps showing big red error on screen which is annoying. I want my user to keep using website even if failed to fech and just show it on DOM. How do you make error not show on website, is there a trick to it?

import React, { useEffect, useState } from 'react'
export default function Home() {
    const [fechError, setFecherror] = useState()
    const fechData = async () => {
        console.log('j')
        let data;
        try {
            data = await fetch('http://localhost:3000/home')
        } catch (err) {
            console.clear()
            setFecherror("fail to fech data please try again")
        }
        if (data) {
            const user = await data.json()
            console.log(user)
        } else {
            console.log('no')

        }

    }

    useEffect(() => {
        fechData()
    })
    return (
        <div className='maintainHeight'>
            this is home
            {fechError}
        </div>
    )
}

I tried using try and catch but it’s not going away. I can’t find any docs to solve it.

HTML button to remote control server

I use on device simple http server using busybox(httpd) at the same time, this device can be controlled via http on port 436, to control mediaplayer.

IP:436/sendremotekey?%7B%22key%22%3A%22VUP%22%7D

As a client I tried find a way to create a button to make some action using link as above, I have prepared a button:

<button type="button">
  <a href="/sendremotekey?%7B%22key%22%3A%22VUP%22%7D" onclick="javascript:event.target.port=436">VOL</a>
</button>

… of course this opens a link..but is it possible to prepare a button that will invisibly execute the command/link after click?

Greetings

Web push, pushsubscriptionchange browser support?

I am trying to understand browser support for event pushsubscriptionchange.

I found this old blog post of chrome (2016) saying that support starts from version 54.

On MDN page it’s reported that only Safari desktop fully supports it.

CanIUse reports that only Safari desktop and Safari iOS supports it, while Chrome as never done.

Blog post from SitePoint 2018 seems to say that the event is widely supported.

web.dev (wich is Google) on teaching how to handle subscription expiration does not cite it.

Dpi-1080 connection was closed by ORA-41409

I am getting below when I am running the application, if you restart the node app error will not come and everything works fine, this error occurs only few times. Dpi-1080 connection was closed by ORA-41409.
Poolsize: min: 2 max: 18
Iam using node js and knex and oracle
Versions:

Node: 16.20.0
Knex: “^2.4.2”
Oracledb: “^5.1.0”

I should not get any connection related issues and application should work fine

How to add a fixed watermark on a video in fullscreen mode using an iframe in HTML?

I am working on a project that requires displaying a video in fullscreen mode within an iframe. I want to add a watermark on the video that remains fixed in the center even when fullscreen is activated. The issue is that the default styles applied by the browser when entering fullscreen mode affect the layout of the elements, causing the watermark to change its position.

When I enter fullscreen mode, the properties of .watermark are affected, causing it to change its position or not appear correctly. How can I ensure that the watermark stays fixed in the center even when switching to fullscreen mode?

I am using HTML, CSS, and JavaScript.
I have tried adjusting styles, but it seems that there are default (user agent styles) affecting the layout of the elements.

I implemented a watermark overlay on a video inside an iframe that switches to fullscreen mode. I expected the watermark to remain centered on the video even in fullscreen. However, the watermark’s position shifts unexpectedly due to the browser’s default styles, and it sometimes gets hidden behind other elements or doesn’t scale properly in fullscreen mode.

Having issues fetching data from Mailchimp in Sveltekit App

I can find little to no information online about this particular issue.

I’m essentially trying to connect my sveltekit app to a campaign list in Mailchimp.

I’ve had to take some code from a google app script file, and modify it for svelte. The data is being pulled in fine via the google apps script, which I’ve just tested.

The API key I have in my .envfile is correct, but I can’t seem to figure out why no data is being fetched.

<script context="module">
  export async function load() {
    const apiKey = process.env.MAILCHIMP_API_KEY;
    const serverPrefix = apiKey.split('-')[1];
    const REPORT_START_DATE = '2022-01-01 00:00:00'; // Adjust as needed

    const campaignListUrl = `https://${serverPrefix}.api.mailchimp.com/3.0/campaigns?count=500&since_send_time=${REPORT_START_DATE}`;

    try {
      const response = await fetch(campaignListUrl, {
        method: 'GET',
        headers: {
          'Authorization': `apikey ${apiKey}`,
          'Content-Type': 'application/json',
        },
      });

      if (!response.ok) {
        throw new Error(`Error fetching Mailchimp campaigns: ${response.statusText}`);
      }

      const responseData = await response.json();
      console.log('Campaigns Response:', responseData);

      const campaigns = await Promise.all(
        responseData.campaigns.map(async (campaign) => {
          const reportsUrl = `https://${serverPrefix}.api.mailchimp.com/3.0/reports/${campaign.id}`;
          const reportsResponse = await fetch(reportsUrl, {
            method: 'GET',
            headers: {
              'Authorization': `apikey ${apiKey}`,
              'Content-Type': 'application/json',
            },
          });

          if (!reportsResponse.ok) {
            throw new Error(`Error fetching report for campaign ${campaign.id}: ${reportsResponse.statusText}`);
          }

          const reportsData = await reportsResponse.json();

          const unsubscribeRate = reportsData.unsubscribed / reportsData.emails_sent || 0;
          const hardBounces = reportsData.bounces.hard_bounces;
          const softBounces = reportsData.bounces.soft_bounces;
          const totalBounces = hardBounces + softBounces;

          return {
            id: campaign.id,
            title: campaign.settings.title,
            subject: campaign.settings.subject_line,
            send_time: campaign.send_time,
            emails_sent: reportsData.emails_sent,
            abuse_reports: reportsData.abuse_reports,
            unsubscribed: reportsData.unsubscribed,
            unsubscribe_rate: unsubscribeRate,
            hard_bounces: hardBounces,
            soft_bounces: softBounces,
            bounces: totalBounces,
            syntax_errors: reportsData.bounces.syntax_errors,
            forwards_count: reportsData.forwards.forwards_count,
            forwards_opens: reportsData.forwards.forwards_opens,
            opens_total: reportsData.opens.opens_total,
            unique_opens: reportsData.opens.unique_opens,
            open_rate: reportsData.opens.open_rate,
            last_open: reportsData.opens.last_open,
            clicks_total: reportsData.clicks.clicks_total,
            unique_clicks: reportsData.clicks.unique_clicks,
            unique_subscriber_clicks: reportsData.clicks.unique_subscriber_clicks,
            click_rate: reportsData.clicks.click_rate,
          };
        })
      );

      return { props: { campaigns } };
    } catch (error) {
      console.error('Error fetching Mailchimp data:', error);
      return { props: { campaigns: [] } }; 
    }
  }
</script>

<script>
  export let campaigns = []; 
</script>

<h1>Mailchimp Campaigns</h1>
{#if campaigns.length > 0} 
  <ul>
    {#each campaigns as campaign}
      <li>
        <h2>{campaign.title}</h2>
        <p>Subject: {campaign.subject}</p>
        <p>Send Time: {campaign.send_time}</p>
        <p>Emails Sent: {campaign.emails_sent}</p>
        <p>Abuse Reports: {campaign.abuse_reports}</p>
        <p>Unsubscribed: {campaign.unsubscribed}</p>
        <p>Unsubscribe Rate: {(campaign.unsubscribe_rate * 100).toFixed(2)}%</p>
        <p>Hard Bounces: {campaign.hard_bounces}</p>
        <p>Soft Bounces: {campaign.soft_bounces}</p>
        <p>Total Bounces: {campaign.bounces}</p>
        <p>Syntax Errors: {campaign.syntax_errors}</p>
        <p>Forwards Count: {campaign.forwards_count}</p>
        <p>Forwards Opens: {campaign.forwards_opens}</p>
        <p>Opens Total: {campaign.opens_total}</p>
        <p>Unique Opens: {campaign.unique_opens}</p>
        <p>Open Rate: {campaign.open_rate}</p>
        <p>Last Open: {campaign.last_open}</p>
        <p>Clicks Total: {campaign.clicks_total}</p>
        <p>Unique Clicks: {campaign.unique_clicks}</p>
        <p>Unique Subscriber Clicks: {campaign.unique_subscriber_clicks}</p>
        <p>Click Rate: {campaign.click_rate}</p>
      </li>
    {/each}
  </ul>
{:else}
  <p>No campaigns found.</p> 
{/if}

Does console.log() convert its arguments to a string before printing them to the console?

In JavaScript The definitive guide 7th edition, page 317, The Console API the author clearly says that console.log():

…converts its arguments to strings and outputs them to the console.

However, MDN says that:

A representation of each of these values is output to the console in the order given with some type of separation between each of them.

Further research has revealed that the display of the arguments in the console is implementation-dependent. I am inclined to disagree with the author(s) of The definitive guide in this situation, however I want to be sure that they are wrong on this particular occasion.

I have checked the Console API “specification” and it didn’t say anything about the arguments being converted to strings.

Quill is not able to render HTML content

I have a small app with an embedded Quill Rich Text Editor. When loading the page I’m receiving HTML code from an external system, the sample content I get is

'<span style="font-weight: bold;">big </span><span style="font-weight: normal;font-style: italic;">big </span><span style="font-weight: normal;text-decoration: underline;">big</span><span style="font-weight: bold;"><br/>n  <span style="font-style: italic;"></span></span>'

This looks like valid HTML to me, I can render it

<span style="font-weight: bold;">big </span><span style="font-weight: normal;font-style: italic;">big </span><span style="font-weight: normal;text-decoration: underline;">big</span><span style="font-weight: bold;"><br/>n  <span style="font-style: italic;"></span></span>

Unfortunately Quill is not able to render it, I can reproduce it in their playground. Head over to the playground and add the following line at the end of the index.js file

quill.root.innerHTML = '<span style="font-weight: bold;">big </span><span style="font-weight: normal;font-style: italic;">big </span><span style="font-weight: normal;text-decoration: underline;">big</span><span style="font-weight: bold;"><br/>n  <span style="font-style: italic;"></span></span>'

You can see that the actual output is

enter image description here

which is wrong…

What’s wrong with the HTML code? Or is my code wrong? ( quill.root.innerHTML = ... , I took the example code from the VueQuill repository )

Because this sample code

<p>This is <b>Max Mustermann</b></p><img src='https://4.img-dpreview.com/files/p/E~TS590x0~articles/3925134721/0266554465.jpeg'>

works as expected

enter image description here