Short lived Observables and higher-order mapping operators

I was reading this article and I can’t wrap my head around why switchMap operator is preferred over other operatros like mergeMap, concatMap, exhaustMap in case of short-lived, finite value, Observables like HTTP Request?

import { of, delay, switchMap, mergeMap, concatMap, exhaustMap } from 'rxjs';

function simulateHttp(val: any, due: number) {
  return of(val).pipe(delay(due));
}


const saveUser$ = simulateHttp("User saved", 1000);

const httpResult$ = saveUser$.pipe(
  switchMap(sourceValue => {
      console.log(sourceValue);
      return simulateHttp("Data reloaded", 2000);
      }
   )
);

httpResult$.subscribe(
  console.log,
  console.error,
  () => console.log('Completed httpResult$')
);

Unable to fetch json file via axios from local project root

I’m using React with Typescript and Webpack 5, served with webpack-dev-server. My project structure is as follows:

Project
 |-src
 | |-(All my code)
 |-app.config.json

I keep my backend URLs in a “app.config” file that’s located in the root directory and I need to read it somehow. Since it’s possible that it will change in runtime as I debug different servers, I’d like it to be loaded dynamically and I tried doing so via axios as axios.get('/app.config.json')

However, this results in a 404 Not Found error. Moreover, if I go directly by the address localhost:8080/app.config.json it will not be there either, as if not included at all when serving. Do I need to somehow include this file in webpack config?

I’d like to add that I would greatly prefer to keep the method of loading this file as axios from the root directory (not in /public, etc.) unless there’s absolutely no fix as other projects in my org follow this pattern and this particular one just doesn’t work after updating to wp5 for some reason.

litespeed cdn with quic cloud is creating errored JS code when it’s minified or cached. How do i turn this off?

I have a simple JS file that adds and removes classes to html elements. it’s working perfectly fine in my dev environment. However in production i noticed i’m getting an error in a file that litespeed created with as shown.
working js

the other image shows when it loads without litespeed there is no problem
issue js

I tried turning off JS setting but it’s not affecting the issue.
Tried flushing the cache and hard restarting on devices.

optimizedwebs.design

I removed the CDN and it works fine now, I’m wondering if it was configuration with litespeed, no docs on the matter that go into enough detail.

UseEffect in infinite loop because of dynamic url

I have looked through the topic already discussed, but I am not finding an answer for my case anywhere. I have a function:

export async function onlyMyErrorsMatter(){
  try {
    // Simulate API call
    const response = await fetch('www.some-url.com', {
      method: 'GET',
      headers: {
        'Authorization': 'Bearer invalid-token', // Simulate permission issue
      },
    });

    if (!response.ok) {
      console.error(`API Error: ${response.status} ${response.statusText}`);
      window.location.href = url; // Use the dynamic URL
     }

    }

    return await response.text();

  } catch (error) {
    console.error("Error caught in onlyMyErrorsMatter:", error);

    // Check if redirection is already done
    window.location.href = url; // Use the dynamic URL
  }
};

As you can see I use dynamic URL which comes from my env file which is provided by Vite:

const url = SOME_VITE_ENV_VAR

I have a react component, which I am using the data in.

function Component() {
  const [result, setResult] = useState("");

  useEffect(() => {
    const fetchResult = async () => {
      const result = await onlyMyErrorsMatter();
      if (result) {
        setResult(result);
      }
    };

    fetchData();
  }, []);

The problem is, when I end up at the endpoint which actually should fire an auth error and redirect, I get into the render loop, no redirect. When I however replace the url with a hardcoded one, like

const url = "www.example.com"

Loop disappears and all works as it should. But I need a dynamic URL here. Also it’s important errors are handled in the separate function, still I am only interested in redirects in special cases of API fetch problems, no other errors should fire a redirect.

Moreover, with dynamic urls my endpoint gets another addition of "/undefined", which is incredibly weird and I can’t really find where is it coming from. Seems like my env variable can be undefined? Is this causing the issue.

How do I keep the literal type information alive?

I have the following code snippet, my question is how do I keep the literal type information of knowLiteral without having to spell out the type like I am doing right now?

// @ts-check
/**
 * @typedef {Readonly<Record<string, string>>} ReadonlyStringRecord
 */

/**
 * @template {ReadonlyStringRecord} T
 */
class Main {
    /**
     * @param {T} knowLiteral
     */
    constructor(knowLiteral) {
        /**
         * @readonly
         * @type {T}
         */
        this.knowLiteral = knowLiteral;
    }
}


// how not to have to write this?
/**
 * @extends {Main<{
 *   this_is_know: '42'
 * }>}
 */
class Main2 extends Main {
    constructor() {
        const knowLiteral = /** @type {const} */ ({ 'this_is_know': '42' });
        super(knowLiteral);
    }

    testFunc() {
        //this should (and does) error.
        let x = this.knowLiteral.this_is_not_defined;
    }
}

Contact Form Error: Uncaught ReferenceError: validatetTextarea is not defined

At the bottom of the code, line 639

$('#form-submit').prop('disabled', false).attr("onclick", "window.location.href='#';");

Once # is replaced with any URL

Error appears: Uncaught ReferenceError: validatetTextarea is not defined

line 626 code:

else if (!validatetTextarea()) { e.preventDefault(); } });

Problem is, when contact form button is clicked, blank page appears with text ‘Something Went Wrong’
If # is replaced with google.com or any link, it still display the same error.


// clear form onload page
$(window).on('pageshow', function() {
  $('#contact')[0].reset();
});


$(document).ready(function() {
  // keypress enter - false
  $('#name, #email, #message').keypress(function (e) {
    let key = e.which;
    if(key == 13) {
        return false;  
    }
  });   


  //-------------------name validation-------------------


  // validate name
  $('#name').on('input blur keydown keyup change', function(){
    validateName();
  });

  let nameVal = false;

  // validation function
  function validateName() {
    let name = document.querySelector('#name');
    let regex = /^[a-zA-Zа-яА-ЯёЁіІїЇєЄ0-9.-_ ]+$/;
    let minLengthName = 2; 
    let maxLengthName  = 30; 
    let regexName = /(.)1{3}/;
    let firstChar = $("#name").val().charAt(0);

    // check max len
    $('#name').on('keydown keypress', function(e) {
      let val = $(this).val();
      if (val.length >= maxLengthName && e.keyCode !== 8 && e.keyCode !== 46) {
        e.preventDefault();
      }
    });

    // checking for an empty string
    if (name.value.length == 0) {
      $('#name').removeClass('valid');
      $('#name').removeClass('invalid');
      $('.valid_info_name').text('');
      nameVal = false;
      return false;
    }
    // check first character
    else if (!(/[a-zA-Zа-яА-ЯёЁіІїЇєЄ]/).test(firstChar)) {
      $('#name').removeClass('valid');
      $('#name').removeClass('invalid');
      $('.valid_info_name').text("The name entered is invalid. The first character must be a letter").css('color', 'red');
      $('#name').addClass('invalid'); 
      nameVal = false;
      return false;
    }
    // length check
    else if ((0 < name.value.length && name.value.length < minLengthName) || (name.value.length > maxLengthName)) {
      $('#name').removeClass('valid');
      $('#name').removeClass('invalid');
      $('.valid_info_name').text("The name entered is invalid. The name must be between " + minLengthName + " and " + maxLengthName + " characters").css('color', 'red');
      $('#name').addClass('invalid'); 
      nameVal = false;
      return false;
    }        
    // input validation no more than 3 identical characters in a row
    else if (regexName.test($('#name').val())) {
      $('#name').removeClass('valid');
      $('#name').removeClass('invalid');
      $('.valid_info_name').text("The name entered is invalid. Too many identical characters").css('color', 'red');
      $('#name').addClass('invalid'); 
      nameVal = false;
      return false;
    }
    // checking for valid characters
    else if (!regex.test($('#name').val())) {
      $('#name').removeClass('valid');
      $('#name').removeClass('invalid');
      $('.valid_info_name').text("The name entered is invalid. The name can only contain letters, numbers, spaces and symbols (._-)").css('color', 'red');
      $('#name').addClass('invalid'); 
      nameVal = false;
      return false;
    }
    else {
      $('#name').removeClass('valid');
      $('#name').removeClass('invalid');
      $('.valid_info_name').text("The entered name is valid").css('color', 'green');
      $('#name').addClass('valid');
      // check first character
      if (!(/[a-zA-Zа-яА-ЯёЁіІїЇєЄ]/).test(firstChar)) {
        $('#name').removeClass('valid');
        $('#name').removeClass('invalid');
        $('.valid_info_name').text("The name entered invalid. The first character must be a letter").css('color', 'red');
        $('#name').addClass('invalid'); 
        nameVal = false;
        return false;
      }
  
      nameVal = true;
      return true;
    }
  }


  // prohibit entering more than 3 identical characters in a row
  $(document).ready(function() {
    $('#name').on('keypress', function(e) {
      let re = /(.)1{2}/;
      if (re.test($('#name').val())) {
        e.preventDefault();
      }
    });
  });


  // first character can only be [a-zA-Zа-яА-ЯіІїЇєЄ]
  $(document).ready(function() {
    $('#name').on('keypress', function(event) {
      let inputName = $("#name");
      let valueName = inputName.val();
      let nameKey = String.fromCharCode(event.which);
      if (valueName.length === 0 && !/[a-zA-Zа-яА-ЯіІїЇєЄ]/.test(nameKey)) {
        event.preventDefault();
      }
    });
  });


  // allowed chars
  $('#name').on('keypress', function(e) {
    let allowedChars = /[a-zA-Zа-яА-ЯёЁіІїЇєЄ0-9'-._ ]/;
    let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
    if (!allowedChars.test(String.fromCharCode(charCode))) {
      e.preventDefault();
    }      
  });  


  //-------------------email validation-------------------


  var minLength = 8;
  var maxLength = 50;
  var emailRegEx = /^([a-zA-Z0-9._-]+|[a-zA-Z0-9]+(?:[._-][a-zA-Z0-9]+)*)@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
  var input = document.querySelector('#email');

  // check max len
  $('#email').on('keydown keypress', function(e) {
      let val = $(this).val(); 
      if (val.length >= maxLength && e.keyCode !== 8 && e.keyCode !== 46) {
        e.preventDefault();
      }
  });


  // check deny char    
  function denyChar(infoCallback) {
    $('#email').on('keypress', function(event) {
      let regex = /[a-zA-Z0-9.-_@]/;
      let char = String.fromCharCode(event.which);
      if (!regex.test(char)) {
        setTimeout(function() {    
            event.preventDefault();
            $('.valid_info_email').text('Latin letters, numbers and some symbols are allowed in this field').css('color', 'red'); 
            setTimeout(function() {
              infoCallback();
            }, 1000); 
        }, 1000);
        return false;
      }
      else { 
        infoCallback();      
        return true;
      }
    });
  }


  // validate email
  $('#email').on('input keypress blur keydown keyup change', function() {
    validateEmail();
  }); 

  let emailVal = false;

  function validateEmail() {
    if (input.value.length >= minLength && input.value.length <= maxLength) {  
      let regexFirst = /^[a-zA-Z0-9]+$/;
      let regexD = /(.)1{6}/;
      // check char "@"
      if (input.value.indexOf('@') !== -1) {
        let email = $('#email').val();
        let atIndex = email.indexOf('@');
        // check dots 
        if (atIndex >= 0) {
          let domain = email.split('@')[1];
          let numDots = (domain.match(/./g) || []).length;
          // check more than 2 dots in a row after "@"
          if (/@.*?.{2,}/.test(email)) {
            $('#email').removeClass('valid');
            $('#email').removeClass('invalid');
            $('.valid_info_email').text('The address entered is invalid. Check the address you entered').css('color', 'red');
            $('#email').addClass('invalid'); 
            let infoCallback = function() {
              $('.valid_info_email').text('The address entered is invalid. Check the address you entered').css('color', 'red');
            };
            denyChar(infoCallback);
            emailVal = false;
            return false;
          }
          // check more than 2 dots after "@"
          else if (numDots >= 3) {
            $('#email').removeClass('valid');
            $('#email').removeClass('invalid');
            $('.valid_info_email').text('The address entered is invalid. Too many dots after "@"').css('color', 'red');
            $('#email').addClass('invalid'); 
            let infoCallback = function() {
              $('.valid_info_email').text('The address entered is invalid. Too many dots after "@"').css('color', 'red');
            };
            denyChar(infoCallback);
            emailVal = false;
            return false;
          }
          // check first char
          else if (!regexFirst.test($('#email').val().charAt(0))) {
            $('#email').removeClass('valid');
            $('#email').removeClass('invalid');
            $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
            $('#email').addClass('invalid'); 
            let infoCallback = function() {
              $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
            };
            denyChar(infoCallback);
            emailVal = false;
            return false;
          } 
          // check duplicates
          else if (regexD.test($('#email').val())) {
            $('#email').removeClass('valid');
            $('#email').removeClass('invalid');
            $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
            $('#email').addClass('invalid'); 
            let infoCallback = function() {
              $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
            };
            denyChar(infoCallback);
            emailVal = false;
            return false;
          }
          else {
            // checking emailRegEx validation
            if (emailRegEx.test($('#email').val())) {
              // check first char
              if (!regexFirst.test($('#email').val().charAt(0))) {
                $('#email').removeClass('valid');
                $('#email').removeClass('invalid');
                $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
                $('#email').addClass('invalid'); 
                let infoCallback = function() {
                  $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
                };
                denyChar(infoCallback);
                emailVal = false;
                return false;
              } 
              // check duplicates
              else if (regexD.test($('#email').val())) {
                $('#email').removeClass('valid');
                $('#email').removeClass('invalid');
                $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
                $('#email').addClass('invalid'); 
                let infoCallback = function() {
                  $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
                };
                denyChar(infoCallback);
                emailVal = false;
                return false;
              }
              else {
                $('#email').removeClass('valid');
                $('#email').removeClass('invalid');
                $('.valid_info_email').text('The entered address is valid').css('color', 'green');
                $('#email').addClass('valid');
                let infoCallback = function() {
                  $('.valid_info_email').text('The entered address is valid').css('color', 'green');
                };
                denyChar(infoCallback);
                emailVal = true;
                return true;     
              }
            }
            else {
              $('#email').removeClass('valid');
              $('#email').removeClass('invalid');
              $('.valid_info_email').text('The address entered is invalid').css('color', 'red');
              $('#email').addClass('invalid'); 
              let infoCallback = function() {
                $('.valid_info_email').text('The address entered is invalid').css('color', 'red');
              };
              denyChar(infoCallback);
              emailVal = false;
              return false;
            }
          }
        }     
      }   
      // check first char
      else if (!regexFirst.test($("#email").val().charAt(0))) {
        $('#email').removeClass('valid');
        $('#email').removeClass('invalid');
        $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
        $('#email').addClass('invalid'); 
        let infoCallback = function() {
          $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
        };
        denyChar(infoCallback);
        emailVal = false;
        return false;
      } 
      // check duplicates
      else if (regexD.test($('#email').val())) {
        $('#email').removeClass('valid');
        $('#email').removeClass('invalid');
        $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
        $('#email').addClass('invalid'); 
        let infoCallback = function() {
          $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
        };
        denyChar(infoCallback);
        emailVal = false;
        return false;
      }
      else {
        $('#email').removeClass('valid');
        $('#email').removeClass('invalid');
        $('.valid_info_email').text('The entered address is invalid, please enter "@"').css('color', 'red');
        $('#email').addClass('invalid'); 
        let infoCallback = function() {
          $('.valid_info_email').text('The entered address is invalid, please enter "@"').css('color', 'red');
        };
        denyChar(infoCallback);
        emailVal = false;
        return false;
      }   
    }           
    else if (input.value.length == 0) {
        $('#email').removeClass('valid');
        $('#email').removeClass('invalid');
        $('.valid_info_email').text('');
        let infoCallback = function() {
          $('.valid_info_email').text('');
        };
        denyChar(infoCallback);
        emailVal = false;
        return false;
    }
    else {
      if ((input.value.length > 0 && input.value.length < minLength) || input.value.length > maxLength) {
        $('#email').removeClass('valid');
        $('#email').removeClass('invalid');
        $('.valid_info_email').text('The address entered is invalid, valid from ' + minLength + ' to ' + maxLength + ' characters').css('color', 'red');
        $('#email').addClass('invalid'); 
        let infoCallback = function() {
          $('.valid_info_email').text('The address entered is invalid, valid from ' + minLength + ' to ' + maxLength + ' characters').css('color', 'red');
        };
        denyChar(infoCallback);
        let regexFirst = /^[a-zA-Z0-9]+$/;
        let regexD = /(.)1{6}/;
        // check first char
        if (!regexFirst.test($("#email").val().charAt(0))) {
          $('#email').removeClass('valid');
          $('#email').removeClass('invalid');
          $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
          $('#email').addClass('invalid'); 
          let infoCallback = function() {
            $('.valid_info_email').text('The address entered is invalid. The first character can only be a letter or a number').css('color', 'red');
          };
          denyChar(infoCallback);
          emailVal = false;
          return false;
        } 
        // check duplicate
        else if (regexD.test($('#email').val())) {
          $('#email').removeClass('valid');
          $('#email').removeClass('invalid');
          $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
          $('#email').addClass('invalid'); 
          let infoCallback = function() {
            $('.valid_info_email').text('The address entered is invalid. Too many identical characters').css('color', 'red');
          };
          denyChar(infoCallback);
          emailVal = false;
          return false;
        }

        emailVal = false;
        return false;       
      }
    }
  }
  

  // input parameters
  $(document).ready(function() {    
    $('#email').on("keypress", function(event) {
      let input = $("#email");
      let value = input.val();
      let key = String.fromCharCode(event.which);
      // first character can only be [a-zA-Z0-9]
      if (value.length === 0 && !/[a-zA-Z0-9]/.test(key)) {
        event.preventDefault();
        return false;
      }
      // starting from the second, you can enter [a-zA-Z0-9.-_]
      if (value.length === 1 && !/[a-zA-Z0-9-_.]/.test(key)) {
        event.preventDefault();
        return false;
      }
      // starting from the third character, you can enter [a-zA-Z0-9.-_@]
      if (value.length >= 2 && !/[a-zA-Z0-9.-_@]/.test(key)) {
        event.preventDefault();
        return false;
      }
      // the input cannot contain two or more dots in a row (for example, '..' or '....')
      if (key === "." && value.slice(-1) === ".") {
        event.preventDefault();
        return false;
      }

      // in the input, the limit on the number of consecutive identical characters is up to 5
      $(document).ready(function() {
        $('#email').on('input', function() {
          let value = $(this).val();
          let regex = /(.)1{6}/;
          
          if (regex.test(value)) {
            $(this).val(value.slice(0, -1));
          }
        });
      });
    
      // after "@" the first character can only be entered [a-zA-Z0-9], starting from the second one can be entered [a-zA-Z0-9.-]
      $(document).ready(function() {
        $('#email').on('keypress', function(event) {
          let key = String.fromCharCode(event.which);
          let value = $(this).val();  

          // if the character "@" is entered, limit the input of the first character after "@"
          if (value.indexOf("@") !== -1 && value.indexOf(".") === -1 && value.slice(-1) === "@" && !/[a-zA-Z0-9]/.test(key)) {
            event.preventDefault();
            return false;
          }

          // after the first character after "@", allow letters, numbers, dots, hyphens, or underscores
          if (value.indexOf("@") !== -1 && value.indexOf(".", value.indexOf("@")+2) !== -1 && /[a-zA-Z0-9.-_]/.test(value.slice(-1)) && !/[a-zA-Z0-9.-_]/.test(key)) {
            event.preventDefault();
            return false;
          }

          // if a dog is found and the entered period is found after it, only the characters [a-zA-Z0-9.-_] are allowed
          if (value.indexOf("@") !== -1 && value.indexOf(".") !== -1 && value.indexOf(".") > value.indexOf("@") && !/[a-zA-Z0-9.-_]/.test(key)) {
            event.preventDefault();
            return false;
          }
          
          // if a dog is found, only 2 dots can be entered after it
          if (value.indexOf("@") !== -1 && value.indexOf(".", value.indexOf("@")+1) !== -1 && value.indexOf(".", value.indexOf("@")+1) !== value.lastIndexOf(".") && value.indexOf(".", value.indexOf("@")+1) !== -1 && !/[a-zA-Z]/.test(key)) {
            event.preventDefault();
            // if a point is found, allow input /[a-zA-Z0-9.-_]/
            if (value.indexOf("@") !== -1 && value.indexOf(".", value.indexOf("@")+1) !== -1 && value.indexOf(".", value.indexOf("@")+1) === value.lastIndexOf(".") && !/[a-zA-Z0-9.-_]/.test(key)) {
              event.preventDefault();    
              return false;
            }
            // if the second entered point is found after the dog, only characters [a-zA-Z] are allowed
            if (value.indexOf("@") !== -1 && value.indexOf(".", value.indexOf("@")) !== -1 && value.indexOf(".", value.indexOf(".", value.indexOf("@")+1)) !== -1 && !/[a-zA-Z]/.test(key)) {
              event.preventDefault();
              return false;
            }
                       
            return false;
          }     
        });
      });    
              
      
      // allow enter "@" only one times
      $(document).ready(function() {
        let inputField = $('#email');
        inputField.on('keypress', function(e) {
          let currentValue = $(this).val();
          if (e.which === 64 && currentValue.indexOf('@') !== -1) {
            e.preventDefault();
          }
        });
      });
    

      // allowed chars
      $('#email').on('keypress', function(e) {
          let allowedChars = /[a-zA-Z0-9._@-]/;
          let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
          if (!allowedChars.test(String.fromCharCode(charCode))) {
          e.preventDefault();
          }      
      });  
    });

  });

  
  //-------------------textarea validation-------------------
  

  // validate textarea
  $('#message').on('blur keydown keyup change', function() {
    validateTextarea();
  });

  let textareaVal = false;

  // validation function of Textarea
  function validateTextarea() {
    let message = $('#message').val();
    let regex_textarea = /^[a-zA-Zа-яА-ЯёЁіІїЇєЄ0-9., -'_]+$/;
    let maxLengthMessage = 500;

    $('#message').on('keydown keypress', function(e) {
      let val = $(this).val();
      if (val.length >= maxLengthMessage && e.keyCode !== 8 && e.keyCode !== 46) {
        e.preventDefault();
      }
    });

    // checking for an empty field
    if (message == 0) {
      $('#message').removeClass('valid');
      $('#message').removeClass('invalid');
      $('.valid_info_message').text("");
      textareaVal = false;
      return false;
    } 
    // if the field contains illegal characters
    else if (!regex_textarea.test(message)) {
      $('#message').removeClass('valid');
      $('#message').removeClass('invalid');
      $('.valid_info_message').text("Invalid characters entered").css('color', 'red');
      $('#message').addClass('invalid'); 
      textareaVal = false;
      return false;
    } 
    // if the field contains more than 500 characters
    else if (message.length > 500) {
      $('#message').removeClass('valid');
      $('#message').removeClass('invalid');
      $('.valid_info_message').text("The maximum message length is 500 characters").css('color', 'red');
      $('#message').addClass('invalid'); 
      textareaVal = false;
      return false;
    } 
    else { 
      $('#message').removeClass('valid');
      $('#message').removeClass('invalid');
      $('.valid_info_message').text("The entered message is valid").css('color', 'green');
      $('#message').addClass('valid');
      textareaVal = true;
      return true;
    }

  }


  // allowed characters
  $('#message').on('keypress', function(e) {
    let allowedChars = /^[a-zA-Zа-яА-ЯёЁіІїЇєЄ0-9., -'_]/;
    let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
    if (!allowedChars.test(String.fromCharCode(charCode))) {
      e.preventDefault();
    }      
  }); 


  // -----------------------------------------------------------
  
  // remove emoji
  $(document).ready(function(){
    var nameRegex = /^[a-zA-Zа-яА-ЯёЁіІїЇєЄ0-9.-_ ]+$/;
    var emailRegex = /^[a-zA-Z0-9._@-]+$/;
    var messageRegex = /^[a-zA-Zа-яА-ЯёЁіІїЇєЄ0-9., -'_]+$/;
  
    $('#name').on('input keydown keyup change', function() {
      removeInvalidChars($(this), nameRegex);
    });
  
    $('#email').on('input keydown keyup change', function() {
      removeInvalidChars($(this), emailRegex);
    });
  
    $('#message').on('input keydown keyup change', function() {
      removeInvalidChars($(this), messageRegex);
    });
  
    function removeInvalidChars(input, regex) {
      var str = input.val();
      str = str.replace(/[^ws.-@а-яА-ЯёЁіІїЇєЄ,']/g, '');
      input.val(str);
    }
  });
  
  // -----------------------------------------------------------
  
  // event handler on form submission
  $('form').on('submit', function(e) {
    // cancel form submission if validation fails
    if (!validateName()) {
      e.preventDefault();
    }
    else if (!validateEmail()) {
      e.preventDefault(); 
    }
    else if (!validatetTextarea()) {
      e.preventDefault(); 
    }   
  });
  

  // form validation
  $('#contact').on('input blur keyup change', function() {
    validateEmail();
    validateName();
    validateTextarea();

    if (emailVal === true && nameVal === true && textareaVal === true) {
      $('#form-submit').prop('disabled', false).attr("onclick", "window.location.href='#';"); 

      $('#name, #email, #message').keypress(function(e) {
        if (emailVal === true && nameVal === true && textareaVal === true) {
          var key = e.which;
          if(key == 13) {
            $('#form-submit').click();
            return false;  
          }
        }
      });   
      return true;
    } 
    else {
      $('#form-submit').prop('disabled', true);

      $('#name, #email, #message').keypress(function(e) {
        let key = e.which;
        if(key == 13) {
            return false;  
        }
      });  
      
      return false;
    }  
  });
  
});    ````


I changed return false/true on few locations, but nothing helped. Reverted it to the original in this post. Can't understand why form won't work. Where is the error? Thanks.

Apps Script to change cell on other 2 sheets, base on changed on first sheet

i have these column that i want to work on
i have initial list of name on ItemDB!D3:D
and these 2 column Purchasing!E2:E and Sale!G14:G that have part of the name from ItemDB!D3:D, also on Purchasing!E2:E and Sale!G14:G i use data validation from list of ItemDB!D3:D.
Then i make a button with the name of updateValues, and i try to make a apps script so when i clicked the button, it will check cell that change on ItemDB!D3:D then after find the change, it will also change the cell on Purchasing!E2:E and Sale!G14:G

the problem here are it run fine on small dataset, but when i applied to larger dataset, it not working properly. So can anyone help me to give some input what i need to do now?
here are some of the example and the code i use.

example:

ItemDB!D3:D

Apple –> changed to AppleX

Lemon

Banana

.

Purchasing!E2:E

Apple –> changed to AppleX when Apple on ItemDB!D3:D changed

Apple –> changed to AppleX when Apple on ItemDB!D3:D changed

Lemon

.

Sale!G14:G

Banana

Lemon

Apple –> changed to AppleX when Apple on ItemDB!D3:D changed

function updateValues() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const itemDBSheet = ss.getSheetByName('ItemDB');
  const purchasingSheet = ss.getSheetByName('Purchasing');
  const saleSheet = ss.getSheetByName('Sale');

  // Get current values from ItemDB!D3:D
  const currentRange = itemDBSheet.getRange('D3:D' + itemDBSheet.getLastRow());
  const currentValues = currentRange.getValues().flat();

  // Load previous values from Script Properties (or initialize if not set)
  const scriptProperties = PropertiesService.getScriptProperties();
  let previousValuesString = scriptProperties.getProperty('previousValues') || '';

  // Convert the string back to an array
  let previousValues = previousValuesString ? previousValuesString.split(',') : [];

  // Initialize previousValues with current values if empty
  if (previousValues.length === 0) {
    previousValues = [...currentValues];
  }

  // Get full ranges for Purchasing and Sale sheets
  const purchasingRange = purchasingSheet.getRange(2, 5, purchasingSheet.getLastRow() - 1, 1);
  const saleRange = saleSheet.getRange(14, 7, saleSheet.getLastRow() - 13, 1);

  const purchasingValues = purchasingRange.getValues();
  const saleValues = saleRange.getValues();

  // Create maps for quick lookups
  const purchasingMap = new Map(purchasingValues.map((value, index) => [value[0], index]));
  const saleMap = new Map(saleValues.map((value, index) => [value[0], index]));

  let changesMade = false;

  // Compare current and previous values
  for (let i = 0; i < currentValues.length; i++) {
    const currentValue = currentValues[i];
    const previousValue = previousValues[i] || '';

    if (currentValue !== previousValue) {
      // Update Purchasing sheet
      if (purchasingMap.has(previousValue)) {
        const rowIndex = purchasingMap.get(previousValue);
        purchasingValues[rowIndex][0] = currentValue;
        changesMade = true;
      }

      // Update Sale sheet
      if (saleMap.has(previousValue)) {
        const rowIndex = saleMap.get(previousValue);
        saleValues[rowIndex][0] = currentValue;
        changesMade = true;
      }

      // Update the previousValues array with the current value
      previousValues[i] = currentValue;
    }
  }

  // Apply the changes to the sheets if any changes were made
  if (changesMade) {
    purchasingRange.setValues(purchasingValues);
    saleRange.setValues(saleValues);
  }

  // Convert the updated previousValues array back to a string and store it in Script Properties
  scriptProperties.setProperty('previousValues', previousValues.join(','));
}

StyledMapType of terrain map type, and others?

As far as I can tell, a google.maps.StyledMapType is (at least by default) a styled version of the default roadmap (google.maps.MapTypeId.ROADMAP). Is there any way to create a StyledMapType based on another map type, such as the terrain map (google.maps.MapTypeId.TERRAIN)?

Context

I am insisting on a StyledMapType here because—as far as I can tell—other approaches will not work for my application.

Have to be able to turn style on and off on the fly

Whether or not this custom style applies depends on my application state. Any solution that permanently adjusts the terrain map, or requires creating a new map for the new style, is a no-go for me.

Cannot use the existing map type with an inline style

My map uses google.maps.AdvancedMarkerElement, which has a requirement that the map has a mapId from the Google Cloud Console.

Having a mapId causes map.setOptions({ styles: /*…*/ }) to fail, issuing a warning to the browser console explaining that it doesn’t work.

Cannot use Cloud-based styling

Having a mapId is supposed to be for using the Google Cloud Console to control styling—that’s why inline styles don’t work when you have one. But I can’t use that, because the Google account is owned by the client, and us contractors don’t have access. The client set up the mapId for me, but they are not willing to give us direct access, or figure out importing the styling themselves.

Beyond that, as far as I can tell from the documentation—which is terrible so I could be wrong—it doesn’t seem like this kind of styling is something you can change on the fly. So, for example, I could style the roadmap, but then the roadmap would always have that style. So even if I fought with the client to get access or convince them to import some styles for us, it doesn’t seem like it would solve things.

Example

const map = new google.maps.Map(containerElement, {
    mapId: 'some_map_id',
    mapTypeId: google.maps.MapTypeId.ROADMAP,
});

// Grayscale roadmap — WORKS
const grayscaleRoadmap = new google.maps.StyledMapType(
    [{ stylers: [{ saturation: '-100' }] }],
    { name: 'Grayscale Roadmap' },
);
map.mapTypes.set('grayscale_roadmap', grayscaleRoadmap);
if (shouldBeGrayscale && map.getMapTypeId() === google.maps.MapTypeId.ROADMAP) {
    map.setMapTypeId('grayscale_roadmap');
}

// Grayscale terrain map — DOES NOT WORK, exactly the same as the grayscale roadmap
const grayscaleTerrain = new google.maps.StyledMapType( // do I need to do something different here?
    [{ stylers: [{ saturation: '-100' }] }],
    { name: 'Grayscale Terrain Map' },
);
map.mapTypes.set('grayscale_terrain', grayscaleTerrain);
if (shouldBeGrayscale && map.getMapTypeId() === google.maps.MapTypeId.TERRAIN) {
    map.setMapTypeId('grayscale_terrain');
}

How to ignore single quotes in the validator.js isAlphanumeric function

I am trying to validate a string using express validator, specifically with the function isAlphanumeric, but I would like to add space, dash, and single quote as characters to accept.

Here is my code:

body("title")
    .trim()
    .isLength({ min: 1 })
    .escape()
    .withMessage("Le champ titre est obligatoire.")
    .isAlphanumeric("fr-FR", { ignore: " -'" })
    .withMessage("Le champ titre contient des caractères non autorisés."),

With this code, I can type in strings with dashes and spaces in the HTML form without them being rejected, but if I add a single quote, I get a validation error with the last “withMessage” error. The string does not pass the isAlphanumeric test.

I thought of a character escaping problem inside of the “ignore” string, so I have tried the following:

  • ” -‘”
  • ” -”'” (as seen here on another question) – quote backslash quote quote
  • / -‘/ (using RegExp)
  • /[ -‘]/ (and every combination with and [], I think)

I cannot find the proper way to include the single quote in the “ignore” option of the isAlphanumeric validator, and I feel like I am trying anything randomly until I find the right way to do it, which is not really satisfying. As everything works fine with other characters, I still think of a character escaping problem but I cannot figure out which one.

I am sure that ignoring single quotes has already been done, but I could not find the answer on Google or Stack Overflow, so I guess it was time I signed up on this site to ask this question by myself.
I would like a “regexp-free” solution if it exists, as it must be doable with the string version (I still need to improve my regexp skill).

Vite chrome-ext issue

My stack react, vite, ts. The only thing is, I changed the manifest a little bit. I need react (other scripts) to load as soon as I click on an extension, i.e. when I click on an extension the react app should appear.

//vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import svgr from 'vite-plugin-svgr';
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';

export default defineConfig({
  plugins: [react(), svgr(), TanStackRouterVite()],
  server: {
    watch: {
      usePolling: true,
    },
    host: true,
    strictPort: true,
    port: 3001,
  },
  build: {
    rollupOptions: {
      input: {
        content: 'src/content.tsx',
        background: 'src/background.ts',
      },
      output: {
        entryFileNames: 'assets/[name].js',
        assetFileNames: 'assets/[name].[ext]',
      },
    },
    outDir: 'dist',
    emptyOutDir: true,
    assetsDir: '.',
    sourcemap: false,
  },
});

// manifest.json
{
  "manifest_version": 3,
  "name": "Test",
  "version": "1.0",
  "action": { "default_icon": {} },
  "permissions": ["activeTab", "scripting"],
  "background": {
    "service_worker": "assets/background.js"
  },
  "icons": {
    "16": "favicon-16x16.png",
    "32": "favicon-32x32.png",
    "48": "icon-48x48.png",
    "128": "icon-128x128.png"
  },
  "web_accessible_resources": [
    {
      "resources": [
        "assets/content.css",
        "favicon-16x16.png",
        "favicon-32x32.png",
        "icon-48x48.png",
        "icon-128x128.png"
      ],
      "matches": []
    }
  ]
}
//background.ts
chrome.action.onClicked.addListener((tab) => {
  if (!tab.id) return;

  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ['assets/content.js'],
  });
});

I want to make a build for chrome extension, but the browser writes error(s) – Uncaught SyntaxError: Unexpected token ‘export’. It’s like it’s not transpiling the code correctly, I don’t know

Object not initialised in jest `describe`, but initialised in jest `it`

I’m coding a test with jest and I have a long input at the bottom of my test file.
Ideally I’d like to call my function under test once in a describe function and run loads of expect under different it functions.

When trying to access input within an it function, the input is initialised, but not when trying to access it from the body of a describe function.
Does anybody know why this is happening within describe and not it.

I know there are ways around this like move the input higher or move it to another file, but I’m trying to find a solution without doing either of these things.

import myFunction from './my-function';

describe('myFunction', () => {
  /**
   * Accessing myVeryLongInput from here throws:
   * ReferenceError: Cannot access 'myVeryLongInput' before initialization
   */
  const result1 = myFunction(myVeryLongInput);
  it('should update property 2310 to 2319 to be like so', () => {
    /**
     * Accessing myVeryLongInput from here is fine
     */
    const result2 = myFunction(myVeryLongInput);
    expect(result2['2310']).toBe('like so');
    expect(result2['2319']).toBe('like so');
  });
});

const myVeryLongInput = {
  /// 2330 lines of data
}

I know there are ways around this error, but I’m just trying to understand the js closure and scope better

Prevent a device from going to sleep when browsing a web page

I would like to prevent the device going to sleep while looking at my web page. I would like this to work for chrome and safari on both ios and andriod.

I have currently researched 2 possible options for this.

  1. Wavelock API This seems promising as the browser supports it natively. But does does not seem to be supported by many browsers just yet. Has anyone had succsess with this yet?
  2. NoSleep.js Seems widely used, but has many open issues and worried it will fall into disrepair. It seems to work by playing a video on the webapge.

The question I have are.

Has anyone had success with either of the above?

and

Is there other options I have not considered which you would reccomend?

My object goes to its initial value on state update

Hello i’m new to React and i’m trying to have something like tabs:
enter image description here
I want tabs not to be visible unless the previous component was completed or the button was clicked.

import { FaCheckCircle } from "react-icons/fa";
import Inicio from "./Inicio";
import Incluido from "./Incluido";
import Descripcion from "./Descripcion";
import React, { useState } from "react";
import { FaBookOpen } from "react-icons/fa";
import { FaCheckDouble } from "react-icons/fa";
function Home() {
    const [toggle, setToggle] = useState(0)
    const [opciones, setOpciones] = useState([{
        id: 0,
        texto: "Inicio",
        visibilidad: "",
        descripcionTitulo: "Bienvenido al programa de carga de Viajes",
        descripcion: "Selecciona lo que quieras hacer",
        icon: <FaCheckCircle />,
        next: next,
        contenido: <Inicio setToggle={setToggle} />,
        botonSiguiente: "Nuevo Viaje"
    },
    {
        id: 1,
        texto: "Descripción",
        visibilidad: "aa",
        descripcionTitulo: "Descripcion",
        descripcion: "detalles importantes",
        icon: <FaBookOpen />,
        next: next,
        contenido: <Descripcion setToggle={setToggle} />,
        botonSiguiente: "Siguiente"
    },
    {
        id: 2,
        texto: "Incluido / no",
        visibilidad: "hidden",
        descripcionTitulo: "Incluido/ No incluido",
        descripcion: "Si el servicio no está seleccionado, el cliente lo verá como no incluido.",
        icon: <FaCheckDouble />,
        next: next,
        contenido: <Incluido setToggle={setToggle} />,
        botonSiguiente: "Siguiente"

    }
    ])
    function next(next) {
        if (opciones[next.id + 1]) {
            setToggle(next.id + 1)
            setOpciones(opciones.map(e => (e.id === next.id + 1 ? { ...e, visibilidad: "descripcion 1" } : e)))
        }
    }

    return (
        <div className="App min-h-[100vh] grid grid-cols-12 gap-3 flex-row p-5 bg-slate-900">
            <aside className='col-span-4 md:col-span-4 lg:col-span-2 min-h-[50%] bg-slate-800 text-start border-l-2 border-slate-600'>
                <ul className='p-5 text-white font-semibold flex flex-col space-x-5'>
                    {opciones.map((opcion) => (
                        <div className={opcion.visibilidad} key={opcion.id}>
                            <li className="flex flex-row cursor-pointer mt-1" onClick={() => setToggle(opcion.id)}>
                                <span className='text-xl mr-2'> {opcion.icon} </span>
                                <span>  {opcion.texto} </span>
                            </li>
                        </div>
                    ))}
                </ul>
            </aside>
            <section className='col-span-8 md:col-span-8 lg:col-span-10 min-h-[50%] bg-slate-800 p-5'>
                {opciones.map((opcion) => (
                    <div key={opcion.id} className={toggle === opcion.id ? "show-content" : "content"}>
                        <div className='border-b text-left border-cyan-400 w-full pb-5'>
                            <h2 className='text-2xl font-semibold text-white'>
                                {opcion.descripcionTitulo}
                            </h2>
                            <span className='text-white'>
                                {opcion.descripcion}
                            </span>
                        </div>
                        {opcion.contenido}
                        <div className='mt-3 flex justify-end w-full'>
                            <button className='bg-cyan-500 text-white font-semibold p-4 rounded' onClick={() => opcion.next(opcion)}>
                                {opcion.botonSiguiente}
                            </button>
                        </div>
                    </div>
                ))}
            </section>
        </div >
    );
}

export default Home;

Right now when i click on the button in the first component “descripción” is displayed but then when i try to click on the button in the “descripción” component it goes back to being hidden. I’m not sure how to handle this?
Everytime “next” function is called it should check if there’s another component next in the array, display it and display the tab list title also but it’s not working since on every rerender it goes back being hidden.