Angular: ng-container not displayed

I have an ng-container that must show in case some observable has a value, and if not, a ng-template must show:

  <ng-container *ngIf="obs$ | async; else template">
    <router-outlet></router-outlet>
  </ng-container>
  <ng-template #template>
    <div>
      No data
    </div>
  </ng-template>

However when I log the value of the observable after subscribing to it, I see that it actually has a value, so I don’t understand what the issue is here.

Here is how the obs$ observable is set:

  obs$: Observable<Promise<SomeClass | null>> =
    this.obs2$.pipe(
      map(async (foo: SomeOtherClass | null) => {
        if (!foo) {
          return null;
        }
        const { id } = foo;
        const bar = await this.someService.getBarById(
          id
        );
        return bar;
      })
    );

how to use Jalali date in antd date picker Version 5

I am using ant design version 5 and I want to use a date picker. How can I use Jalaliday with ant design version 5. From my latest information there is antd-jalali for this purpose but I guess it is not compatible with latest version of antd v5. When I try to render a component from the library, I get this error

Uncaught TypeError: generateConfig2[fn] is not a function

Although there are other libraries that might fixed this issue or provide similar services, I want to know is there any way to use jalali day in antd version 5 ?

(for example my months be like Farvardin(فروردین) and etc.)

Program doesn’t end after async function is called

I am just starting with postgresql.

I have successfully made connection with cloud database and retrieved data using nodejs ‘postgres’ package, but the program doesn’t end after it logs all the data on console, it’s still running. Why is that?

import postgres from 'postgres'
import "dotenv/config"

const sql = postgres({
  host: process.env.DATABASE_HOST,
  database: process.env.DATABASE_NAME,
  username: process.env.DATABASE_USER,
  password: process.env.DATABASE_PASSWORD,
  ssl: 'require',
})

async function getInfo() {
  const users = await sql`
  select * from information_schema.tables
  `

  return users
}

getInfo().then((data) => console.log(data))

Problem in vue nginx.conf, requests are not working

there is such a code

const urls = {
  users: "/users/api/v1/user",
  roles: "/users/api/v1/role",
};

and

location /users/ {
    proxy_pass  https://$USER_URL/;
    proxy_redirect off;
    proxy_ssl_protocols TLSv1.2 TLSv1.3;
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_intercept_errors  off;
    proxy_buffer_size       4k;
    proxy_buffers           4 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
    proxy_read_timeout 120;
    proxy_connect_timeout 120;
    client_max_body_size 20M;
  }

location /api/ {
    proxy_pass  https://$BACKEND_URL/api/;
    proxy_redirect off;
    proxy_ssl_protocols TLSv1.2 TLSv1.3;
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_intercept_errors  off;
    proxy_buffer_size       4k;
    proxy_buffers           4 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
    proxy_read_timeout 120;
    proxy_connect_timeout 120;
    client_max_body_size 20M;
  }

but for some reason it redirects me to location /api/, has anyone encountered this problem?

I tried to write the location separately, tried to change the paths but it didn’t work, although other methods of the service (location /users/) work, here

const urls = {
  login: "/users/api/v1/auth/login",
  refreshToken: "/users/api/v1/auth/refresh",
  validate: "/users/api/v1/auth/validate",
  change_pwd: "/users/api/v1/auth/change-password",
};

Cannot show background events in resource timeline view

I have a fullcalendar component in Vue application and cannot show background events in resource timeline view even if in month view it works!
I don’t get any errors, simply background events are loaded correctly but not shown.

This is my configuration:

<FullCalendar :key="calendarKey" v-if="planningOptions" ref="planning" :options="planningOptions" >
  <template v-slot:eventContent="arg">
    <div :title="arg.event.title">{{ arg.event.title }}</div>
  </template>
</FullCalendar>`


this.planningOptions = {
  plugins: [ resourceTimelinePlugin, dayGridPlugin, listPlugin, interactionPlugin ],
  aspectRatio: 3,
  themeSystem: 'standard',
  headerToolbar: {
    left: 'prev,next today',
    center: 'title',
    right: 'resourceTimelineDay,resourceTimelineWeek,dayGridMonth,listWeek'
  },
  slotLabelClassNames: 'text-center',
  resourceOrder: 'title',
  resources: this.initUsersResource,
  eventSources: [
    {
      events: this.initEvents,
      className: 'event',
    },
    {
      events: this.initNationalHolidays,
      className: 'national-holiday',
    },
  ],
  initialView: 'resourceTimelineWeek',
  resourceAreaWidth: '15%',
  slotMinWidth: '50',
  slotDuration: '01:00:00',
  slotMaxTime: '24:00',
  slotMinTime: '7:00',
  businessHours: {
    daysOfWeek: [ 1, 2, 3, 4, 5, 6, 7 ],
    startTime: '7:00',
    endTime: '24:00',
  },
  navLinks: true,
  nowIndicator: true,
  firstDay: 1,
  locale: 'it',
  timeZone: 'Europe/Rome',
  weekends: true,
  contentHeight: 'calc(100vh - 280px)',
  eventClick: this.showEvent,
  dateClick: this.createEvent,
}

This is my background event object:

{
  "allDay": true,
  "start": "2024-01-01 00:00:00",
  "end": "2024-01-01 23:59:59",
  "title": "Capodanno",
  "display": "background",
  "editable": false,
  "extendedProps": {
    "context": "EVENT"
  },
  "resourceId": null
},

I tried to set:

"resourceId": null
"resourceId": 0
"resourceId": ""
"resourceId": "X" --> a real resource ID

I’d like to show background events in resource timeline too.

Am I missing something or is not possible? I haven’t found any limitation warning in documentation.

Thanks

Undefined params in costructor

I have a problem with following up with this tutorial https://www.youtube.com/watch?v=H9rHrlNTpq8&list=PLzMcBGfZo4-kCLWnGmK0jUBmGLaJxvi4j&index=7, because in my case in Room class constructor does not see params, or should I say they are undefined.

If somebody could please help me understand why they are undefined I would appreciate it.

Below is my Room.js class and HomePage with routing

export default class Room extends Component {
    constructor(props) {
        super(props);
        this.state = {
            votesToSkip: 2,
            guestCanPause: false,
            isHost: false,
        };

        this.roomCode = this.props.match.params.roomCode; //Here compiler is giving error that params is undefined

    }

render() {
        return (
            <div>
                <h3>{this.props.roomCode}</h3>
                <p>{this.state.votesToSkip}</p>
                <p>
                    Guest can pause:{" "}
                    {this.state.guestCanPause !== undefined
                        ? this.state.guestCanPause.toString()
                        : "Loading..."}
                </p>
                <p>Host: {this.state.isHost.toString()}</p>
            </div>
        );
    }
}

And here is my HomePage.js

export default class HomePage extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <Router>
                <Routes>
                    <Route
                        exact
                        path="/"
                        element={<p>This is the home page</p>}
                    ></Route>
                    <Route path="/join" element={<RoomJoinPage />} />
                    <Route path="/create" element={<CreateRoomPage />} />
                    <Route path="/room/:roomCode" element={<Room />} />
                </Routes>
            </Router>
        );
    }
}

For the record, I am using React 17.0.0 and React-dom 17.0.0 because some of the UI libraries in this tutorial were not compatible with React 18. And here is the exact error I’m getting

Uncaught TypeError: Cannot read properties of undefined (reading ‘params’)
at new Room (Room.js:17:38)

google recaptcha for multiple forms

so here I will include the html and JS code. Kindly suggest what changes or modifications should I do so that the recaptcha works for the multiple forms submit

<button class="spec" data-toggle="modal" th:data-target="'#exampleModal_'+${mou.productCatalogId}">
   Datasheet
</button>

the above button is to trigger the modals based on productCatalogId

<div th:each="mou,iterationStatus: ${productCatalogImplList}">       
            <div class="modal left fade" th:id="'exampleModal_'+${mou.productCatalogId}" tabindex="-1"
                    aria-labelledby="exampleModalLabel" aria-hidden="true">
                    <div class="modal-dialog w-100">
                        <div class="modal-content manufacturerModal">
                            <div class="modal-header manufacturerModalHeader border-0">
                                <h5 class="modal-title text-dark manufacturerModalh5"
                                    id="exampleModalLabel">Please Tell Us More About Yourself</h5>
                                <button type="button" class="close py-1 closeMobView"
                                    data-dismiss="modal" aria-label="Close">
                                    <span aria-hidden="true" class="close-times2">&times;</span>
                                </button>
                            </div>
                            <div class="modal-body pt-0 manufacturerModalBody" th:id="'manufacturerPopUp'+${manufacturer.manufacturerId}" >
                                <blc:form name="bridgeluxBroucher" th:id="'bridgeluxBroucher_'+${mou.productCatalogId}"
                                    action="/submitBroucherEnquiryForm2" method="POST">
                                    <div>
                                        <div class="form-group col px-0">
                                            <input type="hidden" name="manufacturerId"
                                                th:value="${mou.manufacturerIdEncrypted}" />
                                            <input type="hidden" name="categoryId"
                                                th:value="${mou.categoryIdEncrypted}" />
                                            <input type="hidden" name="typeBroucher" value="About Us" />
                                            <input type="hidden" name="manufacturerName" th:value="${manufacturer.manuFacturerName}" />
                                            <input type="hidden" name="mainCategoryName" th:value="${mainSubCatagoryBredScrumb.mainCategoryName}" />
                                            <input type="hidden" name="mainSubCategoryName" th:value="${mainSubCatagoryBredScrumb.mainSubCategoryName}" />
                                            <input type="hidden" name="categoryName" th:value="${categoryName}" />
                                            <input type="hidden" name="manufacturerPartBroucher" th:value="${mou.manufacturerPart}" />
                                            <input type="hidden" name="dataSheet" th:id="'dataSheetUrl'+${mou.productCatalogId}"
                                    th:value="${mou.dataSheet}" />
                                             
                                             
                                            <label>Name <b><span class="pencil-dashboard">*</span></b></label>
                                            <input type="text" class="form-control" name="nameBroucher"
                                                th:id="'nameBroucher_'+${mou.productCatalogId}" th:value="${session.customer.fullName}" readonly/>
                                        </div>
                                        
                                    </div>
                                    <div  class="g-recaptcha" data-sitekey="6LeEWI8aAAAAAJd1hEk72AGlDtvAHVO6uuyzjv3V" >
                                    </div>
                            
                                    <input type="hidden" name="authBroucher" id="authBroucher"/>
                                    <input type="hidden" name="selectrecaptchabroucher" id="selectrecaptchabroucher" value="0"/>
                                    
                                    <div class="modal-footer border-0 px-0 mt-5 pt-5 pb-0">
                                        <button type="button" id="mfrKnowMore"
                                            th:onclick="'broucherFormSubmit('' + ${mou.productCatalogId} + '');'" class="details">Submit</button>
                                    </div>
                                </blc:form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

so above is my modal inside which I have a form and google recaptcha corresponding to each productCatalog which are dynamically generated

function checkBroucherRecaptcha() {
      var response = grecaptcha.getResponse();
     document.getElementById("authBroucher").value=response;
      if(response.length == 0) { 
          alert("no pass"); 
      }
      else { 
          document.getElementById("selectrecaptchabroucher").value=1;
          
      }
    }
function broucherFormSubmit(productCatalogId) {
        checkBroucherRecaptcha();
        var form = document.getElementById("bridgeluxBroucher_" + productCatalogId);
        var valid = 1;

        if (document.getElementById("nameBroucher_" + productCatalogId).value == "") {
            alert("Please enter name");
            document.getElementById("nameBroucher_" + productCatalogId).focus();
            return false;
            valid = 0;
        }

        if(document.getElementById("selectrecaptchabroucher").value==0){
          alert("Please confirm you are not robot");
          document.getElementById("selectrecaptchabroucher").focus(); 
          return false; 
          valid = 0;
     }
        
        if (confirm("are you sure you want to submit?")) {
            if (valid == 1) {
                form.submit();

            }
        }

    }

above is my JS code. Kindly suggest what should I do so that every form is getting submitted after checking the recaptcha as it’s not working for every form. I know I have included a lot of content just for better understanding.

Which is better, Adding a single custom event to handle all the use-cases, for creating separte custom events for each use cases? [closed]

Let say I want to create custom event that gets triggered on change of certain entities like name,phone-number, address etc… What would be the right way to handle this?

1st Approach:
Create a single custom event and handle all the entity changes in 1 callback

// custom event
  document.dispatchEvent(
    new window.CustomEvent(`ENTITY_UPDATE`, {
      detail: { type: `name || phone || address` }
    })
  )

//listener for the custom events 

document.addEventListner(`ENTITY_UPDATE`, ({detail:{type}}) => {
switch(type) {

case `name`: //handle name change

case `phone`: //handle phone change

case `address`: //handle address change

}

})

2nd Approach:
Create separate custom events for each of the entities, and have separate callbacks for them.

// custom event for Team
  document.dispatchEvent(
    new window.CustomEvent(`NAME_UPDATE`, {
      detail: {data }
    })
  )
// event handler

document.addEventListner(`NAME_UPDATE`, ({detail:data}) => {//handle team data})

// similarly we create separate custom events for phone and address

I want to handle all the entity changes independently. What would be the right way to handle this?

I just wanted to get an opinion on what would be the best approach to follow here.

How to set html code as header and footer using html2pdf – jspdf javascript plugin?

html2pdf().from(element).set(opt).toPdf().get('pdf').then(function (pdf) {
 var totalPages = pdf.internal.getNumberOfPages(); 
 console.log("getHeight:" + pdf.internal.pageSize.getHeight());
 console.log("getWidth:" + pdf.internal.pageSize.getWidth());
 for (var i = 1; i <= totalPages; i++) {
   pdf.setPage(i);
   pdf.setFontSize(10);
   pdf.setTextColor(150);
   //divided by 2 to go center
   pdf.text('Page ' + i + ' of ' + totalPages, pdf.internal.pageSize.getWidth()/2, 
   pdf.internal.pageSize.getHeight()/ 2);
 } 
 }).save();

I am using this code and it is working fine but instead of plain text I wanted to show some HTML code (actually I want to repeat the code of Header of my original HTML code on every page) but it is not working. Is there any way so that I can add HTML code just like the text here. I just simply wanted to show the table header on each page after the page break. I am using react 14 and the library to generate PDF is html2pdf 0.9.0*

HTML button doesn’t remember the mobile input option chosen when pressed the first time anymore

    <div>
  <input type="file" #fileInput style="display: none;" (change)="onFileSelect($event)" multiple accept=".tif,.tiff,.pdf,.jpg,.jpeg" />
  <input type="file" #cameraInput style="display: none;" (change)="onFileSelect($event)" accept=".tif,.tiff,.pdf,.jpg,.jpeg" capture />
  <div id="buttonContainer">
    <div *ngFor="let button of uploadButtons" class="upload-buttons">
      <button type="button" (click)="onButtonClick(button)" class="btn wizard-btn"
        [ngClass]="notComplete(button)?'empty':''" [disabled]="disabled">
        <span class="counter" *ngIf="typeCount(button)"> {{ typeCount(button) }}</span>
        <img [src]="button.iconPath" height="30">
        <div class="uploadButtonTitle" *ngIf="!settingsService.queryStringData?.fromWinPer"> {{ button.title }} </div>
        <div class="uploadButtonTitle" *ngIf="settingsService.queryStringData?.fromWinPer"> {{ button.titleWinper ||
          button.title }} </div>
      </button>
    </div>
  </div>
</div>

import { Component, ViewChild, ElementRef, EventEmitter, Output, Input, HostListener, NgZone } from '@angular/core';
import { Utilities } from '../../shared/utilities';
import { ImageService } from '../../services/image.service';
import { SettingsService } from '../../services/settings.service';
import { UploadButton, BeahviorButton } from '../../models/uploadButton';
import { Attachment, enumCloudAttachmentTypes, enumCloudAttachmentSubTypes } from '../../models/attachment';
import { StoreDossierService } from '../../services/store-dossier.service';
import { WebcamComponent, WebcamImage } from 'ngx-webcam';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Message, MessageService } from 'primeng/api';
import { ToastSeveritiesEnum } from '../../shared/constants';

@Component({
  selector: 'app-file-easy-upload',
  templateUrl: './file-easy-upload.component.html',
  styleUrls: ['./file-easy-upload.component.css']
})

export class FileEasyUploadComponent {
  @ViewChild('fileInput') fileInput: ElementRef;
  @ViewChild('cameraInput') cameraInput: ElementRef;
  @Input() uploadButtons: UploadButton[];
  @Input() documentsWithType: boolean;    //se true i documenti viene valorizzato il campo workType per superare i controlli di obbligatorietà     
  @Input() disabled: boolean;                          //
  @Output() messageEmitted = new EventEmitter<string>();          // messaggio emesso all'esterno del componente per eventuali toast
  @Output() imageEmitted = new EventEmitter<Attachment>();        // immagine selezionata / scattata emessa all'esterno
  @Output() buttonClickEvent = new EventEmitter<UploadButton>();  // immagine selezionata / scattata emessa all'esterno
  wait: boolean;
  toastMsg: Message[];
  showSmartImage: boolean;
  get printDate(): boolean {
    return Utilities.printDateTime;
  }
  get printCoords(): boolean {
    return Utilities.printCoords;
  }
  get triggerObservable(): Observable<void> {
    return this.trigger.asObservable();
  }
  showWebcam: boolean = false;
  landscapeMode: BehaviorSubject<boolean> = new BehaviorSubject(false);
  clickDisabled: boolean;
  buttonClicked: UploadButton;
  canvas: HTMLCanvasElement;
  camWidth: number;
  camHeight: number;
  private trigger: Subject<void> = new Subject<void>();
  videoOption: MediaTrackConstraints = {
    width: { ideal: 4096 },
    height: { ideal: 2160 },
    //aspectRatio: 1.5,
    facingMode: 'environment'
  };


  constructor(private imageService: ImageService, public settingsService: SettingsService, private storeDossier: StoreDossierService,
    public zone: NgZone, private toastService: MessageService,) { }

  ngOnInit(): void {
    this.canvas = <HTMLCanvasElement>document.getElementById('myCanvas');
    this.onOrientationChange();
  }

  @HostListener('window:orientationchange', ['$event'])
  onOrientationChange() {
    this.camWidth = Math.min(520, 1.5 * (screen.availHeight - 60));
    this.landscapeMode.next(screen.availWidth >= screen.availHeight);
    this.camHeight = this.camWidth / 1.5;
  }

  /**
  * sul click del pulsante su interfaccia HTML
  */
  onButtonClick(button: UploadButton) {
    this.buttonClicked = button;
    this.showWebcam = false;
    this.showSmartImage = false;
    switch (this.buttonClicked.beahvior) {
      case BeahviorButton.camera:
        this.openCamera();
        break;
      case BeahviorButton.gallery:
        this.openGallery();
        break;
      case BeahviorButton.webcam:
        this.showWebcam = true;
        setTimeout(() => {
          this.showSmartImage = true;
        }, 500);
        break;
      case BeahviorButton.event:
        this.buttonClickEvent.emit(this.buttonClicked)
        break;
      default:
        break;
    }
  }

  /**
   * evento emesso dagli input nei quali vengono caricate le immagini (selezionate o scattate) con i dati exif e gps stamapti sopra
   */
  onFileSelect(evt: any) {
    const filesUploaded: File[] = evt.target.files;
    let array = []
    for (let i = 0; i < filesUploaded.length; i++) {
      array.push(filesUploaded[i]);
    }
    array.sort((a,b)=>{
      if (a.lastModified < b.lastModified ) 
        return -1;
      return 1;  
    })
    for (let i = 0; i < filesUploaded.length; i++) {
      const file: File = filesUploaded[i];
      // verifica se il tipo di file è consentito
      if (!this.isAllowedFileType(file)) {
        this.toastMsg = [];
        this.toastMsg.push({ detail: 'Formato file non supportato: ' + file.name, severity: ToastSeveritiesEnum.warn });
        this.toastService.addAll(this.toastMsg);
        continue; // ignora questo file e passa al successivo
      }
      if (Attachment.isImgFile(file.name)) {
        this.processImage(file).then(data => {
          if (data && data.url) {
            let attachment = this.fileToAttachment(file, data.url);
            attachment.gpsFromMobile = data.gpsIncluded;
            attachment.fiveSecondLimit = data.fiveSecondLimit;
            this.imageEmitted.emit(attachment);
          } else {
            this.messageEmitted.emit('Errore generico');
          }
        });
      }
      else {
        this.processDoc(file).then(url => {
          if (url) {
            let attachment = this.specialFileToSpecialAttachment(file, url);
            this.imageEmitted.emit(attachment);
          }
        })
      }
    }
  }

  isAllowedFileType(file: File): boolean {
    const allowedTypes = ['.tif', '.tiff', '.pdf', '.jpg', '.jpeg'];
    const extension = '.' + file.name.split('.').pop();
    return allowedTypes.includes(extension);
  }


  /**
   * disegna su canvas l'immagine con i dati gps
   */
  imageToDraw(dataUrl: string, fileDate: Date, gpsLocationFound: boolean, fileName: string): Promise<string> {
    return new Promise((resolve, reject) => {
      let img: HTMLImageElement = new Image();
      img.src = dataUrl;
      let exifObj = this.imageService.getExifData(dataUrl);
      let text4print = this.printInfo(exifObj, fileDate);
      if (!this.canvas){
        this.canvas = <HTMLCanvasElement>document.getElementById('myCanvas');
      }
      img.onload = () => {
        this.canvas.width = img.width;
        this.canvas.height = img.height;
        let ctx: CanvasRenderingContext2D = this.canvas.getContext('2d');
        ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        ctx.drawImage(img, 0, 0);
        this.writeOnCanvas(ctx, text4print, img);
        let baseUrl = this.canvas.toDataURL('image/jpeg');
        baseUrl = this.imageService.setExifData(exifObj, baseUrl);
        resolve(baseUrl);
      };
    })
  }

  typeCount(item: UploadButton): number {
    if (!this.storeDossier.attachments) { return 0 };
    let result = 0;
    /*
    if (item.type == ButtonTypes.allegatiGenerici) {
      result += this.storeDossier.attachments.filter(att => att.attachmentTypeId == enumCloudAttachmentTypes.Allegati).length;
    }
    */
    result += this.storeDossier.attachments.filter(att => (att.workType || att.attachmentSubTypeId) == item.cloudAttachmentSubType).filter(_ => !_.isHidden).length;
    return result;
  }

  notComplete(item: UploadButton): boolean {
    return !this.typeCount(item);
  }

  // metodi privati
  private openCamera() {
    let el = <HTMLElement>this.cameraInput.nativeElement;
    el.click();
    setTimeout(() => this.activateBtn(), 1000);
  }

  private openGallery() {
    let el = <HTMLElement>this.fileInput.nativeElement;
    el.click();
    setTimeout(() => this.activateBtn(), 1000);
  }

  private activateBtn(): void {
    this.clickDisabled = false;
  }

  /**
   * Processa un file di tipo immagine con ridimensionamento e acquisizione dati exif e gps
   */
  private processImage(fileSelected: File): Promise<{ url: string, gpsIncluded: boolean, fiveSecondLimit: boolean }> {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      Utilities.readFile(fileSelected, reader, (result) => {
        let img = new Image();
        img.src = result;
        let maxResolution = Utilities.maxResolution;
        this.imageService.resize(img, maxResolution, maxResolution).then((dataUrl) => {
          let exifObj = this.imageService.getExifData(dataUrl);
          // In caso di mancanza dati GPS originali sulla foto, li inserisce se la foto è stata
          // scattata non più di 60 secondi fa (caso per scatto con macchina fotografica direttamente da browser)
          let endDate = new Date();
          let fileDate = this.imageService.getExifDateTimeOriginal(exifObj);

          if (!fileDate || fileDate.toDateString() == 'Invalid Date') {
            fileDate = new Date(fileSelected.lastModified);
          }

          let seconds = (endDate.getTime() - fileDate.getTime()) / 1000;
          if (isNaN(seconds)) {
            seconds = -1;
          }
          let fiveSecondLimit = seconds < 6;
          let gpsLocationFound = this.imageService.gpsLocationFound(exifObj);
          if (!gpsLocationFound && (seconds >= 0 && seconds <= 60) && !this.imageService.disabledGPS) {
            this.imageService.setGPSLocation(dataUrl, exifObj).subscribe((_) => {
              this.imageToDraw(_, fileDate, true, fileSelected.name).then(url => {
                if (url) {
                  resolve({ url, gpsIncluded: true, fiveSecondLimit: fiveSecondLimit });
                }
              });
            });
          } else {
            this.imageToDraw(dataUrl, fileDate, gpsLocationFound, fileSelected.name).then(url => {
              if (url) {
                resolve({ url, gpsIncluded: false, fiveSecondLimit: fiveSecondLimit });
              }
            });
          }
        });
      });
    })
  }

  /**
   * Processa un documento di tipo non immagine (f.e. pdf) e risolve la sorgente in byte del file
   */
  private processDoc(fileSelected: File): Promise<string> {
    return new Promise((resolve, reject) => {
      let maxMbSize = parseFloat(this.settingsService.data.jsonSettings.maxMbSizeAttachment);
      let reader = new FileReader();
      Utilities.readFile(fileSelected, reader, (result) => {
        if (fileSelected.size > maxMbSize * 1048256) {
          this.messageEmitted.emit('Il file supera le dimensioni consentite (' + maxMbSize + 'Mb)');
        }
        else {
          resolve(result);
        }
      });
    })
  }

  private writeOnCanvas(ctx: CanvasRenderingContext2D, text: string, img: HTMLImageElement) {
    let fontSize = this.imageService.getImageFont(img);
    ctx.font = fontSize + 'px Helvetica';
    ctx.strokeStyle = 'black';
    ctx.fillStyle = 'rgb(248, 166, 0)';
    let x = fontSize;
    let y = img.height - fontSize;
    ctx.fillText(text, x, y);
    ctx.strokeText(text, x, y);
  }

  /**
   * conversione dal tipo File a Attachment
   */
  private fileToAttachment(file: File, src: string): Attachment {
    let result = new Attachment();
    result.fileName = this.buttonClicked.fileName + Attachment.fileExtension(file.name);
    result.fileSize = src.length;
    result.base64 = src;
    result.attachmentLocation = this.buttonClicked.cloudAttachmentLocation;
    result.attachmentTypeId = this.buttonClicked.cloudAttachmentType;
    result.attachmentSubTypeId = this.buttonClicked.cloudAttachmentSubType;
    return result;
  }

  /**
   * Conversione da file pdf o word a Attachment
   */
  private specialFileToSpecialAttachment(file: File, src: string): Attachment {
    let result = new Attachment();
    result.fileName = file.name;
    result.fileSize = src.length;
    result.base64 = src;
    result.attachmentLocation = this.buttonClicked.cloudAttachmentLocation;
    if (this.documentsWithType) {
        result.workType = this.buttonClicked.cloudAttachmentSubType;
    }
    result.attachmentTypeId = enumCloudAttachmentTypes.Allegati;
    const isPDF = file.type === 'application/pdf';
    result.attachmentSubTypeId = isPDF ? enumCloudAttachmentSubTypes.PDF : enumCloudAttachmentSubTypes.NonDefinito;
    
    return result;
}




  private printInfo(exifObj, markDate: Date): string {
    let text: string = '';
    let markGPS = this.imageService.getGPSData(exifObj);
    //DATA
    if (this.printDate && markDate) {
      text += Utilities.toDateTimeString(markDate) + '  ';
    }
    //GPS
    if (this.printCoords && markGPS) {
      text += markGPS;
    }
    return text;
  }

  public onImageCaptured(image: any, webcam: WebcamComponent) {
    if (this.wait) return;
    this.wait = true;
    let att = new Attachment();
    att.fileName = this.buttonClicked.fileName + '.jpg';
    att.base64 = image.imageAsDataUrl;
    att.fileSize = att.base64.length;
    att.attachmentLocation = this.buttonClicked.cloudAttachmentLocation;
    att.attachmentTypeId = 1;
    att.attachmentSubTypeId = this.buttonClicked.cloudAttachmentSubType;
    this.messageEmitted.emit('risoluzione: ' + image._imageData.width * image._imageData.height / 1000000 + 'MPix')
    this.imageEmitted.emit(att);
    this.closeCam(webcam);
    this.wait = false;
  }

  public triggerSnapshot(): void {
    this.trigger.next();
  }

  public closeCam(webcam: WebcamComponent): void {
    (webcam.nativeVideoElement as HTMLVideoElement).pause();
    (webcam.nativeVideoElement as HTMLVideoElement).src = null;
    ((webcam.nativeVideoElement as HTMLVideoElement).srcObject as MediaStream).getTracks().forEach(track => {
      track.stop();
    });
    (webcam.nativeVideoElement as HTMLVideoElement).srcObject = null;
    this.showWebcam = false;
  }
}

Hey guys, got a little bit of a problem here for which I can’t find a solution alone. Basically, when clicking this button from a mobile device, it used to ask if one would input the file from your camera or from your saved files, and it used to remember what you chose. I didn’t change anything for this component (it’s in an Angular environment), but after the last update a couple weeks ago it doesn’t remember the option you chose anymore, asking you everytime if you want to use your camera or tour saved files. What could be the problem? I’ve tested with every browser I have (Chrome, Firefox, Edge, Safari and Opera) and the problem persists.

Adding whitespace before window.getSelection() removes it when new node is inserted in selection

I’m building a URL Link functionality to selected text in my app.

  1. I select a text.
  2. Click on Link button.
  3. Enter URL in modal popup.
  4. Click save.
  5. I again click on Link button.
  6. Update URL in modal popup.
  7. Click save.

So far so good. I was able to create and update a URL to a selected text in my html. However this breaks when I add empty space before selected text. For example,

  1. I select the word “highlight” inside <div id="content">.
  2. Click on Link button.
  3. Enter URL.
  4. Click on save button.
  5. Now I place the cursor before “highlight” and add two empty space.
  6. Click on Link button.
  7. Click on Save button.
  8. This remove the two empty space I added before the word “highlight”.

I’m not able to share the repro gif. so here is the repro video link – https://github.com/fingers10/NugetVulnerabilityCheck/assets/43729469/3eac8cfe-4d8a-4e7b-a8a8-64c6d0585638

Please can you assist on what I’m missing?

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Highlighter</title>
<style>
    .highlighted {
        color: blue;
    }
    #popup {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: white;
        padding: 20px;
        border: 1px solid #ccc;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        z-index: 9999;
    }
</style>
</head>
<body>

<div id="content" contentEditable="plaintext-only">Select and highlight this text</div>
<button id="linkIcon">Link</button>

<!-- Popup -->
<div id="popup">
    <input type="text" id="selectedText" readonly>
    <input type="text" id="urlInput" placeholder="Enter URL">
    <button id="saveBtn">Save</button>
</div>

<script>
var textSelected = false;
var lastUrl = "";

document.addEventListener("DOMContentLoaded", function() {
    var contentDiv = document.getElementById("content");
    var linkIcon = document.getElementById("linkIcon");
    var popup = document.getElementById("popup");
    var selectedText = document.getElementById("selectedText");
    var urlInput = document.getElementById("urlInput");
    var selectionRange; // To store selection range
    var originalSelection = ""; // To store original selection
    var lastAnchorText = "";

    // Function to get selected text
    function getSelectedText() {
        if (window.getSelection) {
            return window.getSelection().toString();
        } else if (document.selection && document.selection.type != "Control") {
            return document.selection.createRange().text;
        }
        return "";
    }

    // Function to check if an anchor element already exists in the selection range
    function getExistingAnchor() {
        var nodes = selectionRange.cloneContents().childNodes;
        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeName === "A") {
                return nodes[i];
            }
        }
        return null;
    }

    // Function to check if there is an existing link in the content
    function checkExistingLink() {
        if (!textSelected) {
            return false;
        }

        var anchor = getExistingAnchor();
        if (anchor) {
            var existingLink = anchor.href;
            if (existingLink !== "") {
                urlInput.value = existingLink;
                selectedText.value = anchor.innerText;
                popup.style.display = "block";
                originalSelection = anchor.innerText;
                return true;
            }
        }
        return false;
    }

    // Event listener for link icon click
    linkIcon.addEventListener("click", function() {
        if (!checkExistingLink()) {
            var selection = getSelectedText().trim();
            if (selection !== "") {
                selectionRange = window.getSelection().getRangeAt(0); // Store selection range
                originalSelection = selection; // Store original selection
                selectedText.value = selection;
                if (lastUrl !== "") {
                    urlInput.value = lastUrl;
                } else {
                    urlInput.value = "https://";
                }
                popup.style.display = "block";
            } else {
                alert("Please select some text first.");
            }
        }
    });

    // Event listener for save button click
    document.getElementById("saveBtn").addEventListener("click", function() {
        var url = urlInput.value.trim();
        if (originalSelection !== "") {
            if (url !== "") {
                var existingAnchor = getExistingAnchor();
                var newNode;
                if (existingAnchor) {
                    existingAnchor.href = url;
                    existingAnchor.innerText = originalSelection;
                    newNode = existingAnchor;
                } else {
                    newNode = document.createElement("a");
                    newNode.href = url;
                    newNode.innerText = originalSelection; // Use original selection
                    newNode.classList.add("highlighted");
                }
                // Insert the anchor element directly at the selection range
                selectionRange.deleteContents();
                selectionRange.insertNode(newNode);
                popup.style.display = "none";
                originalSelection = ""; // Reset original selection
                lastUrl = url; // Update lastUrl
                textSelected = true;
            } else {
                alert("Please enter a URL.");
            }
        } else {
            alert("No text selected.");
        }
    });
});
</script>

</body>
</html>

How to apply CSS dynamically with JavaScript for the ToDo list?

I’m practicing in creating a basic To-Do web app and I can’t figure out how to make it so that after I click on “submit” it creates another task instead of replacing the existing one, I’ve tried to dynamically create another list, but it overlaps the existing one even if I position it absolutely, and I want it to look the same, to replicate the exact same CSS styling but with whatever I type on the input boxes, may you help me?
Here’s an example of how I was trying to make it look: Example

Here’s the code

//Dark mode background colors
document.addEventListener("DOMContentLoaded", function () {
  const darkModeToggle = document.getElementById("darkmode-toggle");
  const body = document.body;

  darkModeToggle.addEventListener("change", function () {
    if (this.checked) {
      body.classList.add("dark-mode");
    } else {
      body.classList.remove("dark-mode");
    }
  });
});

//Window pop up when clicking new button
document.addEventListener("DOMContentLoaded", function () {
  const newButton = document.getElementById("new-btn");
  const wholeContainer = document.querySelector(".whole-container");
  const submitButton = document.querySelector(".submit-btn");
  const taskNameInput = document.getElementById("taskName");
  const taskDayInput = document.getElementById("taskDay");
  const taskDateInput = document.getElementById("taskDate");
  const taskNameSpan = document.getElementById("task-name");
  const taskDaySpan = document.getElementById("task-day");
  const taskDateSpan = document.getElementById("task-date");
  const checkboxInput = document.getElementById("check-mark");

  function showWholeContainer() {
    wholeContainer.style.visibility = "visible";
    wholeContainer.style.opacity = "1";
    newButton.disabled = true;

    // Add event listener to close the window when clicking outside of it
    document.addEventListener("click", clickOutsideHandler);
  }

  function hideWholeContainer() {
    wholeContainer.style.opacity = "0";
    setTimeout(() => {
      wholeContainer.style.visibility = "hidden";
      newButton.disabled = false;
    }, 200);

    // Remove the event listener to prevent unnecessary checks
    document.removeEventListener("click", clickOutsideHandler);
  }

  function clickOutsideHandler(event) {
    if (!wholeContainer.contains(event.target) && event.target !== newButton) {
      hideWholeContainer();
    }
  }

  function resetForm() {
    taskNameInput.value = ""; // Clear task name input
    taskDayInput.value = ""; // Clear task date input
    taskDateInput.value = ""; // Clear task date input
  }

  function showTaskList() {
    const wholeContainerTwo = document.querySelector(".whole-container-two");
    wholeContainerTwo.style.visibility = "visible";
    wholeContainerTwo.style.opacity = "1";
  }

  function validateForm() {
    if (
      taskNameInput.value.trim() === "" ||
      taskDayInput.value.trim() === "" ||
      taskDateInput.value.trim() === ""
    ) {
      alert("Please fill out task name, day, and task date fields.");
      return false;
    }
    return true; // Return true if form is valid
  }

  newButton.addEventListener("click", function () {
    showWholeContainer();
  });

  checkboxInput.addEventListener("change", function () {
    if (this.checked) {
      taskNameSpan.classList.add("checked");
    } else {
      taskNameSpan.classList.remove("checked");
    }
  });

  submitButton.addEventListener("click", function (event) {
    event.preventDefault();
    if (validateForm()) {
      taskNameSpan.textContent = taskNameInput.value;
      taskDaySpan.textContent = taskDayInput.value;
      taskDateSpan.textContent = taskDateInput.value;
      resetForm();
      showTaskList();
      hideWholeContainer();
    }
  });

  hideWholeContainer(); // Initially hide the container
});
header {
  position: fixed;
}

body {
  margin: 0;
  padding: 0;
  font-family: "Roboto", sans-serif;
  background-color: #f1ebdd;
  overflow-x: hidden;
}

/*header*/

.nav-container {
  position: absolute;
  display: flex;
  flex-direction: column;
  flex: space-between;
  align-items: flex-start;
  z-index: 2;
}

.nav-container button {
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
  border-radius: 25px;
  border-color: transparent;
  background-color: #58585a;
  color: #f1ebdd;
  cursor: pointer;
}

.nav-container button:hover {
  background-color: #f1ebdd;
  color: #58585a;
  transition: 0.6ms;
}

.user-btn {
  margin: 45px 0px 0px 50px;
  padding: 25px 80px 25px 80px;
}

.mylist-btn {
  margin: 45px 0px 0px 50px;
  padding: 10% 25% 10% 26%;
}

.categories-btn {
  margin: 45px 0px 0px 50px;
  padding: 10% 20% 10% 21%;
}

.calendar-btn {
  margin: 45px 0px 0px 50px;
  padding: 10% 23% 10% 25%;
}

.settings-btn {
  margin: 45px 0px 0px 50px;
  padding: 10% 23% 10% 26%;
}

.about-btn {
  margin: 45px 0px 0px 50px;
  padding: 10% 23% 10% 23%;
}

/*Day/night Switch*/

.label-data {
  display: block;
  position: absolute;
  cursor: pointer;
  margin: 775px 0% 0% 50px;
  width: 200px;
  height: 80px;
  border-radius: 200px;
  background-color: #f1ebdd;
  box-shadow: inset 0px 0px 10px 2px rgba(0, 0, 0, 0.2);
  z-index: 2;
}

.label-data:after {
  position: absolute;
  content: "";
  width: 80px;
  height: 80px;
  top: 0px;
  left: 0px;
  border-radius: 180px;
  background: linear-gradient(180deg, rgb(255, 170, 58), #d8a80b);
}

.input-data {
  visibility: hidden;
  width: 0;
  height: 0;
}

.input-data:checked + .label-data {
  background: #242424;
}

.input-data:checked + .label-data:after {
  left: 200px;
  transform: translateX(-100%);
  background: linear-gradient(180deg, #a3a3a3, #353535);
}

.label-data,
.label-data:after {
  transition: 0.4s;
}

.label-data:active:after {
  width: 50px;
}

.label-data svg {
  position: absolute;
  width: 70px;
  top: 5px;
  z-index: 100;
}

.label-data svg.sun {
  left: 5px;
  fill: #f1ebdd;
  border-radius: 50px;
  transition: 0.4s;
}

.label-data svg.moon {
  left: 125px;
  fill: #333333;
  transition: 0.4s;
}

.input-data:checked + .label-data svg.sun {
  fill: #242424;
}

.input-data:checked + .label-data svg.moon {
  fill: #f1ebdd;
}

.input-data:checked + .label-data + .background {
  background: #242424;
}

/*sidebar fixed background color*/

.sidebar-background h1 {
  position: fixed;
  padding: 40% 7% 10% 10%;
  margin: -1% 90% 0% 0%;
  background-color: #dbd8d3;
  z-index: 1;
}

/*to-do list app*/

.todo-container {
  position: absolute;
  margin: 70px 0px 0px 90%;
  z-index: 100;
}

.todo-container .new {
  padding: 3px 18px 3px 18px;
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
  border-radius: 25px;
  color: #f1ebdd;
  background-color: #4eaabe;
  border-color: transparent;
  text-decoration: none;
  cursor: pointer;
  transition: 0.8s;
}

.todo-container .new:hover {
  padding: 5px 25px 5px 25px;
  transition: 0.8s;
}

/*to-do list app window pop up*/

.whole-container {
  display: hidden;
  opacity: 0;
  transition: visibility 0.8s ease, opacity 0.2s ease;
}

.hidden {
  /*JavaScript code*/
  display: none;
}

.window-wrapper {
  position: absolute;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  padding: 150px 150px;
  border-radius: 25px;
  background-color: #dbd8d3;
  transition: 0.4s;
  z-index: 1000;
}

.window-wrapper .form-data {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: left;
  gap: 25px;
  width: 150%;
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  border-radius: 25px;
}

.form-data label {
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
  text-align: center;
  color: #f1ebdd;
  border-radius: 25px;
  padding: 10px;
  background-color: #58585a;
}

.form-data input {
  border-color: transparent;
  border-radius: 25px;
  font-size: 20px;
  padding: 10px;
  color: #58585a;
  background-color: #f1ebdd;
  outline-color: #58585a;
}

.form-data .submit-btn {
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
  border-radius: 25px;
  border-color: transparent;
  background-color: #58585a;
  color: #f1ebdd;
  cursor: pointer;
}

/*to-do list after window*/

.whole-container-two {
  opacity: 0;
  visibility: hidden;
  transition: visibility 0.8s ease, opacity 0.2s ease;
}

.task-wrapper {
  position: absolute;
  margin: 177px 0% 0% 20%;
  padding: 0% 1% 0% 0%;
  border-radius: 25px;
  background-color: #4eaabe;
  z-index: -50;
}

.task-wrapper ul {
  list-style-type: none;
}

.task-list li {
  margin: 0px 0px 0px 30px;
}

.todo-item span {
  text-align: left;
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
}

.checkbox-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.checkmark-label {
  position: absolute;
  cursor: pointer;
}

.fa-circle-check {
  margin: -8px 0px 0px -60px;
  font-size: 40px;
  color: #f1ebdd;
  background-color: #f1ebdd;
  border-radius: 25px;
}

.checkbox-input:checked + .checkmark-label .fa-circle-check {
  color: #ace464;
  background-color: transparent;
  transition: 0.2s;
}

.todo-item .task-date-list {
  position: absolute;
  margin: -60px 0% 0% -70px;
  padding: 5px 1% 5px 1%;
  font-size: 20px;
  color: #bcb7af;
  z-index: -50;
}

.task-name-list {
  color: #f1ebdd;
}

.task-name-list.checked {
  text-decoration-line: line-through;
}

.todo-item .task-day-list {
  position: absolute;
  margin: -100px 0% 0% -75px;
  padding: 5px 1% 5px 1%;
  font-size: 25px;
  color: #58585a;
  z-index: -50;
}

/* Dark mode background colors */
.dark-mode {
  background-color: #242424;
  transition: 0.4s;
}

.dark-mode .nav-container button {
  background-color: #242424;
  color: #f1ebdd;
}

.dark-mode .nav-container button:hover {
  background-color: #f1ebdd;
  color: #242424;
  transition: 0.6ms;
}

.dark-mode .input-data:checked + .label-data {
  background: #242424;
}

.dark-mode .input-data:checked + .label-data:after {
  background: linear-gradient(180deg, #6e46fd, #2f0ea5);
}

.dark-mode .input-data:checked + .label-data svg.sun {
  fill: #242424;
}

.dark-mode .input-data:checked + .label-data svg.moon {
  fill: #f1ebdd;
}

.dark-mode .sidebar-background h1 {
  background: linear-gradient(180deg, #6e46fd, #2f0ea5);
  transition: 0.4s;
}

.dark-mode .window-wrapper {
  position: absolute;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  padding: 150px 150px;
  border-radius: 25px;
  background: linear-gradient(180deg, #6e46fd, #2f0ea5);
  transition: 0.4s;
  z-index: 300;
}

.dark-mode .form-data label {
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
  text-align: center;
  color: #f1ebdd;
  border-radius: 25px;
  padding: 10px;
  background-color: #242424;
}

.dark-mode .form-data input {
  border-color: transparent;
  border-radius: 25px;
  font-size: 20px;
  padding: 10px;
  color: #242424;
  background-color: #f1ebdd;
  outline-color: #242424;
}

.dark-mode .form-data .submit-btn {
  font-family: "Roboto", sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 20px;
  border-radius: 25px;
  border-color: transparent;
  background-color: #242424;
  color: #f1ebdd;
  cursor: pointer;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MyList: Enhanced to-do list app</title>
    <link href="style.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="script.js"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
      rel="stylesheet"
    />
    <script
      src="https://kit.fontawesome.com/3ba961a955.js"
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <header>
      <!--Sidebar navigation-->
      <nav class="nav-container" role="button">
        <button type="button" class="user-btn">User</button>
        <button type="button" class="mylist-btn">My lists</button>
        <button type="button" class="categories-btn">Categories</button>
        <button type="button" class="calendar-btn">Calendar</button>
        <button type="button" class="settings-btn">Settings</button>
        <button type="button" class="about-btn">About us</button>
      </nav>
      <!--Day/night Switch-->
      <input class="input-data" type="checkbox" id="darkmode-toggle" />
      <label class="label-data" for="darkmode-toggle">
        <svg
          version="1.1"
          class="sun"
          viewBox="0 0 24 24"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
          <g
            id="SVGRepo_tracerCarrier"
            stroke-linecap="round"
            stroke-linejoin="round"
          ></g>
          <g id="SVGRepo_iconCarrier">
            <path
              d="M12 3V4M12 20V21M4 12H3M6.31412 6.31412L5.5 5.5M17.6859 6.31412L18.5 5.5M6.31412 17.69L5.5 18.5001M17.6859 17.69L18.5 18.5001M21 12H20M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z"
              stroke="#f1ebdd"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
            ></path>
          </g>
        </svg>
        <svg
          version="1.1"
          class="moon"
          viewBox="0 0 24 24"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <g id="SVGRepo_bgCarrier1" stroke-width="0"></g>
          <g
            id="SVGRepo_tracerCarrier2"
            stroke-linecap="round"
            stroke-linejoin="round"
          ></g>
          <g id="SVGRepo_iconCarrier3">
            <path
              d="M3.32031 11.6835C3.32031 16.6541 7.34975 20.6835 12.3203 20.6835C16.1075 20.6835 19.3483 18.3443 20.6768 15.032C19.6402 15.4486 18.5059 15.6834 17.3203 15.6834C12.3497 15.6834 8.32031 11.654 8.32031 6.68342C8.32031 5.50338 8.55165 4.36259 8.96453 3.32996C5.65605 4.66028 3.32031 7.89912 3.32031 11.6835Z"
              stroke="#f1f1f1f1"
              stroke-width="0"
              stroke-linecap="round"
              stroke-linejoin="round"
            ></path>
          </g>
        </svg>
      </label>
      <!--sidebar fixed background color-->
      <div class="sidebar-background">
        <h1></h1>
      </div>
    </header>
    <!--to-do list app-->
    <div class="todo-container">
      <button type="button" class="new" id="new-btn">New</button>
    </div>
    <!--to-do list app window pop up-->
    <div class="whole-container">
      <div class="window-wrapper">
        <form class="form-data">
          <label for="taskName">Task Name</label>
          <input
            type="text"
            id="taskName"
            name="taskName"
            placeholder="What's in your mind?"
          />
          <label for="taskDate">Task Date and Day</label>
          <input
            type="text"
            id="taskDate"
            name="taskDate"
            placeholder="Set a date for your task"
          />
          <input
            type="text"
            id="taskDay"
            name="taskDay"
            placeholder="Set a day for your task"
          />
          <button type="submit" class="submit-btn">Submit</button>
        </form>
      </div>
    </div>
    <!--to-do list after window-->
    <div class="whole-container-two">
      <div class="task-wrapper">
        <ul class="task-list">
          <li class="todo-item">
            <input type="checkbox" id="check-mark" class="checkbox-input" />
            <label for="check-mark" class="checkmark-label"
              ><i class="fa-solid fa-circle-check"></i
            ></label>
            <span class="task-day-list" id="task-day">Task Day</span>
            <span class="task-date-list" id="task-date">Task Date</span>
            <span class="task-name-list" id="task-name">Task Name</span>
          </li>
        </ul>
      </div>
    </div>
  </body>
</html>

How to add chartjs/graph in and html and generate PDF from it using puppeteer

I am try to generate a pdf using puppeteer with my on custom HTML template.The problem I am facing is that when I set configuration in puppeteer config with "headless: false" to show virtual dom the chart is loading perfectly and it populate the data perfectly.
Now when i click on download (basically generate pdf), It is not loading the chart properly.
I have tried some options to wait like "await page.setContent(html, { waitUntil: ["load","networkidle0"] });" to wait until the data has been load into chartjs, but didn’t get the aspected ouput. here is my code.

async generatePdfFromHtml(html: string): Promise<Buffer> {
    try {
      const browser = await puppeteer.launch({
        headless: false,
        defaultViewport: null,
        // executablePath: '/usr/bin/chromium-browser',
        // args: ['--no-sandbox', '--disable-setuid-sandbox'],
      });

      const page = await browser.newPage();
      await page.setContent(html, { waitUntil: ["load","networkidle0"] });
      await page.addStyleTag({
        content: `
          * {
            transition: none !important;
            animation: none !important;
          }
        `,
      });

      const pdfBuffer = await page.pdf({
        format: 'A4',
        margin: {
          top: '50px',
          right: '30px',
          bottom: '50px',
          left: '30px',
        },
        printBackground: true,
      });

      setTimeout(async () => {
        await browser.close(); // Close the browser after 2 mint
      }, 120000);

      // await browser.close();
      return pdfBuffer;
    } catch (error) {
      console.log('ERR:', error);
    }
  }

here is my html code in which i am including chartjs as cdn.

<html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>Assessment</title>
          <!-- <link rel="stylesheet" type="text/css" href="./assessment.css" /> -->
          <style>
            * {
              margin: 0;
              padding: 0;
              box-sizing: border-box;
            }

            body {
              color: white;
              background-color: black;
              font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
            }

            .container {
              /* border: 1px solid white; */
              width: 80%;
              margin: 0 auto;
              max-width: 1200px;
              padding: 10px 20px;
            }

            .title {
              width: 100%;
              text-align: center;
              margin: 20px 0;
            }

            .container h4 {
              margin: 20px 0;
            }

            .container p {
              margin: 20px 0;
            }
          </style>
        </head>
        <body>
          <div class="container">
            <div class="title">
              <h2>BIQ Result</h2>
              <h4>Score</h4>
            </div>

            <div class="container">
              <h4>Dear Rashid</h4>

              <p>
                Thanks for completing the BIQ assessment. Your scores indicate your
                strength in each of the 5 star system. Stop, Think, Assess, Respond,
                Review.
              </p>

              <p>
                Each of the STARR skill set support your well being, living from
                purpose,self and other awarness, improved job relationship, descion
                making, effective responsiveness and refinning your behaviors for
                effective habits.
              </p>

              <p>Each of the STARR skill sets is addressed in BIQ app.</p>

              <h4>Now you know where to start. so let's begins!</h4>

              <div
                class="row-graph"
                style="width: 100%; height: 350px; overflow: hidden"
              >
                <canvas id="myChart" height="200"></canvas>
              </div>
            </div>

            <div class="title">
              <h4>Score</h4>
            </div>

            <div class="container">
              <p>
                Stop includes your ability to interrupt unproductive Thoughts, Feeling
                and Behaviors in order to be able to Think about your goals, Decide
                what to Do, Make the Plan for Doing It and then Responding.
              </p>
              <h4>Because we are humans we have a full range of em</h4>

              <p>
                Content for respondents who score in this range. Content for
                respondents who score in this type. <br />Content for respondents who
                score in this type. <br />Content for respondents who score in this
                type. <br />Content for respondents who score in this type.
              </p>
              <p>
                Your Global BIQ scores is based on the responses to S, T, A, R, R.<br />The
                goal is to have this at least 80%.
              </p>
              <p>
                To determine how.<br />Content for respondents who score in this type.
              </p>
            </div>
          </div>

          <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
          <script>
            const ctx = document.getElementById("myChart");
            console.log("ctx: ", ctx);

            const myChart = new Chart(ctx, {
              type: "bar",
              data: {
                labels: ["stop","think","assess","respond","review","general","globalBIQ","validity"],
                datasets: [
                  {
                    axis: "y",
                    label: "",
                    data: [48,62,58,70,60,55,0,50],
                    fill: false,
                    barThickness: 6,
                    backgroundColor: [
                      "rgb(244,67,54)",
                      "rgb(255,152,0)",
                      "rgb(255,235,59)",
                      "rgb(76,175,80)",
                      "rgb(33,150,243)",
                      "rgb(0,166,156)",
                      "rgb(139,24,229)",
                      "rgb(11,130,91)",
                    ],
                    borderColor: [
                      "rgb(244,67,54)",
                      "rgb(255,152,0)",
                      "rgb(255,235,59)",
                      "rgb(76,175,80)",
                      "rgb(33,150,243)",
                      "rgb(0,166,156)",
                      "rgb(139,24,229)",
                      "rgb(11,130,91)",
                    ],
                  },
                ],
              },
              options: {
                indexAxis: "y",
                maintainAspectRatio: false,
                scales: {
                  x: {
                    stacked: true,
                  },
                  y: {
                    stacked: true,
                  },
                },
              },
            });
          </script>
        </body>
      </html>

in virtual dom it loads like: enter image description here

while when i download file it look like:
enter image description here

Thanks for you cooperation.